Moose::Exception::TypeConstraint is no longer a role
[gitmo/Moose.git] / lib / Moose / Meta / Attribute.pm
index ed87709..f0156fd 100644 (file)
@@ -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,31 @@ 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";
-    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
-    goto $handler;
+    Moose::Util::throw(@_);
 }
 
 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');
+
+    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 +114,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 +128,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;
         }
     }
@@ -181,7 +202,7 @@ sub clone_and_inherit_options {
             $type_constraint = $options{isa};
         }
         else {
-            $type_constraint = Moose::Util::TypeConstraints::find_or_create_isa_type_constraint($options{isa});
+            $type_constraint = Moose::Util::TypeConstraints::find_or_create_isa_type_constraint($options{isa}, { package_defined_in => $options{definition_context}->{package} });
             (defined $type_constraint)
                 || $self->throw_error("Could not find the type constraint '" . $options{isa} . "'", data => $options{isa});
         }
@@ -195,7 +216,7 @@ sub clone_and_inherit_options {
             $type_constraint = $options{does};
         }
         else {
-            $type_constraint = Moose::Util::TypeConstraints::find_or_create_does_type_constraint($options{does});
+            $type_constraint = Moose::Util::TypeConstraints::find_or_create_does_type_constraint($options{does}, { package_defined_in => $options{definition_context}->{package} });
             (defined $type_constraint)
                 || $self->throw_error("Could not find the type constraint '" . $options{does} . "'", data => $options{does});
         }
@@ -326,7 +347,9 @@ sub _process_isa_option {
     else {
         $options->{type_constraint}
             = Moose::Util::TypeConstraints::find_or_create_isa_type_constraint(
-            $options->{isa} );
+            $options->{isa},
+            { package_defined_in => $options->{definition_context}->{package} }
+        );
     }
 }
 
@@ -343,7 +366,9 @@ sub _process_does_option {
     else {
         $options->{type_constraint}
             = Moose::Util::TypeConstraints::find_or_create_does_type_constraint(
-            $options->{does} );
+            $options->{does},
+            { package_defined_in => $options->{definition_context}->{package} }
+        );
     }
 }
 
@@ -524,7 +549,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 +575,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 +593,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 +642,81 @@ 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 . ')'
+                  . '}',
+                    'class => "Moose::Exception::TypeConstraint"',
+                    'attribute_name => ' . $self->name,
+                    'type_name => ' . $self->type_constraint->name,
+                    'value => ' . $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 . ')'
+                  . '}',
+                    'class => "Moose::Exception::TypeConstraint"',
+                    'attribute_name => ' . $self->name,
+                    'type_name => ' . $self->type_constraint->name,
+                    'value => ' . $value,
+                ) . ';',
+            '}',
+        );
+    }
 }
 
 sub _inline_get_old_value_for_trigger {
@@ -672,7 +740,7 @@ sub _inline_weaken_value {
 
     my $mi = $self->associated_class->get_meta_instance;
     return (
-        $mi->inline_weaken_slot_value($instance, $self->name, $value),
+        $mi->inline_weaken_slot_value($instance, $self->name),
             'if ref ' . $value . ';',
     );
 }
@@ -683,7 +751,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 {
@@ -710,6 +816,10 @@ sub get_value {
             $value = $self->_coerce_and_verify( $value, $instance );
 
             $self->set_initial_value($instance, $value);
+
+            if ( ref $value && $self->is_weak_ref ) {
+                $self->_weaken_value($instance);
+            }
         }
     }
 
@@ -740,21 +850,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 +873,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,10 +897,11 @@ 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),
+        $self->_inline_weaken_value($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 . '\'"'
                 ) . ';',
             '}',
         );
@@ -920,6 +1037,7 @@ sub _process_accessors {
 
     if (
            $method
+        && !$method->is_stub
         && !$method->isa('Class::MOP::Method::Accessor')
         && (  !$self->definition_context
             || $method->package_name eq $self->definition_context->{package} )
@@ -961,13 +1079,17 @@ 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}";
 
-            (!$associated_class->has_method($handle))
-                || $self->throw_error("You cannot overwrite a locally defined method ($handle) with a delegation", method_name => $handle);
+        if ( my $method = $associated_class->get_method($handle) ) {
+            $self->throw_error(
+                "You cannot overwrite a locally defined method ($handle) with a delegation",
+                method_name => $handle
+            ) unless $method->is_stub;
+        }
 
         # NOTE:
         # handles is not allowed to delegate
@@ -1030,7 +1152,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 +1185,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 +1199,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',
@@ -1136,10 +1258,16 @@ sub verify_against_type_constraint {
     my $type_constraint = $self->type_constraint;
 
     $type_constraint->check($val)
-        || $self->throw_error("Attribute ("
+        || $self->throw_error(
+                superclass => 'Moose::Exception::TypeConstraint',
+                message => "Attribute ("
                  . $self->name
                  . ") does not pass the type constraint because: "
-                 . $type_constraint->get_message($val), data => $val, @_);
+                 . $type_constraint->get_message($val),
+                 value => $val,
+                 attribute_name => $self->name,
+                 type_name => $type_constraint->name,
+                 @_);
 }
 
 package Moose::Meta::Attribute::Custom::Moose;
@@ -1344,14 +1472,14 @@ I<Attribute (x) does not pass the type constraint (Int) with 'forty-two'>
 
 Before setting the value, a check is made on the type constraint of
 the attribute, if it has one, to see if the value passes it. If the
-value fails to pass, the set operation dies with a L</throw_error>.
+value fails to pass, the set operation dies.
 
 Any coercion to convert values is done before checking the type constraint.
 
 To check a value against a type constraint before setting it, fetch the
 attribute instance using L<Class::MOP::Class/find_attribute_by_name>,
 fetch the type_constraint from the attribute using L<Moose::Meta::Attribute/type_constraint>
-and call L<Moose::Meta::TypeConstraint/check>. See L<Moose::Cookbook::Basics::Recipe4>
+and call L<Moose::Meta::TypeConstraint/check>. See L<Moose::Cookbook::Basics::Company_Subtypes>
 for an example.
 
 =back