Use fewer assignments when doing a coercion in the constructor
[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; # this one is using by evaled code
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     warn $code if $ENV{DEBUG};
25
26     local $@;
27     my $res = eval $code;
28     die $@ if $@;
29     $res;
30 }
31
32 sub _generate_processattrs {
33     my ($class, $meta, $attrs) = @_;
34     my @res;
35     for my $index (0..scalar(@$attrs)-1) {
36         my $attr = $attrs->[$index];
37         my $from = $attr->init_arg;
38         my $key  = $attr->name;
39
40         my $part1 = do {
41             my @code;
42
43             push @code, "my \$value = \$args->{'$from'};";
44
45             if ($attr->should_coerce) {
46                 push @code, "\$value = \$attr->coerce_constraint( \$value );";
47             }
48
49             if ($attr->has_type_constraint) {
50                 push @code, "\$attr->verify_type_constraint( \$value );";
51             }
52
53             push @code, "\$instance->{'$key'} = \$value;";
54
55             if ($attr->is_weak_ref) {
56                 push @code, "weaken( \$instance->{'$key'} ) if ref( \$value );";
57             }
58
59             if ( $attr->has_trigger ) {
60                 push @code, "\$attr->trigger->( \$instance, \$value, \$attr );";
61             }
62
63             join "\n", @code;
64         };
65
66         my $part2 = 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) {
77                         push @code, "\$attr->coerce_constraint(";
78                     }
79
80                         if ($attr->has_builder) {
81                             push @code, "\$instance->$builder";
82                         }
83                         elsif (ref($default) eq 'CODE') {
84                             push @code, "\$attr->default()->()";
85                         }
86                         else {
87                             push @code, "\$attr->default()";
88                         }
89
90                     if ($attr->should_coerce) {
91                         push @code, ");";
92                     }
93                     else {
94                         push @code, ";";
95                     }
96
97                     if ($attr->has_type_constraint) {
98                         push @code, "\$attr->verify_type_constraint(\$value);";
99                     }
100
101                     push @code, "\$instance->{'$key'} = \$value;";
102
103                     if ($attr->is_weak_ref) {
104                         push @code, "weaken( \$instance->{'$key'} ) if ref( \$value );";
105                     }
106                 }
107                 join "\n", @code;
108             }
109             else {
110                 if ( $attr->is_required ) {
111                     qq{Carp::confess("Attribute ($key) is required");};
112                 } else {
113                     ""
114                 }
115             }
116         };
117         my $code = <<"...";
118             {
119                 my \$attr = \$attrs[$index];
120                 if (exists(\$args->{'$from'})) {
121                     $part1;
122                 } else {
123                     $part2;
124                 }
125             }
126 ...
127         push @res, $code;
128     }
129     return join "\n", @res;
130 }
131
132 sub _generate_BUILDARGS {
133     <<'...';
134     do {
135         if ( scalar @_ == 1 ) {
136             if ( defined $_[0] ) {
137                 ( ref( $_[0] ) eq 'HASH' )
138                 || Carp::confess "Single parameters to new() must be a HASH ref";
139                 +{ %{ $_[0] } };
140             }
141             else {
142                 +{};
143             }
144         }
145         else {
146             +{@_};
147         }
148     };
149 ...
150 }
151
152 sub _generate_BUILDALL {
153     my ($class, $meta) = @_;
154     return '' unless $meta->name->can('BUILD');
155
156     my @code = ();
157     push @code, q{no strict 'refs';};
158     push @code, q{no warnings 'once';};
159     no strict 'refs';
160     for my $class ($meta->linearized_isa) {
161         if (*{ $class . '::BUILD' }{CODE}) {
162             push  @code, qq{${class}::BUILD->(\$instance, \$args);};
163         }
164     }
165     return join "\n", @code;
166 }
167
168 1;