throw an error on non-string builder (RT#78479)
[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 use Scalar::Util 'blessed';
9 use overload ();
10 BEGIN {
11   our $CAN_HAZ_XS =
12     !$ENV{MOO_XS_DISABLE}
13       &&
14     _maybe_load_module('Class::XSAccessor')
15       &&
16     (eval { Class::XSAccessor->VERSION('1.07') })
17   ;
18 }
19
20 sub generate_method {
21   my ($self, $into, $name, $spec, $quote_opts) = @_;
22   $name =~ s/^\+//;
23   die "Must have an is" unless my $is = $spec->{is};
24   if ($is eq 'ro') {
25     $spec->{reader} = $name unless exists $spec->{reader};
26   } elsif ($is eq 'rw') {
27     $spec->{accessor} = $name unless exists $spec->{accessor};
28   } elsif ($is eq 'lazy') {
29     $spec->{reader} = $name unless exists $spec->{reader};
30     $spec->{lazy} = 1;
31     $spec->{builder} ||= '_build_'.$name unless $spec->{default};
32   } elsif ($is eq 'rwp') {
33     $spec->{reader} = $name unless exists $spec->{reader};
34     $spec->{writer} = "_set_${name}" unless exists $spec->{writer};
35   } elsif ($is ne 'bare') {
36     die "Unknown is ${is}";
37   }
38   $spec->{builder} = '_build_'.$name if ($spec->{builder}||0) eq 1;
39   die "Invalid builder for $into->$name - not a string"
40     if exists $spec->{builder} and ref $spec->{builder};
41   if (($spec->{predicate}||0) eq 1) {
42     $spec->{predicate} = $name =~ /^_/ ? "_has${name}" : "has_${name}";
43   }
44   if (($spec->{clearer}||0) eq 1) {
45     $spec->{clearer} = $name =~ /^_/ ? "_clear${name}" : "clear_${name}";
46   }
47   if (($spec->{trigger}||0) eq 1) {
48     $spec->{trigger} = quote_sub('shift->_trigger_'.$name.'(@_)');
49   }
50
51   for my $setting (qw( default coerce )) {
52     next if !exists $spec->{$setting};
53     my $value = $spec->{$setting};
54     my $invalid = "Invalid $setting '" . overload::StrVal($value)
55       . "' for $into->$name - not a coderef";
56     die "$invalid or code-convertible object"
57       unless ref $value and (ref $value eq 'CODE' or blessed($value));
58     die "$invalid and could not be converted to a coderef: $@"
59       if !eval { \&$value };
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     ).'; '
193     .($spec->{isa}
194       ?($self->_generate_isa_check($name, $simple, $spec->{isa}).'; ')
195       :''
196     )
197     .$simple.' }';
198   }
199 }
200
201 sub _generate_simple_has {
202   my ($self, $me, $name) = @_;
203   "exists ${me}->{${\perlstring $name}}";
204 }
205
206 sub _generate_simple_clear {
207   my ($self, $me, $name) = @_;
208   "    delete ${me}->{${\perlstring $name}}\n"
209 }
210
211 sub generate_get_default {
212   my $self = shift;
213   $self->{captures} = {};
214   my $code = $self->_generate_get_default(@_);
215   ($code, delete $self->{captures});
216 }
217
218 sub _generate_use_default {
219   my ($self, $me, $name, $spec, $test) = @_;
220   my $get_value = $self->_generate_get_default($me, $name, $spec);
221   if ($spec->{coerce}) {
222     $get_value = $self->_generate_coerce(
223       $name, $get_value,
224       $spec->{coerce}
225     )
226   }
227   $self->_generate_simple_set(
228     $me, $name, $spec, $get_value
229   ).' unless '.$test;
230 }
231
232 sub _generate_get_default {
233   my ($self, $me, $name, $spec) = @_;
234   $spec->{default}
235     ? $self->_generate_call_code($name, 'default', $me, $spec->{default})
236     : "${me}->${\$spec->{builder}}"
237 }
238
239 sub generate_simple_get {
240   my ($self, @args) = @_;
241   $self->_generate_simple_get(@args);
242 }
243
244 sub _generate_simple_get {
245   my ($self, $me, $name) = @_;
246   my $name_str = perlstring $name;
247   "${me}->{${name_str}}";
248 }
249
250 sub _generate_set {
251   my ($self, $name, $spec) = @_;
252   if ($self->is_simple_set($name, $spec)) {
253     $self->_generate_simple_set('$_[0]', $name, $spec, '$_[1]');
254   } else {
255     my ($coerce, $trigger, $isa_check) = @{$spec}{qw(coerce trigger isa)};
256     my $simple = $self->_generate_simple_set('$self', $name, $spec, '$value');
257     my $code = "do { my (\$self, \$value) = \@_;\n";
258     if ($coerce) {
259       $code .=
260         "        \$value = "
261         .$self->_generate_coerce($name, '$value', $coerce).";\n";
262     }
263     if ($isa_check) {
264       $code .= 
265         "        ".$self->_generate_isa_check($name, '$value', $isa_check).";\n";
266     }
267     if ($trigger) {
268       my $fire = $self->_generate_trigger($name, '$self', '$value', $trigger);
269       $code .=
270         "        ".$simple.";\n        ".$fire.";\n"
271         ."        \$value;\n";
272     } else {
273       $code .= "        ".$simple.";\n";
274     }
275     $code .= "      }";
276     $code;
277   }
278 }
279
280 sub generate_coerce {
281   my $self = shift;
282   $self->{captures} = {};
283   my $code = $self->_generate_coerce(@_);
284   ($code, delete $self->{captures});
285 }
286
287 sub _generate_coerce {
288   my ($self, $name, $value, $coerce) = @_;
289   $self->_generate_die_prefix(
290     "coercion for ${\perlstring($name)} failed: ",
291     $self->_generate_call_code($name, 'coerce', "${value}", $coerce)
292   );
293 }
294  
295 sub generate_trigger {
296   my $self = shift;
297   $self->{captures} = {};
298   my $code = $self->_generate_trigger(@_);
299   ($code, delete $self->{captures});
300 }
301
302 sub _generate_trigger {
303   my ($self, $name, $obj, $value, $trigger) = @_;
304   $self->_generate_call_code($name, 'trigger', "${obj}, ${value}", $trigger);
305 }
306
307 sub generate_isa_check {
308   my ($self, @args) = @_;
309   $self->{captures} = {};
310   my $code = $self->_generate_isa_check(@args);
311   ($code, delete $self->{captures});
312 }
313
314 sub _generate_die_prefix {
315   my ($self, $prefix, $inside) = @_;
316   "do {\n"
317   .'  my $sig_die = $SIG{__DIE__} || sub { die $_[0] };'."\n"
318   .'  local $SIG{__DIE__} = sub {'."\n"
319   .'    $sig_die->(ref($_[0]) ? $_[0] : '.perlstring($prefix).'.$_[0]);'."\n"
320   .'  };'."\n"
321   .$inside
322   ."}\n"
323 }
324
325 sub _generate_isa_check {
326   my ($self, $name, $value, $check) = @_;
327   $self->_generate_die_prefix(
328     "isa check for ${\perlstring($name)} failed: ",
329     $self->_generate_call_code($name, 'isa_check', $value, $check)
330   );
331 }
332
333 sub _generate_call_code {
334   my ($self, $name, $type, $values, $sub) = @_;
335   if (my $quoted = quoted_from_sub($sub)) {
336     my $code = $quoted->[1];
337     if (my $captures = $quoted->[2]) {
338       my $cap_name = qq{\$${type}_captures_for_${name}};
339       $self->{captures}->{$cap_name} = \$captures;
340       Sub::Quote::inlinify(
341         $code, $values, Sub::Quote::capture_unroll($cap_name, $captures, 6), 1
342       );
343     } else {
344       Sub::Quote::inlinify($code, $values, undef, 1);
345     }
346   } else {
347     my $cap_name = qq{\$${type}_for_${name}};
348     $self->{captures}->{$cap_name} = \$sub;
349     "${cap_name}->(${values})";
350   }
351 }
352
353 sub generate_populate_set {
354   my $self = shift;
355   $self->{captures} = {};
356   my $code = $self->_generate_populate_set(@_);
357   ($code, delete $self->{captures});
358 }
359
360 sub _generate_populate_set {
361   my ($self, $me, $name, $spec, $source, $test) = @_;
362   if ($self->has_eager_default($name, $spec)) {
363     my $get_indent = ' ' x ($spec->{isa} ? 6 : 4);
364     my $get_default = $self->_generate_get_default(
365                         '$new', $_, $spec
366                       );
367     my $get_value = 
368       defined($spec->{init_arg})
369         ? "(\n${get_indent}  ${test}\n${get_indent}   ? ${source}\n${get_indent}   : "
370             .$get_default
371             ."\n${get_indent})"
372         : $get_default;
373     if ($spec->{coerce}) {
374       $get_value = $self->_generate_coerce(
375         $name, $get_value,
376         $spec->{coerce}
377       )
378     }
379     ($spec->{isa}
380       ? "    {\n      my \$value = ".$get_value.";\n      "
381         .$self->_generate_isa_check(
382           $name, '$value', $spec->{isa}
383         ).";\n"
384         .'      '.$self->_generate_simple_set($me, $name, $spec, '$value').";\n"
385         ."    }\n"
386       : '    '.$self->_generate_simple_set($me, $name, $spec, $get_value).";\n"
387     )
388     .($spec->{trigger}
389       ? '    '
390         .$self->_generate_trigger(
391           $name, $me, $self->_generate_simple_get($me, $name, $spec),
392           $spec->{trigger}
393         )." if ${test};\n"
394       : ''
395     );
396   } else {
397     "    if (${test}) {\n"
398       .($spec->{coerce}
399         ? "      $source = "
400           .$self->_generate_coerce(
401             $name, $source,
402             $spec->{coerce}
403           ).";\n"
404         : ""
405       )
406       .($spec->{isa}
407         ? "      "
408           .$self->_generate_isa_check(
409             $name, $source, $spec->{isa}
410           ).";\n"
411         : ""
412       )
413       ."      ".$self->_generate_simple_set($me, $name, $spec, $source).";\n"
414       .($spec->{trigger}
415         ? "      "
416           .$self->_generate_trigger(
417             $name, $me, $self->_generate_simple_get($me, $name, $spec),
418             $spec->{trigger}
419           ).";\n"
420         : ""
421       )
422       ."    }\n";
423   }
424 }
425
426 sub _generate_core_set {
427   my ($self, $me, $name, $spec, $value) = @_;
428   my $name_str = perlstring $name;
429   "${me}->{${name_str}} = ${value}";
430 }
431
432 sub _generate_simple_set {
433   my ($self, $me, $name, $spec, $value) = @_;
434   my $name_str = perlstring $name;
435
436   if ($spec->{weak_ref}) {
437     $value = '$preserve = '.$value;
438     my $simple = $self->_generate_core_set($me, $name, $spec, $value);
439     require Scalar::Util;
440
441     # Perl < 5.8.3 can't weaken refs to readonly vars
442     # (e.g. string constants). This *can* be solved by:
443     #
444     #Internals::SetReadWrite($foo);
445     #Scalar::Util::weaken ($foo);
446     #Internals::SetReadOnly($foo);
447     #
448     # but requires XS and is just too damn crazy
449     # so simply throw a better exception
450     my $weak_simple = "my \$preserve; Scalar::Util::weaken(${simple})";
451     Moo::_Utils::lt_5_8_3() ? <<"EOC" : $weak_simple;
452
453       my \$preserve;
454       eval { Scalar::Util::weaken($simple); 1 } or do {
455         if( \$@ =~ /Modification of a read-only value attempted/) {
456           require Carp;
457           Carp::croak( sprintf (
458             'Reference to readonly value in "%s" can not be weakened on Perl < 5.8.3',
459             $name_str,
460           ) );
461         } else {
462           die \$@;
463         }
464       };
465 EOC
466   } else {
467     $self->_generate_core_set($me, $name, $spec, $value);
468   }
469 }
470
471 sub _generate_getset {
472   my ($self, $name, $spec) = @_;
473   q{(@_ > 1}."\n      ? ".$self->_generate_set($name, $spec)
474     ."\n      : ".$self->_generate_get($name, $spec)."\n    )";
475 }
476
477 sub _generate_delegation {
478   my ($self, $asserter, $target, $args) = @_;
479   my $arg_string = do {
480     if (@$args) {
481       # I could, I reckon, linearise out non-refs here using perlstring
482       # plus something to check for numbers but I'm unsure if it's worth it
483       $self->{captures}{'@curries'} = $args;
484       '@curries, @_';
485     } else {
486       '@_';
487     }
488   };
489   "shift->${asserter}->${target}(${arg_string});";
490 }
491
492 sub _generate_xs {
493   my ($self, $type, $into, $name, $slot) = @_;
494   Class::XSAccessor->import(
495     class => $into,
496     $type => { $name => $slot },
497     replace => 1,
498   );
499   $into->can($name);
500 }
501
502 sub default_construction_string { '{}' }
503
504 1;