foo
[gitmo/Moose.git] / lib / Moose / Meta / TypeConstraint.pm
index bb16b01..518cd29 100644 (file)
@@ -5,58 +5,155 @@ use strict;
 use warnings;
 use metaclass;
 
-use Sub::Name 'subname';
-use Carp      'confess';
+use Sub::Name    'subname';
+use Carp         'confess';
+use Scalar::Util 'blessed';
 
-our $VERSION = '0.01';
+our $VERSION = '0.07';
+
+use Moose::Meta::TypeConstraint::Union;
 
 __PACKAGE__->meta->add_attribute('name'       => (reader => 'name'      ));
 __PACKAGE__->meta->add_attribute('parent'     => (reader => 'parent'    ));
 __PACKAGE__->meta->add_attribute('constraint' => (reader => 'constraint'));
+__PACKAGE__->meta->add_attribute('message'   => (
+    accessor  => 'message',
+    predicate => 'has_message'
+));
+__PACKAGE__->meta->add_attribute('coercion'   => (
+    accessor  => 'coercion',
+    predicate => 'has_coercion'
+));
 
 # private accessor
 __PACKAGE__->meta->add_attribute('compiled_type_constraint' => (
     accessor => '_compiled_type_constraint'
 ));
 
-__PACKAGE__->meta->add_attribute('coercion_code' => (
-    reader    => 'coercion_code',
-    writer    => 'set_coercion_code',        
-    predicate => 'has_coercion'
+__PACKAGE__->meta->add_attribute('hand_optimized_type_constraint' => (
+    init_arg  => 'optimized',
+    accessor  => 'hand_optimized_type_constraint',
+    predicate => 'has_hand_optimized_type_constraint',    
 ));
 
 sub new { 
-    my $class  = shift;
-    my $self = $class->meta->new_object(@_);
+    my $class = shift;
+    my $self  = $class->meta->new_object(@_);
     $self->compile_type_constraint();
     return $self;
 }
 
-sub compile_type_constraint () {
-    my $self   = shift;
-    my $check  = $self->constraint;
+sub coerce { 
+    ((shift)->coercion || confess "Cannot coerce without a type coercion")->coerce(@_) 
+}
+
+sub _collect_all_parents {
+    my $self = shift;
+    my @parents;
+    my $current = $self->parent;
+    while (defined $current) {
+        push @parents => $current;
+        $current = $current->parent;
+    }
+    return @parents;
+}
+
+sub compile_type_constraint {
+    my $self  = shift;
+    
+    if ($self->has_hand_optimized_type_constraint) {
+        my $type_constraint = $self->hand_optimized_type_constraint;
+        $self->_compiled_type_constraint(sub {
+            return undef unless $type_constraint->($_[0]);
+            return 1;
+        });
+        return;
+    }
+    
+    my $check = $self->constraint;
     (defined $check)
         || confess "Could not compile type constraint '" . $self->name . "' because no constraint check";
     my $parent = $self->parent;
     if (defined $parent) {
-        $parent = $parent->_compiled_type_constraint;
+        # we have a subtype ...    
+        # so we gather all the parents in order
+        # and grab their constraints ...
+        my @parents;
+        foreach my $parent ($self->_collect_all_parents) {
+            if ($parent->has_hand_optimized_type_constraint) {
+                unshift @parents => $parent->hand_optimized_type_constraint;
+                last;                
+            }
+            else {
+                unshift @parents => $parent->constraint;
+            }
+        }
+        
+        # then we compile them to run without
+        # having to recurse as we did before
                $self->_compiled_type_constraint(subname $self->name => sub {                   
                        local $_ = $_[0];
-                       return undef unless defined $parent->($_[0]) && $check->($_[0]);
-                       $_[0];
-               });        
+            foreach my $parent (@parents) {
+                return undef unless $parent->($_[0]);
+            }
+                       return undef unless $check->($_[0]);
+                       1;
+               });               
     }
     else {
+        # we have a type ....
        $self->_compiled_type_constraint(subname $self->name => sub { 
                local $_ = $_[0];
                return undef unless $check->($_[0]);
-               $_[0];
+               1;
        });
     }
 }
 
-# backwards for now
-sub constraint_code { (shift)->_compiled_type_constraint }
+sub check { $_[0]->_compiled_type_constraint->($_[1]) }
+
+sub validate { 
+    my ($self, $value) = @_;
+    if ($self->_compiled_type_constraint->($value)) {
+        return undef;
+    }
+    else {
+        if ($self->has_message) {
+            local $_ = $value;
+            return $self->message->($value);
+        }
+        else {
+            return "Validation failed for '" . $self->name . "' failed";
+        }
+    }
+}
+
+sub is_a_type_of {
+    my ($self, $type_name) = @_;
+    ($self->name eq $type_name || $self->is_subtype_of($type_name));
+}
+
+sub is_subtype_of {
+    my ($self, $type_name) = @_;
+    my $current = $self;
+    while (my $parent = $current->parent) {
+        return 1 if $parent->name eq $type_name;
+        $current = $parent;
+    }
+    return 0;
+}
+
+sub union {
+    my ($class, @type_constraints) = @_;
+    (scalar @type_constraints >= 2)
+        || confess "You must pass in at least 2 Moose::Meta::TypeConstraint instances to make a union";    
+    (blessed($_) && $_->isa('Moose::Meta::TypeConstraint'))
+        || confess "You must pass in only Moose::Meta::TypeConstraint instances to make unions"
+            foreach @type_constraints;
+    return Moose::Meta::TypeConstraint::Union->new(
+        type_constraints => \@type_constraints,
+    );
+}
 
 1;
 
@@ -66,12 +163,19 @@ __END__
 
 =head1 NAME
 
-Moose::Meta::TypeConstraint - The Moose Type Constraint metaobject
-
-=head1 SYNOPSIS
+Moose::Meta::TypeConstraint - The Moose Type Constraint metaclass
 
 =head1 DESCRIPTION
 
+For the most part, the only time you will ever encounter an 
+instance of this class is if you are doing some serious deep 
+introspection. This API should not be considered final, but 
+it is B<highly unlikely> that this will matter to a regular 
+Moose user.
+
+If you wish to use features at this depth, please come to the 
+#moose IRC channel on irc.perl.org and we can talk :)
+
 =head1 METHODS
 
 =over 4
@@ -80,25 +184,54 @@ Moose::Meta::TypeConstraint - The Moose Type Constraint metaobject
 
 =item B<new>
 
+=item B<is_a_type_of ($type_name)>
+
+This checks the current type name, and if it does not match, 
+checks if it is a subtype of it.
+
+=item B<is_subtype_of ($type_name)>
+
+=item B<compile_type_constraint>
+
+=item B<coerce ($value)>
+
+This will apply the type-coercion if applicable.
+
+=item B<check ($value)>
+
+This method will return a true (C<1>) if the C<$value> passes the 
+constraint, and false (C<0>) otherwise.
+
+=item B<validate ($value)>
+
+This method is similar to C<check>, but it deals with the error 
+message. If the C<$value> passes the constraint, C<undef> will be 
+returned. If the C<$value> does B<not> pass the constraint, then 
+the C<message> will be used to construct a custom error message.  
+
 =item B<name>
 
 =item B<parent>
 
-=item B<check>
-
 =item B<constraint>
 
-=item B<coerce>
+=item B<has_message>
 
-=item B<coercion_code>
+=item B<message>
 
-=item B<set_coercion_code>
+=item B<has_coercion>
 
-=item B<constraint_code>
+=item B<coercion>
 
-=item B<has_coercion>
+=item B<hand_optimized_type_constraint>
 
-=item B<compile_type_constraint>
+=item B<has_hand_optimized_type_constraint>
+
+=back
+
+=over 4
+
+=item B<union (@type_constraints)>
 
 =back
 
@@ -121,4 +254,4 @@ L<http://www.iinteractive.com>
 This library is free software; you can redistribute it and/or modify
 it under the same terms as Perl itself. 
 
-=cut
\ No newline at end of file
+=cut