0c929a764471cbe6196615edacda92f86f9c77aa
[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_constructor = $metaclass->__strict_constructor;
55
56
57     if($strict_constructor){
58         push @res, 'my $used = 0;';
59     }
60
61     for my $index (0 .. @$attrs - 1) {
62         my $code = '';
63
64         my $attr = $attrs->[$index];
65         my $key  = $attr->name;
66
67         my $init_arg        = $attr->init_arg;
68         my $type_constraint = $attr->type_constraint;
69         my $is_weak_ref     = $attr->is_weak_ref;
70         my $need_coercion;
71
72         my $instance_slot  = $method_class->_inline_slot('$instance', $key);
73         my $attr_var       = "\$attrs[$index]";
74         my $constraint_var;
75
76         if(defined $type_constraint){
77              $constraint_var = "$attr_var\->{type_constraint}";
78              $need_coercion  = ($attr->should_coerce && $type_constraint->has_coercion);
79         }
80
81         $code .= "# initialize $key\n";
82
83         my $post_process = '';
84         if(defined $type_constraint){
85             $post_process .= "\$checks[$index]->($instance_slot)";
86             $post_process .= "  or $attr_var->_throw_type_constraint_error($instance_slot, $constraint_var);\n";
87         }
88         if($is_weak_ref){
89             $post_process .= "Scalar::Util::weaken($instance_slot) if ref $instance_slot;\n";
90         }
91
92         if (defined $init_arg) {
93             my $value = "\$args->{q{$init_arg}}";
94
95             $code .= "if (exists $value) {\n";
96
97             if($need_coercion){
98                 $value = "$constraint_var->coerce($value)";
99             }
100
101             $code .= "$instance_slot = $value;\n";
102             $code .= $post_process;
103
104             if ($attr->has_trigger) {
105                 $has_triggers++;
106                 $code .= "push \@triggers, [$attr_var\->{trigger}, $instance_slot];\n";
107             }
108
109             if ($strict_constructor){
110                 $code .= '$used++;' . "\n";
111             }
112
113             $code .= "\n} else {\n"; # $value exists
114         }
115
116         if ($attr->has_default || $attr->has_builder) {
117             unless ($attr->is_lazy) {
118                 my $default = $attr->default;
119                 my $builder = $attr->builder;
120
121                 my $value;
122                 if (defined($builder)) {
123                     $value = "\$instance->$builder()";
124                 }
125                 elsif (ref($default) eq 'CODE') {
126                     $value = "$attr_var\->{default}->(\$instance)";
127                 }
128                 elsif (defined($default)) {
129                     $value = "$attr_var\->{default}";
130                 }
131                 else {
132                     $value = 'undef';
133                 }
134
135                 if($need_coercion){
136                     $value = "$constraint_var->coerce($value)";
137                 }
138
139                 $code .= "$instance_slot = $value;\n";
140                 if($is_weak_ref){
141                     $code .= "Scalar::Util::weaken($instance_slot);\n";
142                 }
143             }
144         }
145         elsif ($attr->is_required) {
146             $code .= "Carp::confess('Attribute ($key) is required');";
147         }
148
149         $code .= "}\n" if defined $init_arg;
150
151         push @res, $code;
152     }
153
154     if($strict_constructor){
155         push @res, q{if($used < keys %{$args})}
156             . q{{ Mouse::Meta::Method::Constructor::_report_unknown_args($metaclass, \@attrs, $instance, $args) }};
157     }
158
159     if($metaclass->is_anon_class){
160         push @res, q{$instance->{__METACLASS__} = $metaclass;};
161     }
162
163     if($has_triggers){
164         unshift @res, q{my @triggers;};
165         push    @res, q{$_->[0]->($instance, $_->[1]) for @triggers;};
166     }
167
168     return join "\n", @res;
169 }
170
171 sub _generate_BUILDARGS {
172     my(undef, $metaclass) = @_;
173
174     my $class = $metaclass->name;
175     if ( $class->can('BUILDARGS') && $class->can('BUILDARGS') != \&Mouse::Object::BUILDARGS ) {
176         return 'my $args = $class->BUILDARGS(@_)';
177     }
178
179     return <<'...';
180         my $args;
181         if ( scalar @_ == 1 ) {
182             ( ref( $_[0] ) eq 'HASH' )
183                 || Carp::confess "Single parameters to new() must be a HASH ref";
184             $args = +{ %{ $_[0] } };
185         }
186         else {
187             $args = +{@_};
188         }
189 ...
190 }
191
192 sub _generate_BUILDALL {
193     my (undef, $metaclass) = @_;
194
195     return '' unless $metaclass->name->can('BUILD');
196
197     my @code;
198     for my $class ($metaclass->linearized_isa) {
199         if (Mouse::Util::get_code_ref($class, 'BUILD')) {
200             unshift  @code, qq{${class}::BUILD(\$instance, \$args);};
201         }
202     }
203     return join "\n", @code;
204 }
205
206 sub _report_unknown_args {
207     my($metaclass, $attrs, $instance, $args) = @_;
208
209     my @unknowns;
210     my %init_args;
211     foreach my $attr(@{$attrs}){
212         my $init_arg = $attr->init_arg;
213         if(defined $init_arg){
214             $init_args{$init_arg}++;
215         }
216     }
217
218     while(my $key = each %{$args}){
219         if(!exists $init_args{$key}){
220             push @unknowns, $key;
221         }
222     }
223
224     $metaclass->throw_error( sprintf
225         "Unknown attribute passed to the constructor of %s: %s",
226         ref($instance), join ', ', @unknowns
227     );
228 }
229
230 1;
231 __END__
232
233 =head1 NAME
234
235 Mouse::Meta::Method::Constructor - A Mouse method generator for constructors
236
237 =head1 VERSION
238
239 This document describes Mouse version 0.50_01
240
241 =head1 SEE ALSO
242
243 L<Moose::Meta::Method::Constructor>
244
245 =cut