X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FMoose%2FMeta%2FAttribute.pm;h=e347197cfbc199c5f0827dfedf7671871eb597e3;hb=064a13a3104c7c38981bdc571b130da00b59945a;hp=ed8770950b403feadc5cb185dc37d59c47bb99e8;hpb=d5f6cadef8d83deaf7dd95302908cd4f61aeab8a;p=gitmo%2FMoose.git diff --git a/lib/Moose/Meta/Attribute.pm b/lib/Moose/Meta/Attribute.pm index ed87709..e347197 100644 --- a/lib/Moose/Meta/Attribute.pm +++ b/lib/Moose/Meta/Attribute.pm @@ -4,7 +4,8 @@ package Moose::Meta::Attribute; use strict; use warnings; -use Class::MOP (); +use B (); +use Class::Load qw(is_class_loaded load_class); use Scalar::Util 'blessed', 'weaken'; use List::MoreUtils 'any'; use Try::Tiny; @@ -24,6 +25,7 @@ Class::MOP::MiniTrait::apply(__PACKAGE__, 'Moose::Meta::Object::Trait'); __PACKAGE__->meta->add_attribute('traits' => ( reader => 'applied_traits', predicate => 'has_applied_traits', + Class::MOP::_definition_context(), )); # we need to have a ->does method in here to @@ -39,25 +41,46 @@ sub does { return $self->Moose::Object::does($name); } +sub _error_thrower { + my $self = shift; + require Moose::Meta::Class; + ( ref $self && $self->associated_class ) || "Moose::Meta::Class"; +} + sub throw_error { my $self = shift; - my $class = ( ref $self && $self->associated_class ) || "Moose::Meta::Class"; + my $inv = $self->_error_thrower; unshift @_, "message" if @_ % 2 == 1; unshift @_, attr => $self if ref $self; - unshift @_, $class; - my $handler = $class->can("throw_error"); # to avoid incrementing depth by 1 + unshift @_, $inv; + my $handler = $inv->can("throw_error"); # to avoid incrementing depth by 1 goto $handler; } sub _inline_throw_error { my ( $self, $msg, $args ) = @_; - "\$meta->throw_error($msg" . ($args ? ", $args" : "") . ")"; # FIXME makes deparsing *REALLY* hard + + my $inv = $self->_error_thrower; + # XXX ugh + $inv = 'Moose::Meta::Class' unless $inv->can('_inline_throw_error'); + + # XXX ugh ugh UGH + my $class = $self->associated_class; + if ($class) { + my $class_name = B::perlstring($class->name); + my $attr_name = B::perlstring($self->name); + $args = 'attr => Class::MOP::class_of(' . $class_name . ')' + . '->find_attribute_by_name(' . $attr_name . '), ' + . (defined $args ? $args : ''); + } + + return $inv->_inline_throw_error($msg, $args) } sub new { my ($class, $name, %options) = @_; $class->_process_options($name, \%options) unless $options{__hack_no_process_options}; # used from clone()... YECHKKK FIXME ICKY YUCK GROSS - + delete $options{__hack_no_process_options}; my %attrs = @@ -106,6 +129,8 @@ sub interpolate_class { if (my $traits = $options->{traits}) { my $i = 0; + my $has_foreign_options = 0; + while ($i < @$traits) { my $trait = $traits->[$i++]; next if ref($trait); # options to a trait we discarded @@ -118,17 +143,28 @@ sub interpolate_class { push @traits, $trait; # are there options? - push @traits, $traits->[$i++] - if $traits->[$i] && ref($traits->[$i]); + if ($traits->[$i] && ref($traits->[$i])) { + $has_foreign_options = 1 + if any { $_ ne '-alias' && $_ ne '-excludes' } keys %{ $traits->[$i] }; + + push @traits, $traits->[$i++]; + } } if (@traits) { - my $anon_class = Moose::Meta::Class->create_anon_class( + my %options = ( superclasses => [ $class ], roles => [ @traits ], - cache => 1, ); + if ($has_foreign_options) { + $options{weaken} = 0; + } + else { + $options{cache} = 1; + } + + my $anon_class = Moose::Meta::Class->create_anon_class(%options); $class = $anon_class->name; } } @@ -524,7 +560,7 @@ sub set_value { my ($self, $instance, @args) = @_; my $value = $args[0]; - my $attr_name = $self->name; + my $attr_name = quotemeta($self->name); if ($self->is_required and not @args) { $self->throw_error("Attribute ($attr_name) is required", object => $instance); @@ -550,12 +586,13 @@ sub set_value { sub _inline_set_value { my $self = shift; - my ($instance, $value, $tc, $tc_obj, $for_constructor) = @_; + my ($instance, $value, $tc, $coercion, $message, $for_constructor) = @_; - my $old = '@old'; - my $copy = '$val'; - $tc ||= '$type_constraint'; - $tc_obj ||= '$type_constraint_obj'; + my $old = '@old'; + my $copy = '$val'; + $tc ||= '$type_constraint'; + $coercion ||= '$type_coercion'; + $message ||= '$type_message'; my @code; if ($self->_writer_value_needs_copy) { @@ -567,7 +604,7 @@ sub _inline_set_value { push @code, $self->_inline_check_required unless $for_constructor; - push @code, $self->_inline_tc_code($value, $tc, $tc_obj); + push @code, $self->_inline_tc_code($value, $tc, $coercion, $message); # constructors do triggers all at once at the end push @code, $self->_inline_get_old_value_for_trigger($instance, $old) @@ -616,39 +653,75 @@ sub _inline_check_required { sub _inline_tc_code { my $self = shift; + my ($value, $tc, $coercion, $message, $is_lazy) = @_; return ( - $self->_inline_check_coercion(@_), - $self->_inline_check_constraint(@_), + $self->_inline_check_coercion( + $value, $tc, $coercion, $is_lazy, + ), + $self->_inline_check_constraint( + $value, $tc, $message, $is_lazy, + ), ); } sub _inline_check_coercion { my $self = shift; - my ($value, $tc, $tc_obj) = @_; + my ($value, $tc, $coercion) = @_; return unless $self->should_coerce && $self->type_constraint->has_coercion; - return $value . ' = ' . $tc_obj . '->coerce(' . $value . ');'; + if ( $self->type_constraint->can_be_inlined ) { + return ( + 'if (! (' . $self->type_constraint->_inline_check($value) . ')) {', + $value . ' = ' . $coercion . '->(' . $value . ');', + '}', + ); + } + else { + return ( + 'if (!' . $tc . '->(' . $value . ')) {', + $value . ' = ' . $coercion . '->(' . $value . ');', + '}', + ); + } } sub _inline_check_constraint { my $self = shift; - my ($value, $tc, $tc_obj) = @_; + my ($value, $tc, $message) = @_; return unless $self->has_type_constraint; my $attr_name = quotemeta($self->name); - return ( - 'if (!' . $tc . '->(' . $value . ')) {', - $self->_inline_throw_error( - '"Attribute (' . $attr_name . ') does not pass the type ' - . 'constraint because: " . ' - . $tc_obj . '->get_message(' . $value . ')', - 'data => ' . $value - ) . ';', - '}', - ); + if ( $self->type_constraint->can_be_inlined ) { + return ( + 'if (! (' . $self->type_constraint->_inline_check($value) . ')) {', + $self->_inline_throw_error( + '"Attribute (' . $attr_name . ') does not pass the type ' + . 'constraint because: " . ' + . 'do { local $_ = ' . $value . '; ' + . $message . '->(' . $value . ')' + . '}', + 'data => ' . $value + ) . ';', + '}', + ); + } + else { + return ( + 'if (!' . $tc . '->(' . $value . ')) {', + $self->_inline_throw_error( + '"Attribute (' . $attr_name . ') does not pass the type ' + . 'constraint because: " . ' + . 'do { local $_ = ' . $value . '; ' + . $message . '->(' . $value . ')' + . '}', + 'data => ' . $value + ) . ';', + '}', + ); + } } sub _inline_get_old_value_for_trigger { @@ -683,7 +756,45 @@ sub _inline_trigger { return unless $self->has_trigger; - return '$attr->trigger->(' . $instance . ', ' . $value . ', ' . $old . ');'; + return '$trigger->(' . $instance . ', ' . $value . ', ' . $old . ');'; +} + +sub _eval_environment { + my $self = shift; + + my $env = { }; + + $env->{'$trigger'} = \($self->trigger) + if $self->has_trigger; + $env->{'$attr_default'} = \($self->default) + if $self->has_default; + + if ($self->has_type_constraint) { + my $tc_obj = $self->type_constraint; + + $env->{'$type_constraint'} = \( + $tc_obj->_compiled_type_constraint + ) unless $tc_obj->can_be_inlined; + # these two could probably get inlined versions too + $env->{'$type_coercion'} = \( + $tc_obj->coercion->_compiled_type_coercion + ) if $tc_obj->has_coercion; + $env->{'$type_message'} = \( + $tc_obj->has_message ? $tc_obj->message : $tc_obj->_default_message + ); + + $env = { %$env, %{ $tc_obj->inline_environment } }; + } + + # XXX ugh, fix these + $env->{'$attr'} = \$self + if $self->has_initializer && $self->is_lazy; + # pretty sure this is only going to be closed over if you use a custom + # error class at this point, but we should still get rid of this + # at some point + $env->{'$meta'} = \($self->associated_class); + + return $env; } sub _weaken_value { @@ -740,21 +851,22 @@ sub get_value { sub _inline_get_value { my $self = shift; - my ($instance, $tc, $tc_obj) = @_; + my ($instance, $tc, $coercion, $message) = @_; my $slot_access = $self->_inline_instance_get($instance); $tc ||= '$type_constraint'; - $tc_obj ||= '$type_constraint_obj'; + $coercion ||= '$type_coercion'; + $message ||= '$type_message'; return ( - $self->_inline_check_lazy($instance, $tc, $tc_obj), + $self->_inline_check_lazy($instance, $tc, $coercion, $message), $self->_inline_return_auto_deref($slot_access), ); } sub _inline_check_lazy { my $self = shift; - my ($instance, $tc, $tc_obj) = @_; + my ($instance, $tc, $coercion, $message) = @_; return unless $self->is_lazy; @@ -762,14 +874,14 @@ sub _inline_check_lazy { return ( 'if (!' . $slot_exists . ') {', - $self->_inline_init_from_default($instance, '$default', $tc, $tc_obj, 'lazy'), + $self->_inline_init_from_default($instance, '$default', $tc, $coercion, $message, 'lazy'), '}', ); } sub _inline_init_from_default { my $self = shift; - my ($instance, $default, $tc, $tc_obj, $for_lazy) = @_; + my ($instance, $default, $tc, $coercion, $message, $for_lazy) = @_; if (!($self->has_default || $self->has_builder)) { $self->throw_error( @@ -786,8 +898,8 @@ sub _inline_init_from_default { # to do things like possibly only do member tc checks, which isn't # appropriate for checking the result of a default $self->has_type_constraint - ? ($self->_inline_check_coercion($default, $tc, $tc_obj, $for_lazy), - $self->_inline_check_constraint($default, $tc, $tc_obj, $for_lazy)) + ? ($self->_inline_check_coercion($default, $tc, $coercion, $for_lazy), + $self->_inline_check_constraint($default, $tc, $message, $for_lazy)) : (), $self->_inline_init_slot($instance, $default), ); @@ -798,21 +910,26 @@ sub _inline_generate_default { my ($instance, $default) = @_; if ($self->has_default) { - return 'my ' . $default . ' = $attr->default(' . $instance . ');'; + my $source = 'my ' . $default . ' = $attr_default'; + $source .= '->(' . $instance . ')' + if $self->is_default_a_coderef; + return $source . ';'; } elsif ($self->has_builder) { + my $builder = B::perlstring($self->builder); + my $builder_str = quotemeta($self->builder); + my $attr_name_str = quotemeta($self->name); return ( 'my ' . $default . ';', - 'if (my $builder = ' . $instance . '->can($attr->builder)) {', + 'if (my $builder = ' . $instance . '->can(' . $builder . ')) {', $default . ' = ' . $instance . '->$builder;', '}', 'else {', 'my $class = ref(' . $instance . ') || ' . $instance . ';', - 'my $builder_name = $attr->builder;', - 'my $attr_name = $attr->name;', $self->_inline_throw_error( '"$class does not support builder method ' - . '\'$builder_name\' for attribute \'$attr_name\'"' + . '\'' . $builder_str . '\' for attribute ' + . '\'' . $attr_name_str . '\'"' ) . ';', '}', ); @@ -961,7 +1078,7 @@ sub install_delegation { # install the delegation ... my $associated_class = $self->associated_class; - foreach my $handle (keys %handles) { + foreach my $handle (sort keys %handles) { my $method_to_call = $handles{$handle}; my $class_name = $associated_class->name; my $name = "${class_name}::${handle}"; @@ -1030,7 +1147,7 @@ sub _canonicalize_handles { } } - Class::MOP::load_class($handles); + load_class($handles); my $role_meta = Class::MOP::class_of($handles); (blessed $role_meta && $role_meta->isa('Moose::Meta::Role')) @@ -1063,7 +1180,7 @@ sub _get_delegate_method_list { sub _find_delegate_metaclass { my $self = shift; if (my $class = $self->_isa_metadata) { - unless ( Class::MOP::is_class_loaded($class) ) { + unless ( is_class_loaded($class) ) { $self->throw_error( sprintf( 'The %s attribute is trying to delegate to a class which has not been loaded - %s', @@ -1077,7 +1194,7 @@ sub _find_delegate_metaclass { return Class::MOP::Class->initialize($class); } elsif (my $role = $self->_does_metadata) { - unless ( Class::MOP::is_class_loaded($class) ) { + unless ( is_class_loaded($class) ) { $self->throw_error( sprintf( 'The %s attribute is trying to delegate to a role which has not been loaded - %s',