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