e2031792d75c5773d3f7262682ac86c52a4b214d
[gitmo/Moo.git] / lib / Method / Generate / Accessor.pm
1 package Method::Generate::Accessor;
2
3 use strictures 1;
4 use Moo::_Utils;
5 use base qw(Moo::Object);
6 use Sub::Quote;
7 use B 'perlstring';
8 BEGIN {
9   our $CAN_HAZ_XS =
10     !$ENV{MOO_XS_DISABLE}
11       &&
12     _maybe_load_module('Class::XSAccessor')
13       &&
14     (eval { Class::XSAccessor->VERSION('1.07') })
15   ;
16 }
17
18 sub generate_method {
19   my ($self, $into, $name, $spec, $quote_opts) = @_;
20   $name =~ s/^\+//;
21   die "Must have an is" unless my $is = $spec->{is};
22   if ($is eq 'ro') {
23     $spec->{reader} = $name unless exists $spec->{reader};
24   } elsif ($is eq 'rw') {
25     $spec->{accessor} = $name unless exists $spec->{accessor};
26   } elsif ($is eq 'lazy') {
27     $spec->{reader} = $name unless exists $spec->{reader};
28     $spec->{lazy} = 1;
29     $spec->{builder} ||= '_build_'.$name unless $spec->{default};
30   } elsif ($is eq 'rwp') {
31     $spec->{reader} = $name unless exists $spec->{reader};
32     $spec->{writer} = "_set_${name}" unless exists $spec->{writer};
33   } elsif ($is ne 'bare') {
34     die "Unknown is ${is}";
35   }
36   $spec->{builder} = '_build_'.$name if ($spec->{builder}||0) eq 1;
37   if (($spec->{predicate}||0) eq 1) {
38     $spec->{predicate} = $name =~ /^_/ ? "_has${name}" : "has_${name}";
39   }
40   if (($spec->{clearer}||0) eq 1) {
41     $spec->{clearer} = $name =~ /^_/ ? "_clear${name}" : "clear_${name}";
42   }
43   if (($spec->{trigger}||0) eq 1) {
44     $spec->{trigger} = quote_sub('shift->_trigger_'.$name.'(@_)');
45   }
46   if (exists $spec->{default}) {
47     my $default = $spec->{default};
48     require Scalar::Util;
49     if (not ref $default) {
50       die "Invalid default $default";
51     }
52     elsif (Scalar::Util::reftype $default ne 'CODE') {
53       if (Scalar::Util::blessed $default) {
54         die "Invalid default $default" unless $default->can('(&{}');
55       }
56       else {
57         die "Invalid default $default";
58       }
59     }
60   }
61
62   my %methods;
63   if (my $reader = $spec->{reader}) {
64     if (our $CAN_HAZ_XS && $self->is_simple_get($name, $spec)) {
65       $methods{$reader} = $self->_generate_xs(
66         getters => $into, $reader, $name, $spec
67       );
68     } else {
69       $self->{captures} = {};
70       $methods{$reader} =
71         quote_sub "${into}::${reader}"
72           => '    die "'.$reader.' is a read-only accessor" if @_ > 1;'."\n"
73              .$self->_generate_get($name, $spec)
74           => delete $self->{captures}
75         ;
76     }
77   }
78   if (my $accessor = $spec->{accessor}) {
79     if (
80       our $CAN_HAZ_XS
81       && $self->is_simple_get($name, $spec)
82       && $self->is_simple_set($name, $spec)
83     ) {
84       $methods{$accessor} = $self->_generate_xs(
85         accessors => $into, $accessor, $name, $spec
86       );
87     } else {
88       $self->{captures} = {};
89       $methods{$accessor} =
90         quote_sub "${into}::${accessor}"
91           => $self->_generate_getset($name, $spec)
92           => delete $self->{captures}
93         ;
94     }
95   }
96   if (my $writer = $spec->{writer}) {
97     if (
98       our $CAN_HAZ_XS
99       && $self->is_simple_set($name, $spec)
100     ) {
101       $methods{$writer} = $self->_generate_xs(
102         setters => $into, $writer, $name, $spec
103       );
104     } else {
105       $self->{captures} = {};
106       $methods{$writer} =
107         quote_sub "${into}::${writer}"
108           => $self->_generate_set($name, $spec)
109           => delete $self->{captures}
110         ;
111     }
112   }
113   if (my $pred = $spec->{predicate}) {
114     $methods{$pred} =
115       quote_sub "${into}::${pred}" =>
116         '    '.$self->_generate_simple_has('$_[0]', $name, $spec)."\n"
117       ;
118   }
119   if (my $cl = $spec->{clearer}) {
120     $methods{$cl} =
121       quote_sub "${into}::${cl}" => 
122         $self->_generate_simple_clear('$_[0]', $name, $spec)."\n"
123       ;
124   }
125   if (my $hspec = $spec->{handles}) {
126     my $asserter = $spec->{asserter} ||= '_assert_'.$name;
127     my @specs = do {
128       if (ref($hspec) eq 'ARRAY') {
129         map [ $_ => $_ ], @$hspec;
130       } elsif (ref($hspec) eq 'HASH') {
131         map [ $_ => ref($hspec->{$_}) ? @{$hspec->{$_}} : $hspec->{$_} ],
132           keys %$hspec;
133       } elsif (!ref($hspec)) {
134         map [ $_ => $_ ], Role::Tiny->methods_provided_by($hspec);
135       } else {
136         die "You gave me a handles of ${hspec} and I have no idea why";
137       }
138     };
139     foreach my $spec (@specs) {
140       my ($proxy, $target, @args) = @$spec;
141       $self->{captures} = {};
142       $methods{$proxy} =
143         quote_sub "${into}::${proxy}" =>
144           $self->_generate_delegation($asserter, $target, \@args),
145           delete $self->{captures}
146         ;
147     }
148   }
149   if (my $asserter = $spec->{asserter}) {
150     $self->{captures} = {};
151     $methods{$asserter} =
152       quote_sub "${into}::${asserter}" =>
153         'do { '.$self->_generate_get($name, $spec).qq! }||die "Attempted to access '${name}' but it is not set"!,
154         delete $self->{captures}
155       ;
156   }
157   \%methods;
158 }
159
160 sub is_simple_attribute {
161   my ($self, $name, $spec) = @_;
162   # clearer doesn't have to be listed because it doesn't
163   # affect whether defined/exists makes a difference
164   !grep $spec->{$_},
165     qw(lazy default builder coerce isa trigger predicate weak_ref);
166 }
167
168 sub is_simple_get {
169   my ($self, $name, $spec) = @_;
170   !($spec->{lazy} and ($spec->{default} or $spec->{builder}));
171 }
172
173 sub is_simple_set {
174   my ($self, $name, $spec) = @_;
175   !grep $spec->{$_}, qw(coerce isa trigger weak_ref);
176 }
177
178 sub has_eager_default {
179   my ($self, $name, $spec) = @_;
180   (!$spec->{lazy} and ($spec->{default} or $spec->{builder}));
181 }
182
183 sub _generate_get {
184   my ($self, $name, $spec) = @_;
185   my $simple = $self->_generate_simple_get('$_[0]', $name, $spec);
186   if ($self->is_simple_get($name, $spec)) {
187     $simple;
188   } else {
189     'do { '.$self->_generate_use_default(
190       '$_[0]', $name, $spec,
191       $self->_generate_simple_has('$_[0]', $name, $spec),
192     ).'; '.$simple.' }';
193   }
194 }
195
196 sub _generate_simple_has {
197   my ($self, $me, $name) = @_;
198   "exists ${me}->{${\perlstring $name}}";
199 }
200
201 sub _generate_simple_clear {
202   my ($self, $me, $name) = @_;
203   "    delete ${me}->{${\perlstring $name}}\n"
204 }
205
206 sub generate_get_default {
207   my $self = shift;
208   $self->{captures} = {};
209   my $code = $self->_generate_get_default(@_);
210   ($code, delete $self->{captures});
211 }
212
213 sub _generate_use_default {
214   my ($self, $me, $name, $spec, $test) = @_;
215   my $get_value = $self->_generate_get_default($me, $name, $spec);
216   if ($spec->{coerce}) {
217     $get_value = $self->_generate_coerce(
218       $name, $get_value,
219       $spec->{coerce}
220     )
221   }
222   $self->_generate_simple_set(
223     $me, $name, $spec, $get_value
224   ).' unless '.$test;
225 }
226
227 sub _generate_get_default {
228   my ($self, $me, $name, $spec) = @_;
229   $spec->{default}
230     ? $self->_generate_call_code($name, 'default', $me, $spec->{default})
231     : "${me}->${\$spec->{builder}}"
232 }
233
234 sub generate_simple_get {
235   my ($self, @args) = @_;
236   $self->_generate_simple_get(@args);
237 }
238
239 sub _generate_simple_get {
240   my ($self, $me, $name) = @_;
241   my $name_str = perlstring $name;
242   "${me}->{${name_str}}";
243 }
244
245 sub _generate_set {
246   my ($self, $name, $spec) = @_;
247   if ($self->is_simple_set($name, $spec)) {
248     $self->_generate_simple_set('$_[0]', $name, $spec, '$_[1]');
249   } else {
250     my ($coerce, $trigger, $isa_check) = @{$spec}{qw(coerce trigger isa)};
251     my $simple = $self->_generate_simple_set('$self', $name, $spec, '$value');
252     my $code = "do { my (\$self, \$value) = \@_;\n";
253     if ($coerce) {
254       $code .=
255         "        \$value = "
256         .$self->_generate_coerce($name, '$value', $coerce).";\n";
257     }
258     if ($isa_check) {
259       $code .= 
260         "        ".$self->_generate_isa_check($name, '$value', $isa_check).";\n";
261     }
262     if ($trigger) {
263       my $fire = $self->_generate_trigger($name, '$self', '$value', $trigger);
264       $code .=
265         "        ".$simple.";\n        ".$fire.";\n"
266         ."        \$value;\n";
267     } else {
268       $code .= "        ".$simple.";\n";
269     }
270     $code .= "      }";
271     $code;
272   }
273 }
274
275 sub generate_coerce {
276   my $self = shift;
277   $self->{captures} = {};
278   my $code = $self->_generate_coerce(@_);
279   ($code, delete $self->{captures});
280 }
281
282 sub _generate_coerce {
283   my ($self, $name, $value, $coerce) = @_;
284   $self->_generate_call_code($name, 'coerce', "${value}", $coerce);
285 }
286  
287 sub generate_trigger {
288   my $self = shift;
289   $self->{captures} = {};
290   my $code = $self->_generate_trigger(@_);
291   ($code, delete $self->{captures});
292 }
293
294 sub _generate_trigger {
295   my ($self, $name, $obj, $value, $trigger) = @_;
296   $self->_generate_call_code($name, 'trigger', "${obj}, ${value}", $trigger);
297 }
298
299 sub generate_isa_check {
300   my ($self, @args) = @_;
301   $self->{captures} = {};
302   my $code = $self->_generate_isa_check(@args);
303   ($code, delete $self->{captures});
304 }
305
306 sub _generate_isa_check {
307   my ($self, $name, $value, $check) = @_;
308   $self->_generate_call_code($name, 'isa_check', $value, $check);
309 }
310
311 sub _generate_call_code {
312   my ($self, $name, $type, $values, $sub) = @_;
313   if (my $quoted = quoted_from_sub($sub)) {
314     my $code = $quoted->[1];
315     my $at_ = '@_ = ('.$values.');';
316     if (my $captures = $quoted->[2]) {
317       my $cap_name = qq{\$${type}_captures_for_${name}};
318       $self->{captures}->{$cap_name} = \$captures;
319       Sub::Quote::inlinify(
320         $code, $values, Sub::Quote::capture_unroll($cap_name, $captures, 6)
321       );
322     } else {
323       Sub::Quote::inlinify($code, $values);
324     }
325   } else {
326     my $cap_name = qq{\$${type}_for_${name}};
327     $self->{captures}->{$cap_name} = \$sub;
328     "${cap_name}->(${values})";
329   }
330 }
331
332 sub generate_populate_set {
333   my $self = shift;
334   $self->{captures} = {};
335   my $code = $self->_generate_populate_set(@_);
336   ($code, delete $self->{captures});
337 }
338
339 sub _generate_populate_set {
340   my ($self, $me, $name, $spec, $source, $test) = @_;
341   if ($self->has_eager_default($name, $spec)) {
342     my $get_indent = ' ' x ($spec->{isa} ? 6 : 4);
343     my $get_default = $self->_generate_get_default(
344                         '$new', $_, $spec
345                       );
346     my $get_value = 
347       defined($spec->{init_arg})
348         ? "(\n${get_indent}  ${test}\n${get_indent}   ? ${source}\n${get_indent}   : "
349             .$get_default
350             ."\n${get_indent})"
351         : $get_default;
352     if ($spec->{coerce}) {
353       $get_value = $self->_generate_coerce(
354         $name, $get_value,
355         $spec->{coerce}
356       )
357     }
358     ($spec->{isa}
359       ? "    {\n      my \$value = ".$get_value.";\n      "
360         .$self->_generate_isa_check(
361           $name, '$value', $spec->{isa}
362         ).";\n"
363         .'      '.$self->_generate_simple_set($me, $name, $spec, '$value').";\n"
364         ."    }\n"
365       : '    '.$self->_generate_simple_set($me, $name, $spec, $get_value).";\n"
366     )
367     .($spec->{trigger}
368       ? '    '
369         .$self->_generate_trigger(
370           $name, $me, $self->_generate_simple_get($me, $name, $spec),
371           $spec->{trigger}
372         )." if ${test};\n"
373       : ''
374     );
375   } else {
376     "    if (${test}) {\n"
377       .($spec->{coerce}
378         ? "      $source = "
379           .$self->_generate_coerce(
380             $name, $source,
381             $spec->{coerce}
382           ).";\n"
383         : ""
384       )
385       .($spec->{isa}
386         ? "      "
387           .$self->_generate_isa_check(
388             $name, $source, $spec->{isa}
389           ).";\n"
390         : ""
391       )
392       ."      ".$self->_generate_simple_set($me, $name, $spec, $source).";\n"
393       .($spec->{trigger}
394         ? "      "
395           .$self->_generate_trigger(
396             $name, $me, $self->_generate_simple_get($me, $name, $spec),
397             $spec->{trigger}
398           ).";\n"
399         : ""
400       )
401       ."    }\n";
402   }
403 }
404
405 sub generate_multi_set {
406   my ($self, $me, $to_set, $from) = @_;
407   "\@{${me}}{qw(${\join ' ', @$to_set})} = $from";
408 }
409
410 sub _generate_core_set {
411   my ($self, $me, $name, $spec, $value) = @_;
412   my $name_str = perlstring $name;
413   "${me}->{${name_str}} = ${value}";
414 }
415
416 sub _generate_simple_set {
417   my ($self, $me, $name, $spec, $value) = @_;
418   my $name_str = perlstring $name;
419
420   if ($spec->{weak_ref}) {
421     $value = '$preserve = '.$value;
422     my $simple = $self->_generate_core_set($me, $name, $spec, $value);
423     require Scalar::Util;
424
425     # Perl < 5.8.3 can't weaken refs to readonly vars
426     # (e.g. string constants). This *can* be solved by:
427     #
428     #Internals::SetReadWrite($foo);
429     #Scalar::Util::weaken ($foo);
430     #Internals::SetReadOnly($foo);
431     #
432     # but requires XS and is just too damn crazy
433     # so simply throw a better exception
434     my $weak_simple = "my \$preserve; Scalar::Util::weaken(${simple})";
435     Moo::_Utils::lt_5_8_3() ? <<"EOC" : $weak_simple;
436
437       my \$preserve;
438       eval { Scalar::Util::weaken($simple); 1 } or do {
439         if( \$@ =~ /Modification of a read-only value attempted/) {
440           require Carp;
441           Carp::croak( sprintf (
442             'Reference to readonly value in "%s" can not be weakened on Perl < 5.8.3',
443             $name_str,
444           ) );
445         } else {
446           die \$@;
447         }
448       };
449 EOC
450   } else {
451     $self->_generate_core_set($me, $name, $spec, $value);
452   }
453 }
454
455 sub _generate_getset {
456   my ($self, $name, $spec) = @_;
457   q{(@_ > 1}."\n      ? ".$self->_generate_set($name, $spec)
458     ."\n      : ".$self->_generate_get($name, $spec)."\n    )";
459 }
460
461 sub _generate_delegation {
462   my ($self, $asserter, $target, $args) = @_;
463   my $arg_string = do {
464     if (@$args) {
465       # I could, I reckon, linearise out non-refs here using perlstring
466       # plus something to check for numbers but I'm unsure if it's worth it
467       $self->{captures}{'@curries'} = $args;
468       '@curries, @_';
469     } else {
470       '@_';
471     }
472   };
473   "shift->${asserter}->${target}(${arg_string});";
474 }
475
476 sub _generate_xs {
477   my ($self, $type, $into, $name, $slot) = @_;
478   Class::XSAccessor->import(
479     class => $into,
480     $type => { $name => $slot },
481     replace => 1,
482   );
483   $into->can($name);
484 }
485
486 sub default_construction_string { '{}' }
487
488 1;