Checking in changes prior to tagging of version 0.50_04. Changelog diff is:
[gitmo/Mouse.git] / lib / Mouse / Meta / Method / Constructor.pm
1 package Mouse::Meta::Method::Constructor;
2 use Mouse::Util qw(:meta); # enables strict and warnings
3
4 sub _inline_slot{
5     my(undef, $self_var, $attr_name) = @_;
6     return sprintf '%s->{q{%s}}', $self_var, $attr_name;
7 }
8
9 sub _generate_constructor {
10     my ($class, $metaclass, $args) = @_;
11
12     my $associated_metaclass_name = $metaclass->name;
13
14     my @attrs         = $metaclass->get_all_attributes;
15
16     my $buildall      = $class->_generate_BUILDALL($metaclass);
17     my $buildargs     = $class->_generate_BUILDARGS($metaclass);
18     my $processattrs  = $class->_generate_processattrs($metaclass, \@attrs);
19
20     my @checks = map { $_ && $_->_compiled_type_constraint }
21                  map { $_->type_constraint } @attrs;
22
23     my $source = sprintf("#line %d %s\n", __LINE__, __FILE__).<<"...";
24         sub \{
25             my \$class = shift;
26             return \$class->Mouse::Object::new(\@_)
27                 if \$class ne q{$associated_metaclass_name};
28             # BUILDARGS
29             $buildargs;
30             my \$instance = bless {}, \$class;
31             # process attributes
32             $processattrs;
33             # BUILDALL
34             $buildall;
35             return \$instance;
36         }
37 ...
38     #warn $source;
39     my $code;
40     my $e = do{
41         local $@;
42         $code = eval $source;
43         $@;
44     };
45     die $e if $e;
46     return $code;
47 }
48
49 sub _generate_processattrs {
50     my ($method_class, $metaclass, $attrs) = @_;
51     my @res;
52
53     my $has_triggers;
54     my $strict = $metaclass->__strict_constructor;
55
56     if($strict){
57         push @res, 'my $used = 0;';
58     }
59
60     for my $index (0 .. @$attrs - 1) {
61         my $code = '';
62
63         my $attr = $attrs->[$index];
64         my $key  = $attr->name;
65
66         my $init_arg        = $attr->init_arg;
67         my $type_constraint = $attr->type_constraint;
68         my $is_weak_ref     = $attr->is_weak_ref;
69         my $need_coercion;
70
71         my $instance_slot  = $method_class->_inline_slot('$instance', $key);
72         my $attr_var       = "\$attrs[$index]";
73         my $constraint_var;
74
75         if(defined $type_constraint){
76              $constraint_var = "$attr_var\->{type_constraint}";
77              $need_coercion  = ($attr->should_coerce && $type_constraint->has_coercion);
78         }
79
80         $code .= "# initialize $key\n";
81
82         my $post_process = '';
83         if(defined $type_constraint){
84             $post_process .= "\$checks[$index]->($instance_slot)";
85             $post_process .= "  or $attr_var->_throw_type_constraint_error($instance_slot, $constraint_var);\n";
86         }
87         if($is_weak_ref){
88             $post_process .= "Scalar::Util::weaken($instance_slot) if ref $instance_slot;\n";
89         }
90
91         if (defined $init_arg) {
92             my $value = "\$args->{q{$init_arg}}";
93
94             $code .= "if (exists $value) {\n";
95
96             if($need_coercion){
97                 $value = "$constraint_var->coerce($value)";
98             }
99
100             $code .= "$instance_slot = $value;\n";
101             $code .= $post_process;
102
103             if ($attr->has_trigger) {
104                 $has_triggers++;
105                 $code .= "push \@triggers, [$attr_var\->{trigger}, $instance_slot];\n";
106             }
107
108             if ($strict){
109                 $code .= '++$used;' . "\n";
110             }
111
112             $code .= "\n} else {\n"; # $value exists
113         }
114
115         if ($attr->has_default || $attr->has_builder) {
116             unless ($attr->is_lazy) {
117                 my $default = $attr->default;
118                 my $builder = $attr->builder;
119
120                 my $value;
121                 if (defined($builder)) {
122                     $value = "\$instance->$builder()";
123                 }
124                 elsif (ref($default) eq 'CODE') {
125                     $value = "$attr_var\->{default}->(\$instance)";
126                 }
127                 elsif (defined($default)) {
128                     $value = "$attr_var\->{default}";
129                 }
130                 else {
131                     $value = 'undef';
132                 }
133
134                 if($need_coercion){
135                     $value = "$constraint_var->coerce($value)";
136                 }
137
138                 $code .= "$instance_slot = $value;\n";
139                 if($is_weak_ref){
140                     $code .= "Scalar::Util::weaken($instance_slot);\n";
141                 }
142             }
143         }
144         elsif ($attr->is_required) {
145             $code .= "Carp::confess('Attribute ($key) is required');";
146         }
147
148         $code .= "}\n" if defined $init_arg;
149
150         push @res, $code;
151     }
152
153     if($strict){
154         push @res, q{if($used < keys %{$args})}
155             . sprintf q{{ %s->_report_unknown_args($metaclass, \@attrs, $args) }}, $method_class;
156     }
157
158     if($metaclass->is_anon_class){
159         push @res, q{$instance->{__METACLASS__} = $metaclass;};
160     }
161
162     if($has_triggers){
163         unshift @res, q{my @triggers;};
164         push    @res, q{$_->[0]->($instance, $_->[1]) for @triggers;};
165     }
166
167     return join "\n", @res;
168 }
169
170 sub _generate_BUILDARGS {
171     my(undef, $metaclass) = @_;
172
173     my $class = $metaclass->name;
174     if ( $class->can('BUILDARGS') && $class->can('BUILDARGS') != \&Mouse::Object::BUILDARGS ) {
175         return 'my $args = $class->BUILDARGS(@_)';
176     }
177
178     return <<'...';
179         my $args;
180         if ( scalar @_ == 1 ) {
181             ( ref( $_[0] ) eq 'HASH' )
182                 || Carp::confess "Single parameters to new() must be a HASH ref";
183             $args = +{ %{ $_[0] } };
184         }
185         else {
186             $args = +{@_};
187         }
188 ...
189 }
190
191 sub _generate_BUILDALL {
192     my (undef, $metaclass) = @_;
193
194     return '' unless $metaclass->name->can('BUILD');
195
196     my @code;
197     for my $class ($metaclass->linearized_isa) {
198         if (Mouse::Util::get_code_ref($class, 'BUILD')) {
199             unshift  @code, qq{${class}::BUILD(\$instance, \$args);};
200         }
201     }
202     return join "\n", @code;
203 }
204
205 sub _report_unknown_args {
206     my(undef, $metaclass, $attrs, $args) = @_;
207
208     my @unknowns;
209     my %init_args;
210     foreach my $attr(@{$attrs}){
211         my $init_arg = $attr->init_arg;
212         if(defined $init_arg){
213             $init_args{$init_arg}++;
214         }
215     }
216
217     while(my $key = each %{$args}){
218         if(!exists $init_args{$key}){
219             push @unknowns, $key;
220         }
221     }
222
223     $metaclass->throw_error( sprintf
224         "Unknown attribute passed to the constructor of %s: %s",
225         $metaclass->name, Mouse::Util::english_list(@unknowns),
226     );
227 }
228
229 1;
230 __END__
231
232 =head1 NAME
233
234 Mouse::Meta::Method::Constructor - A Mouse method generator for constructors
235
236 =head1 VERSION
237
238 This document describes Mouse version 0.50_04
239
240 =head1 SEE ALSO
241
242 L<Moose::Meta::Method::Constructor>
243
244 =cut