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