Move type coercion mechanism from Util/TypeConstraints.pm to Meta/TypeConstraint.pm
gfx [Wed, 30 Sep 2009 11:00:33 +0000 (20:00 +0900)]
lib/Mouse/Meta/Attribute.pm
lib/Mouse/Meta/Class.pm
lib/Mouse/Meta/Method/Accessor.pm
lib/Mouse/Meta/Method/Constructor.pm
lib/Mouse/Meta/TypeConstraint.pm
lib/Mouse/Util/TypeConstraints.pm

index 1ef8a77..4b3539f 100644 (file)
@@ -265,15 +265,32 @@ sub create {
     return $self;
 }
 
+sub _coerce_and_verify {
+    my($self, $value, $instance) = @_;
+
+    my $type_constraint = $self->{type_constraint};
+
+    return $value if !$type_constraint;
+
+    if ($self->should_coerce && $type_constraint->has_coercion) {
+        $value = $type_constraint->coerce($value);
+    }
+
+    return $value if $type_constraint->check($value);
+
+    $self->verify_against_type_constraint($value);
+
+    return $value;
+}
+
 sub verify_against_type_constraint {
     my ($self, $value) = @_;
-    my $tc = $self->type_constraint;
-    return 1 unless $tc;
 
-    local $_ = $value;
-    return 1 if $tc->check($value);
+    my $type_constraint = $self->{type_constraint};
+    return 1 if !$type_constraint;;
+    return 1 if $type_constraint->check($value);
 
-    $self->verify_type_constraint_error($self->name, $value, $tc);
+    $self->verify_type_constraint_error($self->name, $value, $type_constraint);
 }
 
 sub verify_type_constraint_error {
index c7c4433..b5adde8 100644 (file)
@@ -171,18 +171,13 @@ sub _initialize_instance{
         my $key  = $attribute->name;
 
         if (defined($from) && exists($args->{$from})) {
-            $args->{$from} = $attribute->coerce_constraint($args->{$from})
-                if $attribute->should_coerce;
-
-            $attribute->verify_against_type_constraint($args->{$from});
-
-            $instance->{$key} = $args->{$from};
+            $instance->{$key} = $attribute->_coerce_and_verify($args->{$from});
 
             weaken($instance->{$key})
                 if ref($instance->{$key}) && $attribute->is_weak_ref;
 
             if ($attribute->has_trigger) {
-                push @triggers_queue, [ $attribute->trigger, $args->{$from} ];
+                push @triggers_queue, [ $attribute->trigger, $instance->{$from} ];
             }
         }
         else {
index db3903c..631fa32 100755 (executable)
@@ -15,7 +15,7 @@ sub _install_accessor{
     my $should_deref  = $attribute->should_auto_deref;
     my $should_coerce = $attribute->should_coerce;
 
-    my $compiled_type_constraint    = $constraint ? $constraint->_compiled_type_constraint : undef;
+    my $compiled_type_constraint = $constraint ? $constraint->_compiled_type_constraint : undef;
 
     my $self  = '$_[0]';
     my $key   = sprintf q{"%s"}, quotemeta $name;
@@ -41,11 +41,14 @@ sub _install_accessor{
         my $value = '$_[1]';
 
         if ($constraint) {
+            if(!$compiled_type_constraint){
+                Carp::confess("[BUG]Missing compiled type constraint for $constraint");
+            }
             if ($should_coerce) {
                 $accessor .=
                     "\n".
                     '#line ' . __LINE__ . ' "' . __FILE__ . "\"\n" .
-                    'my $val = Mouse::Util::TypeConstraints->typecast_constraints("'.$attribute->associated_class->name.'", $attribute->{type_constraint}, '.$value.');';
+                    'my $val = $constraint->coerce('.$value.');';
                 $value = '$val';
             }
             if ($compiled_type_constraint) {
index 1dd8b3a..f956fa2 100644 (file)
@@ -15,7 +15,7 @@ sub generate_constructor_method_inline {
     my @compiled_constraints = map { $_->_compiled_type_constraint }
                                map { $_->{type_constraint} ? $_->{type_constraint} : () } @attrs;
 
-    my $code = <<"...";
+    my $code = sprintf("#line %d %s\n", __LINE__, __FILE__).<<"...";
     sub {
         my \$class = shift;
         return \$class->Mouse::Object::new(\@_)
@@ -48,34 +48,28 @@ sub _generate_processattrs {
         if (defined $attr->init_arg) {
             my $from = $attr->init_arg;
 
-            $code .= "if (exists \$args->{'$from'}) {\n";
+            $code .= "if (exists \$args->{q{$from}}) {\n";
 
-            if ($attr->should_coerce && $attr->type_constraint) {
-                $code .= "my \$value = Mouse::Util::TypeConstraints->typecast_constraints('".$attr->associated_class->name."', \$attrs[$index]->{type_constraint}, \$args->{'$from'});\n";
-            }
-            else {
-                $code .= "my \$value = \$args->{'$from'};\n";
-            }
+            my $value = "\$args->{q{$from}}";
+            if(my $type_constraint = $attr->type_constraint){
+                if($attr->should_coerce && $type_constraint->has_coercion){
+                    $code .= "my \$value = \$attrs[$index]->{type_constraint}->coerce(\$args->{q{$from}});\n";
+                    $value = '$value';
+                }
 
-            if ($attr->has_type_constraint) {
-                $code .= "unless (\$compiled_constraints[$index](\$value)) {";
-                $code .= "
-                        \$attrs[$index]->verify_type_constraint_error(
-                            q{$key}, \$value, \$attrs[$index]->type_constraint
-                        )
-                    }
-                ";
+                $code .= "\$compiled_constraints[$index]->($value)\n";
+                $code .= "  or \$attrs[$index]->verify_type_constraint_error(q{$key}, $value, \$attrs[$index]->{type_constraint});\n";
             }
 
-            $code .= "\$instance->{q{$key}} = \$value;\n";
+            $code .= "\$instance->{q{$key}} = $value;\n";
 
             if ($attr->is_weak_ref) {
-                $code .= "Scalar::Util::weaken( \$instance->{q{$key}} ) if ref( \$value );\n";
+                $code .= "Scalar::Util::weaken( \$instance->{q{$key}} ) if ref($value);\n";
             }
 
             if ($attr->has_trigger) {
                 $has_triggers++;
-                $code .= "push \@triggers, [\$attrs[$index]->{trigger}, \$value];\n";
+                $code .= "push \@triggers, [\$attrs[$index]->{trigger}, $value];\n";
             }
 
             $code .= "\n} else {\n";
@@ -89,24 +83,24 @@ sub _generate_processattrs {
                 $code .= "my \$value = ";
 
                 if ($attr->should_coerce && $attr->type_constraint) {
-                    $code .= "Mouse::Util::TypeConstraints->typecast_constraints('".$attr->associated_class->name."', \$attrs[$index]->{type_constraint}, ";
+                    $code .= "\$attrs[$index]->_coerce_and_verify(";
                 }
 
-                    if ($attr->has_builder) {
-                        $code .= "\$instance->$builder";
-                    }
-                    elsif (ref($default) eq 'CODE') {
-                        $code .= "\$attrs[$index]->{default}->(\$instance)";
-                    }
-                    elsif (!defined($default)) {
-                        $code .= 'undef';
-                    }
-                    elsif ($default =~ /^\-?[0-9]+(?:\.[0-9]+)$/) {
-                        $code .= $default;
-                    }
-                    else {
-                        $code .= "'$default'";
-                    }
+                if ($attr->has_builder) {
+                    $code .= "\$instance->$builder()";
+                }
+                elsif (ref($default) eq 'CODE') {
+                    $code .= "\$attrs[$index]->{default}->(\$instance)";
+                }
+                elsif (!defined($default)) {
+                    $code .= 'undef';
+                }
+                elsif ($default =~ /^\-?[0-9]+(?:\.[0-9]+)$/) {
+                    $code .= $default;
+                }
+                else {
+                    $code .= "'$default'";
+                }
 
                 if ($attr->should_coerce) {
                     $code .= ");\n";
index 436f690..b5cd0b6 100644 (file)
@@ -51,6 +51,25 @@ sub new {
     my $self = bless \%args, $class;
     $self->compile_type_constraint() if !$self->{hand_optimized_type_constraint};
 
+    if($self->{type_constraints}){ # Union
+        my @coercions;
+        foreach my $type(@{$self->{type_constraints}}){
+            if($type->has_coercion){
+                push @coercions, $type;
+            }
+        }
+        if(@coercions){
+            $self->{_compiled_type_coercion} = sub {
+                my($thing) = @_;
+                foreach my $type(@coercions){
+                    my $value = $type->coerce($thing);
+                    return $value if $self->check($value);
+                }
+                return $thing;
+            };
+        }
+    }
+
     return $self;
 }
 
@@ -79,6 +98,7 @@ sub message { $_[0]->{message} }
 
 sub _compiled_type_constraint{ $_[0]->{compiled_type_constraint} }
 
+sub has_coercion{ exists $_[0]->{_compiled_type_coercion} }
 
 sub compile_type_constraint{
     my($self) = @_;
@@ -134,9 +154,60 @@ sub compile_type_constraint{
     return;
 }
 
+sub _add_type_coercions{
+    my $self = shift;
+
+    my $coercions = ($self->{_coercion_map} ||= []);
+    my %has       = map{ $_->[0] => undef } @{$coercions};
+
+    for(my $i = 0; $i < @_; $i++){
+        my $from   = $_[  $i];
+        my $action = $_[++$i];
+
+        if(exists $has{$from}){
+            confess("A coercion action already exists for '$from'");
+        }
+
+        my $type = Mouse::Util::TypeConstraints::find_or_parse_type_constraint($from)
+            or confess("Could not find the type constraint ($from) to coerce from");
+
+        push @{$coercions}, [ $type => $action ];
+    }
+
+    # compile
+    if(exists $self->{type_constraints}){ # union type
+        confess("Cannot add additional type coercions to Union types");
+    }
+    else{
+        $self->{_compiled_type_coercion} = sub {
+           my($thing) = @_;\r
+           foreach my $pair (@{$coercions}) {\r
+                #my ($constraint, $converter) = @$pair;\r
+                if ($pair->[0]->check($thing)) {\r
+                  local $_ = $thing;
+                  return $pair->[1]->($thing);
+                }\r
+           }\r
+           return $thing;\r
+        };
+    }
+    return;
+}
+
 sub check {
     my $self = shift;
-    $self->_compiled_type_constraint->(@_);
+    return $self->_compiled_type_constraint->(@_);
+}
+
+sub coerce {
+    my $self = shift;
+    if(!$self->{_compiled_type_coercion}){
+        confess("Cannot coerce without a type coercion ($self)");
+    }
+
+    return $_[0] if $self->_compiled_type_constraint->(@_);
+
+    return $self->{_compiled_type_coercion}->(@_);
 }
 
 sub get_message {
index db543f4..4ebb08a 100644 (file)
@@ -150,35 +150,12 @@ sub subtype {
 }
 
 sub coerce {
-    my $name = shift;
+    my $type_name = shift;
 
-    $name =~ s/\s+//g;
-    confess "Cannot find type '$name', perhaps you forgot to load it."
-        unless $TYPE{$name};
+    my $type = find_type_constraint($type_name)
+        or confess("Cannot find type '$type_name', perhaps you forgot to load it.");
 
-    unless ($COERCE{$name}) {
-        $COERCE{$name}      = {};
-        $COERCE_KEYS{$name} = [];
-    }
-
-    my $package_defined_in = caller;
-
-    while (my($from, $action) = splice @_, 0, 2) {
-        $from =~ s/\s+//g;
-
-        confess "A coercion action already exists for '$from'"
-            if $COERCE{$name}->{$from};
-
-        my $type = find_or_parse_type_constraint($from, $package_defined_in);
-        if (!$type) {
-            confess "Could not find the type constraint ($from) to coerce from"
-        }
-
-        warn "# REGISTER COERCE $name, from $type\n" if _DEBUG;
-
-        push @{ $COERCE_KEYS{$name} }, $type;
-        $COERCE{$name}->{$from} = $action;
-    }
+    $type->_add_type_coercions(@_);
     return;
 }
 
@@ -214,35 +191,10 @@ sub role_type {
 
 # this is an original method for Mouse
 sub typecast_constraints {
-    my($class, $pkg, $types, $value) = @_;
+    my($class, $pkg, $type, $value) = @_;
     Carp::croak("wrong arguments count") unless @_ == 4;
 
-    local $_;
-    for my $type ($types->{type_constraints} ? @{$types->{type_constraints}} : $types ) {
-        for my $coerce_type (@{ $COERCE_KEYS{$type}}) {
-
-            if(_DEBUG){
-                warn sprintf "# COERCE: from %s to %s for %s (%s)\n",
-                    $coerce_type, $type, defined($value) ? "'$value'" : 'undef',
-                    $coerce_type->check($value) ? "try" : "skip";
-            }
-
-            next if !$coerce_type->check($value);
-
-            # try to coerce
-            $_ = $value;
-            my $coerced = $COERCE{$type}->{$coerce_type}->($value); # coerce
-
-            if(_DEBUG){
-                warn sprintf "# COERCE: got %s, which is%s %s\n",
-                    defined($coerced) ? $coerced : 'undef', $types->check($coerced) ? '' : ' not', $types;
-            }
-
-            # check with $types, not $constraint
-            return $coerced if $types->check($coerced);
-        }
-    }
-    return $value; # returns original $value
+    return $type->coerce($value);
 }
 
 sub enum {