File Coverage

blib/lib/Moxie/Traits/Provider/Experimental.pm
Criterion Covered Total %
statement 82 90 91.1
branch 13 22 59.0
condition 9 21 42.8
subroutine 19 19 100.0
pod 0 3 0.0
total 123 155 79.3


line stmt bran cond sub pod time code
1             package Moxie::Traits::Provider::Experimental;
2             # ABSTRACT: built in traits
3              
4 25     25   14190 use v5.22;
  25         83  
5 25     25   671 use warnings;
  25         66  
  25         951  
6 25         125 use experimental qw[
7             signatures
8             postderef
9 25     25   141 ];
  25         39  
10              
11 25     25   3157 use Method::Traits ':for_providers';
  25         48  
  25         144  
12              
13 25     25   7233 use Carp ();
  25         52  
  25         467  
14 25     25   6119 use Sub::Util (); # for setting the prototype of the lexical accessors
  25         6261  
  25         581  
15 25     25   6264 use PadWalker (); # for generating lexical accessors
  25         12847  
  25         619  
16 25     25   145 use MOP::Util ();
  25         46  
  25         8052  
17              
18             our $VERSION = '0.07';
19             our $AUTHORITY = 'cpan:STEVAN';
20              
21 3     3 0 4 sub lazy ( $meta, $method, @args ) : OverwritesMethod {
  3         1713  
  3         4  
  3         6  
  3         4  
22              
23 3         7 my $method_name = $method->name;
24              
25 3         46 my $slot_name;
26 3 100       9 if ( $args[0] ) {
27 1         2 $slot_name = shift @args;
28             }
29             else {
30 2 50       3 if ( $method_name =~ /^build_(.*)$/ ) {
31 0         0 $slot_name = $1;
32             }
33             else {
34 2         4 $slot_name = $method_name;
35             }
36             }
37              
38 3 50       6 Carp::confess('Unable to build `lazy` accessor for slot `' . $slot_name.'` in `'.$meta->name.'` because class is immutable.')
39             if ($meta->name)->isa('Moxie::Object::Immutable');
40              
41 3 50 33     50 Carp::confess('Unable to build `lazy` accessor for slot `' . $slot_name.'` in `'.$meta->name.'` because the slot cannot be found.')
42             unless $meta->has_slot( $slot_name )
43             || $meta->has_slot_alias( $slot_name );
44              
45              
46             # NOTE:
47             # lazy is read-only by design, if you want
48             # a rw+lazy, write it yourself
49             # - SL
50              
51 3         361 my $orig = $meta->get_method( $method_name )->body;
52              
53             $meta->add_method( $method_name => sub {
54 17   100 17   2067 $_[0]->{ $slot_name } //= $orig->( @_ );
        12      
55 3         367 });
56 25     25   166 }
  25         41  
  25         155  
57              
58              
59 2     2 0 3 sub handles ( $meta, $method, @args ) : OverwritesMethod {
  2         1147  
  2         6  
  2         2  
  2         4  
60              
61 2         6 my $method_name = $method->name;
62              
63 2         33 my ($slot_name, $delegate) = ($args[0] =~ /^(.*)\-\>(.*)$/);
64              
65 2 50 33     19 Carp::confess('Delegation spec must be in the pattern `slot->method`, not '.$args[0])
66             unless $slot_name && $delegate;
67              
68 2 50 33     10 Carp::confess('Unable to build delegation method for slot `' . $slot_name.'` in `'.$meta->name.'` because the slot cannot be found.')
69             unless $meta->has_slot( $slot_name )
70             || $meta->has_slot_alias( $slot_name );
71              
72             $meta->add_method( $method_name => sub {
73 4     4   1982 $_[0]->{ $slot_name }->$delegate( @_[ 1 .. $#_ ] );
        21      
74 2         281 });
75 25     25   15010 }
  25         58  
  25         90  
76              
77 36     36 0 9143 sub private ( $meta, $method, @args ) {
  36         63  
  36         56  
  36         57  
  36         54  
78              
79 36         98 my $method_name = $method->name;
80              
81 36         668 my $slot_name;
82 36 50       97 if ( $args[0] ) {
83 0         0 $slot_name = shift @args;
84             }
85             else {
86 36         64 $slot_name = $method_name;
87             }
88              
89 36 50 33     124 Carp::confess('Unable to build private accessor for slot `' . $slot_name.'` in `'.$meta->name.'` because the slot cannot be found.')
90             unless $meta->has_slot( $slot_name )
91             || $meta->has_slot_alias( $slot_name );
92              
93             # NOTE:
94             # These are lexical accessors ...
95              
96             # we should not be able to find it in the symbol table ...
97 36 50 33     4987 if ( $meta->has_method( $method_name ) || $meta->has_method_alias( $method_name ) || $meta->requires_method( $method_name ) ) {
      33        
98 0         0 Carp::confess('Unable to install private (lexical) accessor for slot('.$slot_name.') named ('
99             .$method_name.') because we found a conflicting non-lexical method of that name. '
100             .'Private methods must be defined before any public methods of the same name.');
101             }
102             else {
103             # set the prototype here so that the compiler sees
104             # this as early as possible ...
105 36         2069 Sub::Util::set_prototype( '', $method->body );
106              
107             # at this point we can assume that we have a lexical
108             # method which we need to transform, and in order to
109             # do that we need to look at all the methods in this
110             # class and find all the ones who 'close over' the
111             # lexical method and then re-write their lexical pad
112             # to use the accessor method that I will generate.
113              
114             # NOTE:
115             # we need to delay this until the UNITCHECK phase
116             # because we need all the methods of this class to
117             # have been compiled, at this moment, they are not.
118             MOP::Util::defer_until_UNITCHECK(sub {
119              
120             # now see if this class is immutable or not, it will
121             # determine the type of accessor we generate ...
122 36     36   807 my $class_is_immutable = ($meta->name)->isa('Moxie::Object::Immutable');
123              
124             # now check the class local methods ....
125 36         596 foreach my $m ( $meta->methods ) {
126             # get a HASH of the things the method closes over
127 159         22006 my $closed_over = PadWalker::closed_over( $m->body );
128              
129             #warn Data::Dumper::Dumper({
130             # class => $meta->name,
131             # method => $m->name,
132             # closed_over => $closed_over,
133             # looking_for => $method_name,
134             #});
135              
136             # XXX:
137             # Consider using something like Text::Levenshtein
138             # to check for typos in the accessor usage.
139             # - SL
140              
141             # if the private method is used, then it will be
142             # here with a prepended `&` sigil ...
143 159 100       1079 if ( exists $closed_over->{ '&' . $method_name } ) {
144             # now we know that we have someone using the
145             # lexical method inside the method body, so
146             # we need to generate our accessor accordingly
147              
148             # XXX:
149             # The DB::args stuff below is fragile because it
150             # is susceptible to alteration of @_ in the
151             # method that calls these accessors. Perhaps this
152             # can be fixed with XS, but for now we are going
153             # to assume people aren't doing this since they
154             # *should* be using the signatures that we enable
155             # for them.
156             # - SL
157              
158 62         92 my $accessor;
159 62 50       128 if ( $class_is_immutable ) {
160             # NOTE:
161             # if the class is immutable, perl will sometimes
162             # complain about accessing a read-only value in
163             # a way it is not comfortable, and this can be
164             # annoying. However, since we actually told perl
165             # that we want to be immutable, there actually is
166             # no need to generate the lvalue accessor when
167             # we can make a read-only one.
168             # - SL
169             $accessor = sub {
170 0         0 package DB; @DB::args = (); my () = caller(1);
  0         0  
171 0         0 my ($self) = @DB::args;
172 0         0 $self->{ $slot_name };
173 0         0 };
174             }
175             else {
176             $accessor = sub : lvalue {
177 250     250   29653 package DB; @DB::args = (); my () = caller(1);
  250         1208  
178 250         533 my ($self) = @DB::args;
179 250         806 $self->{ $slot_name };
180 62         241 };
181             }
182              
183             # then this is as simple as assigning the HASH key
184 62         129 $closed_over->{ '&' . $method_name } = $accessor;
185              
186             # okay, now restore the closed over vars
187             # with our new addition...
188 62         127 PadWalker::set_closed_over( $m->body, $closed_over );
189             }
190             }
191 36         400 });
192             }
193              
194             }
195              
196             1;
197              
198             __END__