builder inline accessor bug fix and new test
[gitmo/Moose.git] / lib / Moose / Meta / Method / Accessor.pm
index 413b5b9..6b387da 100644 (file)
@@ -6,7 +6,8 @@ use warnings;
 
 use Carp 'confess';
 
-our $VERSION = '0.03';
+our $VERSION   = '0.06';
+our $AUTHORITY = 'cpan:STEVAN';
 
 use base 'Moose::Meta::Method',
          'Class::MOP::Method::Accessor';
@@ -14,184 +15,240 @@ use base 'Moose::Meta::Method',
 ## Inline method generators
 
 sub generate_accessor_method_inline {
-    my $self      = shift;
-    my $attr      = $self->associated_attribute; 
-    my $attr_name = $attr->name;
-
-    my $value_name = $attr->should_coerce ? '$val' : '$_[1]';
-       my $mi = $attr->associated_class->get_meta_instance;
-       my $slot_name = sprintf "'%s'", $attr->slots;
-       my $inv = '$_[0]';
-    my $code = 'sub { '
-    . 'if (scalar(@_) == 2) {'
-        . $self->_inline_check_required
-        . $self->_inline_check_coercion
-        . $self->_inline_check_constraint($value_name)
-               . $self->_inline_store($inv, $value_name)
-               . $self->_inline_trigger($inv, $value_name)
-    . ' }'
-    . $self->_inline_check_lazy
-    . 'return ' . $self->_inline_auto_deref($self->_inline_get($inv))
+    my $self        = $_[0];
+    my $attr        = $self->associated_attribute;
+    my $attr_name   = $attr->name;
+    my $inv         = '$_[0]';
+    my $slot_access = $self->_inline_access($inv, $attr_name);
+    my $value_name  = $attr->should_coerce ? '$val' : '$_[1]';
+
+    my $code = 'sub { ' . "\n"
+    . $self->_inline_pre_body(@_) . "\n"
+    . 'if (scalar(@_) == 2) {' . "\n"
+        . $self->_inline_check_required . "\n"
+        . $self->_inline_check_coercion . "\n"
+        . $self->_inline_check_constraint($value_name) . "\n"
+                . $self->_inline_store($inv, $value_name) . "\n"
+                . $self->_inline_trigger($inv, $value_name) . "\n"
+    . ' }' . "\n"
+    . $self->_inline_check_lazy . "\n"
+    . $self->_inline_post_body(@_) . "\n"
+    . 'return ' . $self->_inline_auto_deref($self->_inline_get($inv)) . "\n"
     . ' }';
-    
+
     # NOTE:
     # set up the environment
-    my $type_constraint = $attr->type_constraint 
+    my $type_constraint = $attr->type_constraint
                                 ? $attr->type_constraint->_compiled_type_constraint
                                 : undef;
-    
+
     my $sub = eval $code;
     confess "Could not create accessor for '$attr_name' because $@ \n code: $code" if $@;
-    return $sub;    
+    return $sub;
 }
 
 sub generate_writer_method_inline {
-    my $self      = shift;
-    my $attr      = $self->associated_attribute; 
-    my $attr_name = $attr->name;
-    
-    my $value_name = $attr->should_coerce ? '$val' : '$_[1]';
-       my $inv = '$_[0]';
+    my $self        = $_[0];
+    my $attr        = $self->associated_attribute;
+    my $attr_name   = $attr->name;
+    my $inv         = '$_[0]';
+    my $slot_access = $self->_inline_get($inv, $attr_name);
+    my $value_name  = $attr->should_coerce ? '$val' : '$_[1]';
+
     my $code = 'sub { '
+    . $self->_inline_pre_body(@_)
     . $self->_inline_check_required
     . $self->_inline_check_coercion
-       . $self->_inline_check_constraint($value_name)
-       . $self->_inline_store($inv, $value_name)
-       . $self->_inline_trigger($inv, $value_name)
+        . $self->_inline_check_constraint($value_name)
+        . $self->_inline_store($inv, $value_name)
+        . $self->_inline_post_body(@_)
+        . $self->_inline_trigger($inv, $value_name)
     . ' }';
-    
+
     # NOTE:
     # set up the environment
-    my $type_constraint = $attr->type_constraint 
+    my $type_constraint = $attr->type_constraint
                                 ? $attr->type_constraint->_compiled_type_constraint
-                                : undef;    
-    
+                                : undef;
+
     my $sub = eval $code;
     confess "Could not create writer for '$attr_name' because $@ \n code: $code" if $@;
-    return $sub;    
+    return $sub;
 }
 
 sub generate_reader_method_inline {
-    my $self      = shift;
-    my $attr      = $self->associated_attribute; 
-    my $attr_name = $attr->name;
-    
+    my $self        = $_[0];
+    my $attr        = $self->associated_attribute;
+    my $attr_name   = $attr->name;
+    my $inv         = '$_[0]';
+    my $slot_access = $self->_inline_get($inv, $attr_name);
+
     my $code = 'sub {'
+    . $self->_inline_pre_body(@_)
     . 'confess "Cannot assign a value to a read-only accessor" if @_ > 1;'
     . $self->_inline_check_lazy
-    . 'return ' . $self->_inline_auto_deref( '$_[0]->{$attr_name}' ) . ';'
+    . $self->_inline_post_body(@_)
+    . 'return ' . $self->_inline_auto_deref( $slot_access ) . ';'
     . '}';
-    
+
     # NOTE:
     # set up the environment
-    my $type_constraint = $attr->type_constraint 
+    my $type_constraint = $attr->type_constraint
                                 ? $attr->type_constraint->_compiled_type_constraint
-                                : undef;    
-    
+                                : undef;
+
     my $sub = eval $code;
     confess "Could not create reader for '$attr_name' because $@ \n code: $code" if $@;
     return $sub;
 }
 
-## normal method generators 
+sub generate_reader_method { shift->generate_reader_method_inline(@_) }
+sub generate_writer_method { shift->generate_writer_method_inline(@_) }
+sub generate_accessor_method { shift->generate_accessor_method_inline(@_) }
 
-*generate_reader_method   = \&generate_reader_method_inline;
-*generate_writer_method   = \&generate_writer_method_inline;
-*generate_accessor_method = \&generate_accessor_method_inline;
-
-## ... private helpers
+sub _inline_pre_body  { '' }
+sub _inline_post_body { '' }
 
 sub _inline_check_constraint {
-       my ($self, $value) = @_;
-       
-       my $attr = $self->associated_attribute; 
-       
-       return '' unless $attr->has_type_constraint;
-       
-       # FIXME - remove 'unless defined($value) - constraint Undef
-       return sprintf <<'EOF', $value, $value, $value, $value
+        my ($self, $value) = @_;
+
+        my $attr = $self->associated_attribute;
+
+        return '' unless $attr->has_type_constraint;
+
+        # FIXME
+        # This sprintf is insanely annoying, we should
+        # fix it someday - SL
+        return sprintf <<'EOF', $value, $value, $value, $value, $value, $value, $value
 defined($type_constraint->(%s))
-       || confess "Attribute (" . $attr->name . ") does not pass the type constraint ("
-       . $attr->type_constraint->name . ") with " . (defined(%s) ? "'%s'" : "undef")
+        || confess "Attribute (" . $attr->name . ") does not pass the type constraint ("
+       . $attr->type_constraint->name . ") with "
+       . (defined(%s) ? (Scalar::Util::blessed(%s) && overload::Overloaded(%s) ? overload::StrVal(%s) : %s) : "undef")
   if defined(%s);
 EOF
 }
 
 sub _inline_check_coercion {
-       my $attr = (shift)->associated_attribute; 
-       
-       return '' unless $attr->should_coerce;
+        my $attr = (shift)->associated_attribute;
+
+        return '' unless $attr->should_coerce;
     return 'my $val = $attr->type_constraint->coerce($_[1]);'
 }
 
 sub _inline_check_required {
-       my $attr = (shift)->associated_attribute; 
-       
-       return '' unless $attr->is_required;
+        my $attr = (shift)->associated_attribute;
+
+        return '' unless $attr->is_required;
     return 'defined($_[1]) || confess "Attribute ($attr_name) is required, so cannot be set to undef";'
 }
 
 sub _inline_check_lazy {
-       my $attr = (shift)->associated_attribute; 
-       
-       return '' unless $attr->is_lazy;
-       
-       if ($attr->has_type_constraint) {
-           # NOTE:
-           # this could probably be cleaned 
-           # up and streamlined a little more
-           return 'unless (exists $_[0]->{$attr_name}) {' .
-                  '    if ($attr->has_default) {' .
-                  '        my $default = $attr->default($_[0]);' .
+    my $self = $_[0];
+    my $attr = $self->associated_attribute;
+
+        return '' unless $attr->is_lazy;
+
+    my $inv         = '$_[0]';
+    my $slot_access = $self->_inline_access($inv, $attr->name);
+    my $slot_exists = $self->_inline_has($inv, $attr->name);
+        if ($attr->has_type_constraint) {
+            # NOTE:
+            # this could probably be cleaned
+            # up and streamlined a little more
+            return 'unless (' . $slot_exists . ') {' .
+                   '    if ($attr->has_default || $attr->has_builder ) {' .
+                   '        my $default; '.
+                   '        $default = $attr->default(' . $inv . ')  if $attr->has_default;' .
+                   '        if ( $attr->has_builder ) { '.
+                   '            my $builder = $attr->builder;'.
+                   '            if($builder = '.$inv.'->can($builder)){ '.
+                   '                $default = '.$inv.'->$builder; '.
+                   '            } else { '.
+                   '                confess(blessed('.$inv.')." does not support builder method \'$builder\' for attribute \'" . $attr->name . "\'");'.
+                   '            }'.
+                   '        }'.
+                   ($attr->should_coerce
+                       ? '$default = $attr->type_constraint->coerce($default);'
+                       : '') .
                '        (defined($type_constraint->($default)))' .
-               '               || confess "Attribute (" . $attr->name . ") does not pass the type constraint ("' .
-               '               . $attr->type_constraint->name . ") with " . (defined($default) ? "\'$default\'" : "undef")' .
-               '          if defined($default);' .                     
-                  '        $_[0]->{$attr_name} = $default; ' .
-                  '    }' .
-                  '    else {' .
-               '        $_[0]->{$attr_name} = undef;' .
-                  '    }' .
-                  '}';     
-       }
-    return '$_[0]->{$attr_name} = ($attr->has_default ? $attr->default($_[0]) : undef)'
-         . 'unless exists $_[0]->{$attr_name};';
+               '                || confess "Attribute (" . $attr->name . ") does not pass the type constraint ("' .
+               '               . $attr->type_constraint->name . ") with " . (defined($default) ? (Scalar::Util::blessed($default) && overload::Overloaded($default) ? overload::StrVal($default) : $default) : "undef")' .
+               '          if defined($default);' .
+                   '        ' . $slot_access . ' = $default; ' .
+                   '    }' .
+                   '    else {' .
+               '        ' . $slot_access . ' = undef;' .
+                   '    }' .
+                   '}';
+        }
+
+    return  'unless ( ' . $slot_exists . ') {' .
+            '    if ($attr->has_default) { ' . $slot_access . ' = $attr->default(' . $inv . '); }' .
+            '    elsif ($attr->has_builder) { '.
+            '        my $builder = $attr->builder; ' .
+            '        if($builder = '.$inv.'->can($builder)){ '.
+            '            ' . $slot_access . ' = ' . $inv . '->$builder; '.
+            '        } else { '.
+            '            confess(blessed('.$inv.')." does not support builder method \'$builder\' for attribute \'" . $attr->name . "\'");'.
+            '        }'.
+            '    } else { ' .$slot_access . ' = undef; } '.
+            '}';
 }
 
 
 sub _inline_store {
-       my ($self, $instance, $value) = @_;
-       my $attr = $self->associated_attribute;         
+        my ($self, $instance, $value) = @_;
+        my $attr = $self->associated_attribute;
 
-       my $mi = $attr->associated_class->get_meta_instance;
-       my $slot_name = sprintf "'%s'", $attr->slots;
+        my $mi = $attr->associated_class->get_meta_instance;
+        my $slot_name = sprintf "'%s'", $attr->slots;
 
     my $code = $mi->inline_set_slot_value($instance, $slot_name, $value)    . ";";
-       $code   .= $mi->inline_weaken_slot_value($instance, $slot_name, $value) . ";"
-           if $attr->is_weak_ref;
+        $code   .= $mi->inline_weaken_slot_value($instance, $slot_name, $value) . ";"
+            if $attr->is_weak_ref;
     return $code;
 }
 
 sub _inline_trigger {
-       my ($self, $instance, $value) = @_;
-       my $attr = $self->associated_attribute;         
-       return '' unless $attr->has_trigger;
-       return sprintf('$attr->trigger->(%s, %s, $attr);', $instance, $value);
+        my ($self, $instance, $value) = @_;
+        my $attr = $self->associated_attribute;
+        return '' unless $attr->has_trigger;
+        return sprintf('$attr->trigger->(%s, %s, $attr);', $instance, $value);
 }
 
 sub _inline_get {
-       my ($self, $instance) = @_;
-       my $attr = $self->associated_attribute;         
+        my ($self, $instance) = @_;
+        my $attr = $self->associated_attribute;
 
-       my $mi = $attr->associated_class->get_meta_instance;
-       my $slot_name = sprintf "'%s'", $attr->slots;
+        my $mi = $attr->associated_class->get_meta_instance;
+        my $slot_name = sprintf "'%s'", $attr->slots;
 
     return $mi->inline_get_slot_value($instance, $slot_name);
 }
 
+sub _inline_access {
+        my ($self, $instance) = @_;
+        my $attr = $self->associated_attribute;
+
+        my $mi = $attr->associated_class->get_meta_instance;
+        my $slot_name = sprintf "'%s'", $attr->slots;
+
+    return $mi->inline_slot_access($instance, $slot_name);
+}
+
+sub _inline_has {
+        my ($self, $instance) = @_;
+        my $attr = $self->associated_attribute;
+
+        my $mi = $attr->associated_class->get_meta_instance;
+        my $slot_name = sprintf "'%s'", $attr->slots;
+
+    return $mi->inline_is_slot_initialized($instance, $slot_name);
+}
+
 sub _inline_auto_deref {
     my ( $self, $ref_value ) = @_;
-       my $attr = $self->associated_attribute;     
+        my $attr = $self->associated_attribute;
 
     return $ref_value unless $attr->should_auto_deref;
 
@@ -200,10 +257,10 @@ sub _inline_auto_deref {
     my $sigil;
     if ($type_constraint->is_a_type_of('ArrayRef')) {
         $sigil = '@';
-    } 
+    }
     elsif ($type_constraint->is_a_type_of('HashRef')) {
         $sigil = '%';
-    } 
+    }
     else {
         confess "Can not auto de-reference the type constraint '" . $type_constraint->name . "'";
     }
@@ -223,10 +280,10 @@ Moose::Meta::Method::Accessor - A Moose Method metaclass for accessors
 
 =head1 DESCRIPTION
 
-This is a subclass of L<Class::MOP::Method::Accessor> and it's primary 
-responsibility is to generate the accessor methods for attributes. It 
+This is a subclass of L<Class::MOP::Method::Accessor> and it's primary
+responsibility is to generate the accessor methods for attributes. It
 can handle both closure based accessors, as well as inlined source based
-accessors. 
+accessors.
 
 This is a fairly new addition to the MOP, but this will play an important
 role in the optimization strategy we are currently following.
@@ -251,7 +308,7 @@ role in the optimization strategy we are currently following.
 
 =head1 BUGS
 
-All complex software has bugs lurking in it, and this module is no 
+All complex software has bugs lurking in it, and this module is no
 exception. If you find a bug please either email me, or add the bug
 to cpan-RT.
 
@@ -263,11 +320,11 @@ Yuval Kogman E<lt>nothingmuch@woobling.comE<gt>
 
 =head1 COPYRIGHT AND LICENSE
 
-Copyright 2006 by Infinity Interactive, Inc.
+Copyright 2006, 2007 by Infinity Interactive, Inc.
 
 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