From: gfx Date: Sat, 20 Feb 2010 05:30:24 +0000 (+0900) Subject: Implement strict constructors, which will warn unkown constructor arguments X-Git-Tag: 0.50_02~5 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=e128626c409797822ffd8a4079f833eb3dc0fd37;p=gitmo%2FMouse.git Implement strict constructors, which will warn unkown constructor arguments --- diff --git a/lib/Mouse/Meta/Class.pm b/lib/Mouse/Meta/Class.pm index 9f7bc68..7b03c6f 100644 --- a/lib/Mouse/Meta/Class.pm +++ b/lib/Mouse/Meta/Class.pm @@ -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); diff --git a/lib/Mouse/Meta/Method/Constructor.pm b/lib/Mouse/Meta/Method/Constructor.pm index 6064aff..0c929a7 100644 --- a/lib/Mouse/Meta/Method/Constructor.pm +++ b/lib/Mouse/Meta/Method/Constructor.pm @@ -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__ diff --git a/lib/Mouse/PurePerl.pm b/lib/Mouse/PurePerl.pm index a946b03..2890b28 100644 --- a/lib/Mouse/PurePerl.pm +++ b/lib/Mouse/PurePerl.pm @@ -325,6 +325,8 @@ sub _initialize_object{ sub is_immutable { $_[0]->{is_immutable} } +sub __strict_constructor{ $_[0]->{strict_constructor} } + package Mouse::Meta::Role; diff --git a/xs-src/Mouse.xs b/xs-src/Mouse.xs index 86b7896..b095d32 100644 --- a/xs-src/Mouse.xs +++ b/xs-src/Mouse.xs @@ -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");