useful and detailed errors for default checker in attrib generation
[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 BEGIN {
10   our $CAN_HAZ_XS =
11     !$ENV{MOO_XS_DISABLE}
12       &&
13     _maybe_load_module('Class::XSAccessor')
14       &&
15     (eval { Class::XSAccessor->VERSION('1.07') })
16   ;
17 }
18
19 sub generate_method {
20   my ($self, $into, $name, $spec, $quote_opts) = @_;
21   $name =~ s/^\+//;
22   die "Must have an is" unless my $is = $spec->{is};
23   if ($is eq 'ro') {
24     $spec->{reader} = $name unless exists $spec->{reader};
25   } elsif ($is eq 'rw') {
26     $spec->{accessor} = $name unless exists $spec->{accessor};
27   } elsif ($is eq 'lazy') {
28     $spec->{reader} = $name unless exists $spec->{reader};
29     $spec->{lazy} = 1;
30     $spec->{builder} ||= '_build_'.$name unless $spec->{default};
31   } elsif ($is eq 'rwp') {
32     $spec->{reader} = $name unless exists $spec->{reader};
33     $spec->{writer} = "_set_${name}" unless exists $spec->{writer};
34   } elsif ($is ne 'bare') {
35     die "Unknown is ${is}";
36   }
37   $spec->{builder} = '_build_'.$name if ($spec->{builder}||0) eq 1;
38   if (($spec->{predicate}||0) eq 1) {
39     $spec->{predicate} = $name =~ /^_/ ? "_has${name}" : "has_${name}";
40   }
41   if (($spec->{clearer}||0) eq 1) {
42     $spec->{clearer} = $name =~ /^_/ ? "_clear${name}" : "clear_${name}";
43   }
44   if (($spec->{trigger}||0) eq 1) {
45     $spec->{trigger} = quote_sub('shift->_trigger_'.$name.'(@_)');
46   }
47   if (exists $spec->{default}) {
48     my $default = $spec->{default};
49     my $invalid = "Invalid default '" . overload::StrVal($default)
50       . "' for $into->$name - not a coderef";
51     die "$invalid or code-convertible object"
52       unless ref $default and (ref $default eq 'CODE' or blessed($default));
53     die "$invalid and could not be converted to a coderef: $@"
54       if !eval { \&$default };
55   }
56
57   my %methods;
58   if (my $reader = $spec->{reader}) {
59     if (our $CAN_HAZ_XS && $self->is_simple_get($name, $spec)) {
60       $methods{$reader} = $self->_generate_xs(
61         getters => $into, $reader, $name, $spec
62       );
63     } else {
64       $self->{captures} = {};
65       $methods{$reader} =
66         quote_sub "${into}::${reader}"
67           => '    die "'.$reader.' is a read-only accessor" if @_ > 1;'."\n"
68              .$self->_generate_get($name, $spec)
69           => delete $self->{captures}
70         ;
71     }
72   }
73   if (my $accessor = $spec->{accessor}) {
74     if (
75       our $CAN_HAZ_XS
76       && $self->is_simple_get($name, $spec)
77       && $self->is_simple_set($name, $spec)
78     ) {
79       $methods{$accessor} = $self->_generate_xs(
80         accessors => $into, $accessor, $name, $spec
81       );
82     } else {
83       $self->{captures} = {};
84       $methods{$accessor} =
85         quote_sub "${into}::${accessor}"
86           => $self->_generate_getset($name, $spec)
87           => delete $self->{captures}
88         ;
89     }
90   }
91   if (my $writer = $spec->{writer}) {
92     if (
93       our $CAN_HAZ_XS
94       && $self->is_simple_set($name, $spec)
95     ) {
96       $methods{$writer} = $self->_generate_xs(
97         setters => $into, $writer, $name, $spec
98       );
99     } else {
100       $self->{captures} = {};
101       $methods{$writer} =
102         quote_sub "${into}::${writer}"
103           => $self->_generate_set($name, $spec)
104           => delete $self->{captures}
105         ;
106     }
107   }
108   if (my $pred = $spec->{predicate}) {
109     $methods{$pred} =
110       quote_sub "${into}::${pred}" =>
111         '    '.$self->_generate_simple_has('$_[0]', $name, $spec)."\n"
112       ;
113   }
114   if (my $cl = $spec->{clearer}) {
115     $methods{$cl} =
116       quote_sub "${into}::${cl}" => 
117         $self->_generate_simple_clear('$_[0]', $name, $spec)."\n"
118       ;
119   }
120   if (my $hspec = $spec->{handles}) {
121     my $asserter = $spec->{asserter} ||= '_assert_'.$name;
122     my @specs = do {
123       if (ref($hspec) eq 'ARRAY') {
124         map [ $_ => $_ ], @$hspec;
125       } elsif (ref($hspec) eq 'HASH') {
126         map [ $_ => ref($hspec->{$_}) ? @{$hspec->{$_}} : $hspec->{$_} ],
127           keys %$hspec;
128       } elsif (!ref($hspec)) {
129         map [ $_ => $_ ], Role::Tiny->methods_provided_by($hspec);
130       } else {
131         die "You gave me a handles of ${hspec} and I have no idea why";
132       }
133     };
134     foreach my $spec (@specs) {
135       my ($proxy, $target, @args) = @$spec;
136       $self->{captures} = {};
137       $methods{$proxy} =
138         quote_sub "${into}::${proxy}" =>
139           $self->_generate_delegation($asserter, $target, \@args),
140           delete $self->{captures}
141         ;
142     }
143   }
144   if (my $asserter = $spec->{asserter}) {
145     $self->{captures} = {};
146     $methods{$asserter} =
147       quote_sub "${into}::${asserter}" =>
148         'do { '.$self->_generate_get($name, $spec).qq! }||die "Attempted to access '${name}' but it is not set"!,
149         delete $self->{captures}
150       ;
151   }
152   \%methods;
153 }
154
155 sub is_simple_attribute {
156   my ($self, $name, $spec) = @_;
157   # clearer doesn't have to be listed because it doesn't
158   # affect whether defined/exists makes a difference
159   !grep $spec->{$_},
160     qw(lazy default builder coerce isa trigger predicate weak_ref);
161 }
162
163 sub is_simple_get {
164   my ($self, $name, $spec) = @_;
165   !($spec->{lazy} and ($spec->{default} or $spec->{builder}));
166 }
167
168 sub is_simple_set {
169   my ($self, $name, $spec) = @_;
170   !grep $spec->{$_}, qw(coerce isa trigger weak_ref);
171 }
172
173 sub has_eager_default {
174   my ($self, $name, $spec) = @_;
175   (!$spec->{lazy} and ($spec->{default} or $spec->{builder}));
176 }
177
178 sub _generate_get {
179   my ($self, $name, $spec) = @_;
180   my $simple = $self->_generate_simple_get('$_[0]', $name, $spec);
181   if ($self->is_simple_get($name, $spec)) {
182     $simple;
183   } else {
184     'do { '.$self->_generate_use_default(
185       '$_[0]', $name, $spec,
186       $self->_generate_simple_has('$_[0]', $name, $spec),
187     ).'; '
188     .($spec->{isa}
189       ?($self->_generate_isa_check($name, $simple, $spec->{isa}).'; ')
190       :''
191     )
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_core_set {
406   my ($self, $me, $name, $spec, $value) = @_;
407   my $name_str = perlstring $name;
408   "${me}->{${name_str}} = ${value}";
409 }
410
411 sub _generate_simple_set {
412   my ($self, $me, $name, $spec, $value) = @_;
413   my $name_str = perlstring $name;
414
415   if ($spec->{weak_ref}) {
416     $value = '$preserve = '.$value;
417     my $simple = $self->_generate_core_set($me, $name, $spec, $value);
418     require Scalar::Util;
419
420     # Perl < 5.8.3 can't weaken refs to readonly vars
421     # (e.g. string constants). This *can* be solved by:
422     #
423     #Internals::SetReadWrite($foo);
424     #Scalar::Util::weaken ($foo);
425     #Internals::SetReadOnly($foo);
426     #
427     # but requires XS and is just too damn crazy
428     # so simply throw a better exception
429     my $weak_simple = "my \$preserve; Scalar::Util::weaken(${simple})";
430     Moo::_Utils::lt_5_8_3() ? <<"EOC" : $weak_simple;
431
432       my \$preserve;
433       eval { Scalar::Util::weaken($simple); 1 } or do {
434         if( \$@ =~ /Modification of a read-only value attempted/) {
435           require Carp;
436           Carp::croak( sprintf (
437             'Reference to readonly value in "%s" can not be weakened on Perl < 5.8.3',
438             $name_str,
439           ) );
440         } else {
441           die \$@;
442         }
443       };
444 EOC
445   } else {
446     $self->_generate_core_set($me, $name, $spec, $value);
447   }
448 }
449
450 sub _generate_getset {
451   my ($self, $name, $spec) = @_;
452   q{(@_ > 1}."\n      ? ".$self->_generate_set($name, $spec)
453     ."\n      : ".$self->_generate_get($name, $spec)."\n    )";
454 }
455
456 sub _generate_delegation {
457   my ($self, $asserter, $target, $args) = @_;
458   my $arg_string = do {
459     if (@$args) {
460       # I could, I reckon, linearise out non-refs here using perlstring
461       # plus something to check for numbers but I'm unsure if it's worth it
462       $self->{captures}{'@curries'} = $args;
463       '@curries, @_';
464     } else {
465       '@_';
466     }
467   };
468   "shift->${asserter}->${target}(${arg_string});";
469 }
470
471 sub _generate_xs {
472   my ($self, $type, $into, $name, $slot) = @_;
473   Class::XSAccessor->import(
474     class => $into,
475     $type => { $name => $slot },
476     replace => 1,
477   );
478   $into->can($name);
479 }
480
481 sub default_construction_string { '{}' }
482
483 1;