From: gfx Date: Wed, 30 Sep 2009 11:00:33 +0000 (+0900) Subject: Move type coercion mechanism from Util/TypeConstraints.pm to Meta/TypeConstraint.pm X-Git-Tag: 0.37_01~21 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=gitmo%2FMouse.git;a=commitdiff_plain;h=ffbbf459fec594dcd08b5f7d05014740390bde58 Move type coercion mechanism from Util/TypeConstraints.pm to Meta/TypeConstraint.pm --- diff --git a/lib/Mouse/Meta/Attribute.pm b/lib/Mouse/Meta/Attribute.pm index 1ef8a77..4b3539f 100644 --- a/lib/Mouse/Meta/Attribute.pm +++ b/lib/Mouse/Meta/Attribute.pm @@ -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 { diff --git a/lib/Mouse/Meta/Class.pm b/lib/Mouse/Meta/Class.pm index c7c4433..b5adde8 100644 --- a/lib/Mouse/Meta/Class.pm +++ b/lib/Mouse/Meta/Class.pm @@ -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 { diff --git a/lib/Mouse/Meta/Method/Accessor.pm b/lib/Mouse/Meta/Method/Accessor.pm index db3903c..631fa32 100755 --- a/lib/Mouse/Meta/Method/Accessor.pm +++ b/lib/Mouse/Meta/Method/Accessor.pm @@ -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) { diff --git a/lib/Mouse/Meta/Method/Constructor.pm b/lib/Mouse/Meta/Method/Constructor.pm index 1dd8b3a..f956fa2 100644 --- a/lib/Mouse/Meta/Method/Constructor.pm +++ b/lib/Mouse/Meta/Method/Constructor.pm @@ -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"; diff --git a/lib/Mouse/Meta/TypeConstraint.pm b/lib/Mouse/Meta/TypeConstraint.pm index 436f690..b5cd0b6 100644 --- a/lib/Mouse/Meta/TypeConstraint.pm +++ b/lib/Mouse/Meta/TypeConstraint.pm @@ -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) = @_; + foreach my $pair (@{$coercions}) { + #my ($constraint, $converter) = @$pair; + if ($pair->[0]->check($thing)) { + local $_ = $thing; + return $pair->[1]->($thing); + } + } + return $thing; + }; + } + 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 { diff --git a/lib/Mouse/Util/TypeConstraints.pm b/lib/Mouse/Util/TypeConstraints.pm index db543f4..4ebb08a 100644 --- a/lib/Mouse/Util/TypeConstraints.pm +++ b/lib/Mouse/Util/TypeConstraints.pm @@ -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 {