Test for init_arg => undef
[gitmo/Mouse.git] / lib / Mouse / Meta / Method / Constructor.pm
1 package Mouse::Meta::Method::Constructor;
2 use strict;
3 use warnings;
4
5 sub generate_constructor_method_inline {
6     my ($class, $meta) = @_;
7
8     my @attrs = $meta->compute_all_applicable_attributes;
9     my $buildall = $class->_generate_BUILDALL($meta);
10     my $buildargs = $class->_generate_BUILDARGS();
11     my $processattrs = $class->_generate_processattrs($meta, \@attrs);
12
13     my $code = <<"...";
14     sub {
15         my \$class = shift;
16         my \$args = $buildargs;
17         my \$instance = bless {}, \$class;
18         $processattrs;
19         $buildall;
20         return \$instance;
21     }
22 ...
23
24     local $@;
25     my $res = eval $code;
26     die $@ if $@;
27     $res;
28 }
29
30 sub _generate_processattrs {
31     my ($class, $meta, $attrs) = @_;
32     my @res;
33     for my $index (0..scalar(@$attrs)-1) {
34         my $attr = $attrs->[$index];
35         my $from = $attr->init_arg;
36         my $key  = $attr->name;
37
38         my $set_value = do {
39             my @code;
40
41             if ($attr->should_coerce && $attr->type_constraint) {
42                 push @code, "my \$value = Mouse::TypeRegistry->typecast_constraints('".$attr->associated_class->name."', \$attrs[$index]->{find_type_constraint}, \$attrs[$index]->{type_constraint}, \$args->{'$from'});";
43             }
44             else {
45                 push @code, "my \$value = \$args->{'$from'};";
46             }
47
48             if ($attr->has_type_constraint) {
49                 push @code, "{local \$_ = \$value; unless (\$attrs[$index]->{find_type_constraint}->(\$_)) {";
50                 push @code, "\$attrs[$index]->verify_type_constraint_error('$key', \$_, \$attrs[$index]->type_constraint)}}";
51             }
52
53             push @code, "\$instance->{'$key'} = \$value;";
54
55             if ($attr->is_weak_ref) {
56                 push @code, "Scalar::Util::weaken( \$instance->{'$key'} ) if ref( \$value );";
57             }
58
59             if ( $attr->has_trigger ) {
60                 push @code, "\$attrs[$index]->{trigger}->( \$instance, \$value, \$attrs[$index] );";
61             }
62
63             join "\n", @code;
64         };
65
66         my $make_default_value = do {
67             my @code;
68
69             if ( $attr->has_default || $attr->has_builder ) {
70                 unless ( $attr->is_lazy ) {
71                     my $default = $attr->default;
72                     my $builder = $attr->builder;
73
74                     push @code, "my \$value = ";
75
76                     if ($attr->should_coerce && $attr->type_constraint) {
77                         push @code, "Mouse::TypeRegistry->typecast_constraints('".$attr->associated_class->name."', \$attrs[$index]->{find_type_constraint}, \$attrs[$index]->{type_constraint}, ";
78                     }
79                         if ($attr->has_builder) {
80                             push @code, "\$instance->$builder";
81                         }
82                         elsif (ref($default) eq 'CODE') {
83                             push @code, "\$attrs[$index]->{default}->(\$instance)";
84                         }
85                         elsif (!defined($default)) {
86                             push @code, 'undef';
87                         }
88                         elsif ($default =~ /^\-?[0-9]+(?:\.[0-9]+)$/) {
89                             push @code, $default;
90                         }
91                         else {
92                             push @code, "'$default'";
93                         }
94
95                     if ($attr->should_coerce) {
96                         push @code, ");";
97                     }
98                     else {
99                         push @code, ";";
100                     }
101
102                     if ($attr->has_type_constraint) {
103                         push @code, "{local \$_ = \$value; unless (\$attrs[$index]->{find_type_constraint}->(\$_)) {";
104                         push @code, "\$attrs[$index]->verify_type_constraint_error('$key', \$_, \$attrs[$index]->type_constraint)}}";
105                     }
106
107                     push @code, "\$instance->{'$key'} = \$value;";
108
109                     if ($attr->is_weak_ref) {
110                         push @code, "Scalar::Util::weaken( \$instance->{'$key'} ) if ref( \$value );";
111                     }
112                 }
113                 join "\n", @code;
114             }
115             else {
116                 if ( $attr->is_required ) {
117                     qq{Carp::confess("Attribute ($key) is required");};
118                 } else {
119                     ""
120                 }
121             }
122         };
123         my $code = <<"...";
124             {
125                 if (exists(\$args->{'$from'})) {
126                     $set_value;
127 ...
128         if ($make_default_value) {
129             $code .= <<"...";
130                 } else {
131                     $make_default_value;
132 ...
133         }
134         $code .= <<"...";
135                 }
136             }
137 ...
138         push @res, $code;
139     }
140     return join "\n", @res;
141 }
142
143 sub _generate_BUILDARGS {
144     <<'...';
145     do {
146         if ( scalar @_ == 1 ) {
147             if ( defined $_[0] ) {
148                 ( ref( $_[0] ) eq 'HASH' )
149                 || Carp::confess "Single parameters to new() must be a HASH ref";
150                 +{ %{ $_[0] } };
151             }
152             else {
153                 +{};
154             }
155         }
156         else {
157             +{@_};
158         }
159     };
160 ...
161 }
162
163 sub _generate_BUILDALL {
164     my ($class, $meta) = @_;
165     return '' unless $meta->name->can('BUILD');
166
167     my @code = ();
168     push @code, q{no strict 'refs';};
169     push @code, q{no warnings 'once';};
170     no strict 'refs';
171     for my $klass ($meta->linearized_isa) {
172         if (*{ $klass . '::BUILD' }{CODE}) {
173             push  @code, qq{${klass}::BUILD(\$instance, \$args);};
174         }
175     }
176     return join "\n", @code;
177 }
178
179 1;