Implement strict constructors, which will warn unkown constructor arguments
gfx [Sat, 20 Feb 2010 05:30:24 +0000 (14:30 +0900)]
lib/Mouse/Meta/Class.pm
lib/Mouse/Meta/Method/Constructor.pm
lib/Mouse/PurePerl.pm
xs-src/Mouse.xs

index 9f7bc68..7b03c6f 100644 (file)
@@ -244,6 +244,8 @@ sub make_immutable {
 
     $self->{is_immutable}++;
 
+    $self->{strict_constructor} = $args{strict_constructor};
+
     if ($args{inline_constructor}) {
         my $c = $self->constructor_class;
         Mouse::Util::load_class($c);
index 6064aff..0c929a7 100644 (file)
@@ -51,6 +51,12 @@ sub _generate_processattrs {
     my @res;
 
     my $has_triggers;
+    my $strict_constructor = $metaclass->__strict_constructor;
+
+
+    if($strict_constructor){
+        push @res, 'my $used = 0;';
+    }
 
     for my $index (0 .. @$attrs - 1) {
         my $code = '';
@@ -100,7 +106,11 @@ sub _generate_processattrs {
                 $code .= "push \@triggers, [$attr_var\->{trigger}, $instance_slot];\n";
             }
 
-            $code .= "\n} else {\n";
+            if ($strict_constructor){
+                $code .= '$used++;' . "\n";
+            }
+
+            $code .= "\n} else {\n"; # $value exists
         }
 
         if ($attr->has_default || $attr->has_builder) {
@@ -141,13 +151,18 @@ sub _generate_processattrs {
         push @res, $code;
     }
 
+    if($strict_constructor){
+        push @res, q{if($used < keys %{$args})}
+            . q{{ Mouse::Meta::Method::Constructor::_report_unknown_args($metaclass, \@attrs, $instance, $args) }};
+    }
+
     if($metaclass->is_anon_class){
         push @res, q{$instance->{__METACLASS__} = $metaclass;};
     }
 
     if($has_triggers){
         unshift @res, q{my @triggers;};
-        push    @res,  q{$_->[0]->($instance, $_->[1]) for @triggers;};
+        push    @res, q{$_->[0]->($instance, $_->[1]) for @triggers;};
     }
 
     return join "\n", @res;
@@ -188,6 +203,30 @@ sub _generate_BUILDALL {
     return join "\n", @code;
 }
 
+sub _report_unknown_args {
+    my($metaclass, $attrs, $instance, $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",
+        ref($instance), join ', ', @unknowns
+    );
+}
+
 1;
 __END__
 
index a946b03..2890b28 100644 (file)
@@ -325,6 +325,8 @@ sub _initialize_object{
 
 sub is_immutable {  $_[0]->{is_immutable} }
 
+sub __strict_constructor{ $_[0]->{strict_constructor} }
+
 package
     Mouse::Meta::Role;
 
index 86b7896..b095d32 100644 (file)
@@ -21,6 +21,8 @@ enum mouse_xc_flags_t {
     MOUSEf_XC_IS_IMMUTABLE   = 0x0001,
     MOUSEf_XC_IS_ANON        = 0x0002,
     MOUSEf_XC_HAS_BUILDARGS  = 0x0004,
+    MOUSEf_XC_CONSTRUCTOR_IS_STRICT
+                             = 0x0008,
 
     MOUSEf_XC_mask           = 0xFFFF /* not used */
 };
@@ -115,6 +117,10 @@ 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")){
+        flags |= MOUSEf_XC_CONSTRUCTOR_IS_STRICT;
+    }
+
     av_store(xc, MOUSE_XC_FLAGS,       newSVuv(flags));
     av_store(xc, MOUSE_XC_ATTRALL,     (SV*)attrall);
     av_store(xc, MOUSE_XC_BUILDALL,    (SV*)buildall);
@@ -240,12 +246,52 @@ mouse_buildargs(pTHX_ SV* metaclass, SV* const klass, I32 ax, I32 items) {
 }
 
 static void
+mouse_report_unknown_args(pTHX_ SV* const meta, AV* const attrs, HV* const args) {
+    HV* const attr_map = newHV_mortal();
+    SV* const unknown  = newSVpvs_flags("", SVs_TEMP);
+    I32 const len      = AvFILLp(attrs) + 1;
+    I32 i;
+    HE* he;
+
+    for(i = 0; i < len; i++){
+        SV* const attr = MOUSE_av_at(attrs, i);
+        AV* const xa   = mouse_get_xa(aTHX_ attr);
+        SV* const init_arg = MOUSE_xa_init_arg(xa);
+        if(SvOK(init_arg)){
+            (void)hv_store_ent(attr_map, init_arg, &PL_sv_undef, 0U);
+        }
+    }
+
+    hv_iterinit(args);
+    while((he = hv_iternext(args))){
+        SV* const key = hv_iterkeysv(he);
+        if(!hv_exists_ent(attr_map, key, 0U)){
+            sv_catpvf(unknown, "%"SVf", ", key);
+        }
+    }
+
+    if(SvCUR(unknown) > 0){
+        SvCUR(unknown) -= 2; /* chop "," */
+    }
+    else{
+        sv_setpvs(unknown, "(unknown)");
+    }
+
+    mouse_throw_error(meta, NULL,
+        "Unknown attribute passed to the constructor of %"SVf": %"SVf,
+        mcall0(meta, mouse_name), unknown);
+}
+
+
+
+static void
 mouse_class_initialize_object(pTHX_ SV* const meta, SV* const object, HV* const args, bool const ignore_triggers) {
     AV* const xc    = mouse_get_xc(aTHX_ meta);
     AV* const attrs = MOUSE_xc_attrall(xc);
     I32 len         = AvFILLp(attrs) + 1;
     I32 i;
     AV* triggers_queue = NULL;
+    I32 used = 0;
 
     assert(meta || object);
     assert(args);
@@ -259,6 +305,7 @@ mouse_class_initialize_object(pTHX_ SV* const meta, SV* const object, HV* const
         triggers_queue = newAV_mortal();
     }
 
+    /* for each attribute */
     for(i = 0; i < len; i++){
         SV* const attr = MOUSE_av_at(attrs, i);
         AV* const xa   = mouse_get_xa(aTHX_ attr);
@@ -284,6 +331,7 @@ mouse_class_initialize_object(pTHX_ SV* const meta, SV* const object, HV* const
 
                 av_push(triggers_queue, (SV*)pair);
             }
+            used++;
         }
         else { /* no init arg */
             if(flags & (MOUSEf_ATTR_HAS_DEFAULT | MOUSEf_ATTR_HAS_BUILDER)){
@@ -295,7 +343,11 @@ mouse_class_initialize_object(pTHX_ SV* const meta, SV* const object, HV* const
                 mouse_throw_error(attr, NULL, "Attribute (%"SVf") is required", slot);
             }
         }
-    } /* for each attributes */
+    } /* for each attribute */
+
+    if(MOUSE_xc_flags(xc) & MOUSEf_XC_CONSTRUCTOR_IS_STRICT && used < HvUSEDKEYS(args)){
+        mouse_report_unknown_args(aTHX_ meta, attrs, args);
+    }
 
     if(triggers_queue){
         len = AvFILLp(triggers_queue) + 1;
@@ -480,6 +532,7 @@ BOOT:
     INSTALL_SIMPLE_READER(Class, roles);
     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");