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