Make strict_constructor public
gfx [Tue, 6 Jul 2010 11:15:05 +0000 (20:15 +0900)]
lib/Mouse/Meta/Class.pm
lib/Mouse/Meta/Method/Constructor.pm
lib/Mouse/PurePerl.pm
mouse.h
t/001_mouse/068-strict-constructor.t
xs-src/Mouse.xs

index b1cffd8..f2adb02 100644 (file)
@@ -258,8 +258,6 @@ sub make_immutable {
 
     $self->{is_immutable}++;
 
-    $self->{strict_constructor} = $args{strict_constructor};
-
     if ($args{inline_constructor}) {
         $self->add_method($args{constructor_name} =>
             Mouse::Util::load_class($self->constructor_class)
index 7435aea..3c68a80 100644 (file)
@@ -51,7 +51,7 @@ sub _generate_processattrs {
     my @res;
 
     my $has_triggers;
-    my $strict = $metaclass->__strict_constructor;
+    my $strict = $metaclass->strict_constructor;
 
     if($strict){
         push @res, 'my $used = 0;';
@@ -152,7 +152,7 @@ sub _generate_processattrs {
 
     if($strict){
         push @res, q{if($used < keys %{$args})}
-            . sprintf q{{ %s->_report_unknown_args($metaclass, \@attrs, $args) }}, $method_class;
+            . q{{ $metaclass->_report_unknown_args(\@attrs, $args) }};
     }
 
     if($metaclass->is_anon_class){
@@ -202,30 +202,6 @@ sub _generate_BUILDALL {
     return join "\n", @code;
 }
 
-sub _report_unknown_args {
-    my(undef, $metaclass, $attrs, $args) = @_;
-
-    my @unknowns;
-    my %init_args;
-    foreach my $attr(@{$attrs}){
-        my $init_arg = $attr->init_arg;
-        if(defined $init_arg){
-            $init_args{$init_arg}++;
-        }
-    }
-
-    while(my $key = each %{$args}){
-        if(!exists $init_args{$key}){
-            push @unknowns, $key;
-        }
-    }
-
-    $metaclass->throw_error( sprintf
-        "Unknown attribute passed to the constructor of %s: %s",
-        $metaclass->name, Mouse::Util::english_list(@unknowns),
-    );
-}
-
 1;
 __END__
 
index 8285fa7..4db48db 100644 (file)
@@ -307,6 +307,8 @@ sub _initialize_object{
 
     my @triggers_queue;
 
+    my $used = 0;
+
     foreach my $attribute ($self->get_all_attributes) {
         my $init_arg = $attribute->init_arg;
         my $slot     = $attribute->name;
@@ -320,6 +322,7 @@ sub _initialize_object{
             if ($attribute->has_trigger) {
                 push @triggers_queue, [ $attribute->trigger, $object->{$slot} ];
             }
+            $used++;
         }
         else { # no init arg
             if ($attribute->has_default || $attribute->has_builder) {
@@ -342,6 +345,10 @@ sub _initialize_object{
         }
     }
 
+    if($used < keys %{$args} && $self->strict_constructor) {
+        $self->_report_unknown_args([ $self->get_all_attributes ], $args);
+    }
+
     if(@triggers_queue){
         foreach my $trigger_and_value(@triggers_queue){
             my($trigger, $value) = @{$trigger_and_value};
@@ -358,7 +365,47 @@ sub _initialize_object{
 
 sub is_immutable {  $_[0]->{is_immutable} }
 
-sub __strict_constructor{ $_[0]->{strict_constructor} }
+sub strict_constructor{
+    my $self = shift;
+    if(@_) {
+        $self->{strict_constructor} = shift;
+    }
+
+    foreach my $class($self->linearized_isa) {
+        my $meta = Mouse::Util::get_metaclass_by_name($class)
+            or next;
+
+        if(exists $meta->{strict_constructor}) {
+            return $meta->{strict_constructor};
+        }
+    }
+
+    return 0; # false
+}
+
+sub _report_unknown_args {
+    my($metaclass, $attrs, $args) = @_;
+
+    my @unknowns;
+    my %init_args;
+    foreach my $attr(@{$attrs}){
+        my $init_arg = $attr->init_arg;
+        if(defined $init_arg){
+            $init_args{$init_arg}++;
+        }
+    }
+
+    while(my $key = each %{$args}){
+        if(!exists $init_args{$key}){
+            push @unknowns, $key;
+        }
+    }
+
+    $metaclass->throw_error( sprintf
+        "Unknown attribute passed to the constructor of %s: %s",
+        $metaclass->name, Mouse::Util::english_list(@unknowns),
+    );
+}
 
 package Mouse::Meta::Role;
 
diff --git a/mouse.h b/mouse.h
index 8764f8a..8e95932 100644 (file)
--- a/mouse.h
+++ b/mouse.h
@@ -29,6 +29,7 @@
 #define no_mro_get_linear_isa
 #define mro_get_linear_isa(stash) mouse_mro_get_linear_isa(aTHX_ stash)
 AV* mouse_mro_get_linear_isa(pTHX_ HV* const stash);
+#define mro_method_changed_in(stash) ((void)((stash), ++PL_sub_generation))
 #endif /* !mro_get_linear_isa */
 
 #ifndef mro_get_pkg_gen
index cd38d21..3a325a8 100644 (file)
@@ -5,6 +5,7 @@ use warnings;
 use if 'Mouse' eq 'Moose',
     'Test::More' => skip_all => 'Moose does nots support strict constructor';
 use Test::More;
+use Test::Mouse;
 use Test::Exception;
 
 {
@@ -25,41 +26,50 @@ use Test::Exception;
         default => 42,
     );
 
-    __PACKAGE__->meta->make_immutable(strict_constructor => 1);
+    __PACKAGE__->meta->strict_constructor(1);
+}
+{
+    package MySubClass;
+    use Mouse;
+    extends 'MyClass';
 }
 
-lives_and {
-    my $o = MyClass->new(foo => 1);
-    isa_ok($o, 'MyClass');
-    is $o->baz, 42;
-} 'correc use of the constructor';
-
-lives_and {
-    my $o = MyClass->new(foo => 1, baz => 10);
-    isa_ok($o, 'MyClass');
-    is $o->baz, 10;
-} 'correc use of the constructor';
-
-
-throws_ok {
-    MyClass->new(foo => 1, hoge => 42);
-} qr/\b hoge \b/xms;
-
-throws_ok {
-    MyClass->new(foo => 1, bar => 42);
-} qr/\b bar \b/xms, "init_arg => undef";
-
-
-throws_ok {
-    MyClass->new(aaa => 1, bbb => 2, ccc => 3);
-} qr/\b aaa \b/xms, $@;
-
-throws_ok {
-    MyClass->new(aaa => 1, bbb => 2, ccc => 3);
-} qr/\b bbb \b/xms, $@;
-
-throws_ok {
-    MyClass->new(aaa => 1, bbb => 2, ccc => 3);
-} qr/\b ccc \b/xms, $@;
+with_immutable sub {
+    lives_and {
+        my $o = MyClass->new(foo => 1);
+        isa_ok($o, 'MyClass');
+        is $o->baz, 42;
+    } 'correc use of the constructor';
+
+    lives_and {
+        my $o = MyClass->new(foo => 1, baz => 10);
+        isa_ok($o, 'MyClass');
+        is $o->baz, 10;
+    } 'correc use of the constructor';
+
+
+    throws_ok {
+        MyClass->new(foo => 1, hoge => 42);
+    } qr/\b hoge \b/xms;
+
+    throws_ok {
+        MyClass->new(foo => 1, bar => 42);
+    } qr/\b bar \b/xms, "init_arg => undef";
+
+
+    eval {
+        MyClass->new(aaa => 1, bbb => 2, ccc => 3);
+    };
+    like $@, qr/\b aaa \b/xms;
+    like $@, qr/\b bbb \b/xms;
+    like $@, qr/\b ccc \b/xms;
+
+    eval {
+        MySubClass->new(aaa => 1, bbb => 2, ccc => 3);
+    };
+    like $@, qr/\b aaa \b/xms;
+    like $@, qr/\b bbb \b/xms;
+    like $@, qr/\b ccc \b/xms;
+}, qw(MyClass MySubClass);
 
 done_testing;
index 250604d..ecc6e8c 100644 (file)
@@ -123,7 +123,7 @@ mouse_class_update_xc(pTHX_ SV* const metaclass PERL_UNUSED_DECL, HV* const stas
         flags |= MOUSEf_XC_HAS_BUILDARGS;
     }
 
-    if(predicate_calls(metaclass, "__strict_constructor")){
+    if(predicate_calls(metaclass, "strict_constructor")){
         flags |= MOUSEf_XC_CONSTRUCTOR_IS_STRICT;
     }
 
@@ -297,7 +297,7 @@ mouse_class_initialize_object(pTHX_ SV* const meta, SV* const object, HV* const
     I32 const len   = AvFILLp(attrs) + 1;
     I32 i;
     AV* triggers_queue = NULL;
-    I32 used = 0;
+    U32 used = 0;
 
     assert(meta || object);
     assert(args);
@@ -525,8 +525,6 @@ BOOT:
     INSTALL_SIMPLE_PREDICATE_WITH_KEY(Class, is_anon_class, anon_serial_id);
     INSTALL_SIMPLE_READER(Class, is_immutable);
 
-    INSTALL_SIMPLE_READER_WITH_KEY(Class, __strict_constructor,     strict_constructor);
-
     INSTALL_CLASS_HOLDER(Class, method_metaclass,     "Mouse::Meta::Method");
     INSTALL_CLASS_HOLDER(Class, attribute_metaclass,  "Mouse::Meta::Attribute");
     INSTALL_CLASS_HOLDER(Class, constructor_class,    "Mouse::Meta::Method::Constructor::XS");
@@ -613,6 +611,45 @@ CODE:
     mouse_class_initialize_object(aTHX_ meta, object, args, is_cloning);
 }
 
+void
+strict_constructor(SV* self, SV* value = NULL)
+CODE:
+{
+    SV* const slot      = sv_2mortal(newSVpvs_share("strict_constructor"));
+    SV* const stash_ref = mcall0(self, mouse_namespace);
+    HV* stash;
+
+    if(!(SvROK(stash_ref) && SvTYPE(SvRV(stash_ref)) == SVt_PVHV)) {
+        croak("namespace() didn't return a HASH reference");
+    }
+    stash = (HV*)SvRV(stash_ref);
+
+    if(value) { /* setter */
+        set_slot(self, slot, value);
+        mro_method_changed_in(stash);
+    }
+
+    value = get_slot(self, slot);
+
+    if(!value) {
+        AV* const isa   = mro_get_linear_isa(stash);
+        I32 const len   = av_len(isa) + 1;
+        I32 i;
+        for(i = 1; i < len; i++) {
+            SV* const klass = MOUSE_av_at(isa, i);
+            SV* const meta  = get_metaclass(klass);
+            if(!SvOK(meta)){
+                continue; /* skip non-Mouse classes */
+            }
+            value = get_slot(meta, slot);
+            if(value) {
+                break;
+            }
+        }
+    }
+    ST(0) = value ? value : &PL_sv_no;
+}
+
 MODULE = Mouse  PACKAGE = Mouse::Meta::Role
 
 BOOT: