File Coverage

blib/lib/MOP/Internal/Util.pm
Criterion Covered Total %
statement 180 193 93.2
branch 57 106 53.7
condition 22 50 44.0
subroutine 35 35 100.0
pod 0 19 0.0
total 294 403 72.9


line stmt bran cond sub pod time code
1             package MOP::Internal::Util;
2             # ABSTRACT: For MOP Internal Use Only
3              
4 35     35   241 use strict;
  35         81  
  35         1078  
5 35     35   205 use warnings;
  35         76  
  35         1039  
6              
7 35     35   213 use B (); # nasty stuff, all nasty stuff
  35         163  
  35         594  
8 35     35   185 use Carp (); # errors and stuff
  35         69  
  35         599  
9 35     35   10817 use Sub::Name (); # handling some sub stuff
  35         17358  
  35         1134  
10 35     35   10586 use Sub::Metadata (); # handling other sub stuff
  35         38480  
  35         897  
11 35     35   10381 use Symbol (); # creating the occasional symbol
  35         26977  
  35         871  
12 35     35   254 use Scalar::Util (); # I think I use blessed somewhere in here ...
  35         363  
  35         638  
13 35     35   11196 use Devel::OverloadInfo (); # Sometimes I need to know about overloading
  35         433559  
  35         1063  
14 35     35   10981 use Devel::Hook (); # for scheduling UNITCHECK blocks ...
  35         33751  
  35         19849  
15              
16             our $VERSION = '0.12';
17             our $AUTHORITY = 'cpan:STEVAN';
18              
19             ## ------------------------------------------------------------------
20             ## Basic Glob access
21             ## ------------------------------------------------------------------
22              
23             sub IS_VALID_MODULE_NAME {
24 112     112 0 242 my ($name) = @_;
25 112         828 $name =~ /[A-Z_a-z][0-9A-Z_a-z]*(?:::[0-9A-Z_a-z]+)*/
26             }
27              
28             sub IS_STASH_REF {
29 4     4 0 9 my ($stash) = @_;
30 4 50       11 Carp::confess('[ARGS] You must specify a stash')
31             unless defined $stash;
32 4 100       33 if ( my $name = B::svref_2object( $stash )->NAME ) {
33 2         6 return IS_VALID_MODULE_NAME( $name );
34             }
35 2         8 return;
36             }
37              
38             sub GET_NAME {
39 1117     1117 0 1967 my ($stash) = @_;
40 1117 50       2495 Carp::confess('[ARGS] You must specify a stash')
41             unless defined $stash;
42 1117         5732 B::svref_2object( $stash )->NAME
43             }
44              
45             sub GET_STASH_NAME {
46 81     81 0 146 my ($stash) = @_;
47 81 50       189 Carp::confess('[ARGS] You must specify a stash')
48             unless defined $stash;
49 81         1249 B::svref_2object( $stash )->STASH->NAME
50             }
51              
52             sub GET_GLOB_NAME {
53 546     546 0 895 my ($stash) = @_;
54 546 50       1083 Carp::confess('[ARGS] You must specify a stash')
55             unless defined $stash;
56 546         2678 B::svref_2object( $stash )->GV->NAME
57             }
58              
59             sub GET_GLOB_STASH_NAME {
60 605     605 0 1011 my ($stash) = @_;
61 605 50       1233 Carp::confess('[ARGS] You must specify a stash')
62             unless defined $stash;
63 605         4346 B::svref_2object( $stash )->GV->STASH->NAME
64             }
65              
66             sub GET_GLOB_SLOT {
67 1591     1591 0 3551 my ($stash, $name, $slot) = @_;
68              
69 1591 50       3322 Carp::confess('[ARGS] You must specify a stash')
70             unless defined $stash;
71 1591 50       2771 Carp::confess('[ARGS] You must specify a name')
72             unless defined $name;
73 1591 50       2700 Carp::confess('[ARGS] You must specify a slot')
74             unless defined $slot;
75              
76             # do my best to not autovivify, and
77             # return undef if not
78 1591 100       3320 return unless exists $stash->{ $name };
79             # occasionally we need to auto-inflate
80             # the optimized version of a required
81             # method, its annoying, but the XS side
82             # should not have to care about this so
83             # it can be removed eventually.
84 1441 50 100     9041 if (( $slot eq 'CODE' && $stash->{ $name } eq "-1" ) || ref $stash->{ $name } ne 'GLOB') {
      66        
85 1441         7991 B::svref_2object( $stash )->NAME->can( $name );
86             }
87              
88              
89             # return the reference stored in the glob
90             # which might be undef, but that can be
91             # handled by the caller
92 1441         3539 return *{ $stash->{ $name } }{ $slot };
  1441         7492  
93             }
94              
95             sub SET_GLOB_SLOT {
96 4     4 0 14 my ($stash, $name, $value_ref) = @_;
97              
98 4 50       18 Carp::confess('[ARGS] You must specify a stash')
99             unless defined $stash;
100 4 50       22 Carp::confess('[ARGS] You must specify a name')
101             unless defined $name;
102 4 50       17 Carp::confess('[ARGS] You must specify a value REF')
103             unless defined $value_ref;
104              
105             {
106 35     35   315 no strict 'refs';
  35         108  
  35         1375  
  4         11  
107 35     35   250 no warnings 'once';
  35         87  
  35         22651  
108             # get the name of the stash, we could have
109             # passed this in, but it is easy to get in
110             # XS, and so we can punt that down the road
111             # for the time being
112 4         32 my $pkg = B::svref_2object( $stash )->NAME;
113 4         14 *{ $pkg . '::' . $name } = $value_ref;
  4         40  
114             }
115 4         14 return;
116             }
117              
118             ## ------------------------------------------------------------------
119             ## UNITCHECK hook
120             ## ------------------------------------------------------------------
121              
122             sub ADD_UNITCHECK_HOOK {
123 5     5 0 12 my ($cv) = @_;
124 5 50       18 Carp::confess('[ARGS] You must specify a CODE reference')
125             unless $cv;
126 5 50 33     38 Carp::confess('[ARGS] You must specify a CODE reference')
127             unless $cv && ref $cv eq 'CODE';
128 5         30 Devel::Hook->push_UNITCHECK_hook( $cv );
129             }
130              
131             ## ------------------------------------------------------------------
132             ## CV/Glob introspection
133             ## ------------------------------------------------------------------
134              
135             sub CAN_COERCE_TO_CODE_REF {
136 109     109 0 182 my ($object) = @_;
137 109 100 66     898 return 0 unless $object && Scalar::Util::blessed( $object );
138             # might be just a blessed CODE ref ...
139 26 50       105 return 1 if Scalar::Util::reftype( $object ) eq 'CODE';
140             # or might be overloaded object ...
141 0 0       0 return 0 unless Devel::OverloadInfo::is_overloaded( $object );
142 0         0 return exists Devel::OverloadInfo::overload_info( $object )->{'&{}'};
143             }
144              
145             sub IS_CV_NULL {
146 926     926 0 1561 my ($cv) = @_;
147 926 50       1795 Carp::confess('[ARGS] You must specify a CODE reference')
148             unless $cv;
149 926 50 33     3688 Carp::confess('[ARGS] You must specify a CODE reference')
      33        
150             unless $cv && ref $cv eq 'CODE'
151             || CAN_COERCE_TO_CODE_REF( $cv );
152 926         3559 return Sub::Metadata::sub_body_type( $cv ) eq 'UNDEF';
153             }
154              
155             sub DOES_GLOB_HAVE_NULL_CV {
156 160     160 0 558 my ($glob) = @_;
157 160 50       686 Carp::confess('[ARGS] You must specify a GLOB')
158             unless $glob;
159             # NOTE:
160             # If the glob eq -1 that means it may well be a null sub
161             # this seems to be some kind of artifact of an optimization
162             # perhaps, I really don't know, it is odd. It should not
163             # need to be dealt with in XS, it seems to be a Perl language
164             # level thing.
165             # - SL
166 160 100       617 return 1 if $glob eq '-1';
167             # next lets see if we have a CODE slot ...
168 159 100       311 if ( my $code = *{ $glob }{CODE} ) {
  159         691  
169 136         1166 return Sub::Metadata::sub_body_type( $code ) eq 'UNDEF';
170             }
171             # if we had no CODE slot, it can't be a NULL CV ...
172 23         94 return 0;
173             }
174              
175             sub CREATE_NULL_CV {
176 3     3 0 10 my ($in_pkg, $name) = @_;
177 3 50       10 Carp::confess('[ARGS] You must specify a package name')
178             unless defined $in_pkg;
179 3 50       11 Carp::confess('[ARGS] You must specify a name')
180             unless defined $name;
181             # this just tries to eval the NULL CV into
182             # place, it is ugly, but works for now
183 3 100       177 eval "sub ${in_pkg}::${name}; 1;" or do { Carp::confess($@) };
  1         260  
184 2         9 return;
185             }
186              
187             sub SET_COMP_STASH_FOR_CV {
188 3     3 0 7 my ($cv, $in_pkg) = @_;
189 3 50       9 Carp::confess('[ARGS] You must specify a CODE reference')
190             unless $cv;
191 3 50       9 Carp::confess('[ARGS] You must specify a package name')
192             unless defined $in_pkg;
193 3 50 66     20 Carp::confess('[ARGS] You must specify a CODE reference')
      66        
194             unless $cv && ref $cv eq 'CODE'
195             || CAN_COERCE_TO_CODE_REF( $cv );
196 3         18 Sub::Metadata::mutate_sub_package( $cv, $in_pkg );
197             }
198              
199             sub INSTALL_CV {
200 421     421 0 1309 my ($in_pkg, $name, $cv, %opts) = @_;
201              
202 421 50       892 Carp::confess('[ARGS] You must specify a package name')
203             unless defined $in_pkg;
204 421 50       828 Carp::confess('[ARGS] You must specify a name')
205             unless defined $name;
206 421 50 33     1609 Carp::confess('[ARGS] You must specify a CODE reference')
      33        
207             unless $cv && ref $cv eq 'CODE'
208             || CAN_COERCE_TO_CODE_REF( $cv );
209             Carp::confess("[ARGS] You must specify a boolean value for `set_subname` option")
210 421 50       1014 if not exists $opts{set_subname};
211              
212             {
213 35     35   281 no strict 'refs';
  35         87  
  35         1316  
  421         644  
214 35     35   233 no warnings 'once', 'redefine';
  35         95  
  35         7120  
215              
216 421         971 my $fullname = $in_pkg.'::'.$name;
217 421 100       857 *{$fullname} = $opts{set_subname} ? Sub::Name::subname($fullname, $cv) : $cv;
  421         2306  
218             }
219 421         1289 return;
220             }
221              
222             sub REMOVE_CV_FROM_GLOB {
223 3     3 0 7 my ($stash, $name) = @_;
224              
225 3 50 33     20 Carp::confess('[ARGS] You must specify a stash')
226             unless $stash && ref $stash eq 'HASH';
227 3 50       9 Carp::confess('[ARGS] You must specify a name')
228             unless defined $name;
229              
230             # find the glob we are looking for
231             # which might not exist, in which
232             # case we do nothing ....
233 3 50       12 if ( my $glob = $stash->{ $name } ) {
234             # once we find it, extract all the
235             # slots we need, note the missing
236             # CODE slot since we don't need
237             # that in our new glob ...
238 3         5 my %to_save;
239 3         8 foreach my $slot (qw[ SCALAR ARRAY HASH FORMAT IO ]) {
240 15 100       24 if ( my $val = *{ $glob }{ $slot } ) {
  15         45  
241 3         9 $to_save{ $slot } = $val;
242             }
243             }
244             # replace the old glob with a new one ...
245 3         16 $stash->{ $name } = Symbol::gensym();
246             # now go about constructing our new
247             # glob by restoring the other slots
248             {
249 35     35   278 no strict 'refs';
  35         102  
  35         1262  
  3         59  
250 35     35   252 no warnings 'once';
  35         87  
  35         33144  
251             # get the name of the stash, we could have
252             # passed this in, but it is easy to get in
253             # XS, and so we can punt that down the road
254             # for the time being
255 3         15 my $pkg = B::svref_2object( $stash )->NAME;
256 3         13 foreach my $type ( keys %to_save ) {
257 3         8 *{ $pkg . '::' . $name } = $to_save{ $type };
  3         13  
258             }
259             }
260             }
261             # ... the end
262 3         11 return;
263             }
264              
265             ## ------------------------------------------------------------------
266             ## Role application and composition
267             ## ------------------------------------------------------------------
268              
269             sub APPLY_ROLES {
270 16     16 0 437 my ($meta, $roles) = @_;
271              
272 16 50       106 Carp::confess('[ARGS] You must specify a metaclass to apply roles to')
273             unless Scalar::Util::blessed( $meta );
274 16 50 33     171 Carp::confess('[ARGS] You must specify a least one roles to apply as an ARRAY ref')
      33        
275             unless $roles && ref $roles eq 'ARRAY' && scalar( @$roles ) != 0;
276              
277 16         177 foreach my $r ( $meta->roles ) {
278             Carp::confess("[ERROR] Could not find role ($_) in the set of roles in $meta (" . $meta->name . ")")
279 18 50       45 unless scalar grep { $r eq $_ } @$roles;
  22         118  
280             }
281              
282 16         56 my @meta_roles = map { MOP::Role->new( name => $_ ) } @$roles;
  18         150  
283              
284             my (
285 16         344 $slots,
286             $slot_conflicts
287             ) = COMPOSE_ALL_ROLE_SLOTS( @meta_roles );
288              
289 16 50       83 Carp::confess("[CONFLICT] There should be no conflicting slots when composing (" . (join ', ' => @$roles) . ") into (" . $meta->name . ")")
290             if scalar keys %$slot_conflicts;
291              
292 16         55 foreach my $name ( keys %$slots ) {
293             # if we have a slot already by that name ...
294 0 0       0 Carp::confess("[CONFLICT] Role Conflict, cannot compose slot ($name) into (" . $meta->name . ") because ($name) already exists")
295             if $meta->has_slot( $name );
296             # otherwise alias it ...
297 0         0 $meta->alias_slot( $name, $slots->{ $name } );
298             }
299              
300             my (
301 16         61 $methods,
302             $method_conflicts,
303             $required_methods
304             ) = COMPOSE_ALL_ROLE_METHODS( @meta_roles );
305              
306             Carp::confess("[CONFLICT] There should be no conflicting methods when composing (" . (join ', ' => @$roles) . ") into (" . $meta->name . ") but instead we found (" . (join ', ' => keys %$method_conflicts) . ")")
307             if (scalar keys %$method_conflicts) # do we have any conflicts ...
308             # and the conflicts are not satisfied by the composing item ...
309 16 0 50     95 && (scalar grep { !$meta->has_method( $_ ) } keys %$method_conflicts);
  0         0  
310              
311             # check the required method set and
312             # see if what we are composing into
313             # happens to fulfill them
314 16         57 foreach my $name ( keys %$required_methods ) {
315 3 50       8 delete $required_methods->{ $name }
316             if $meta->name->can( $name );
317             }
318              
319 16 50       70 Carp::confess("[CONFLICT] There should be no required methods when composing (" . (join ', ' => @$roles) . ") into (" . $meta->name . ") but instead we found (" . (join ', ' => keys %$required_methods) . ")")
320             if scalar keys %$required_methods; # do we have required methods ...
321              
322 16         120 foreach my $name ( keys %$methods ) {
323             # if we have a method already by that name ...
324 419 50       1030 next if $meta->has_method( $name );
325             # otherwise, alias it ...
326 419         1101 $meta->alias_method( $name, $methods->{ $name } );
327             }
328              
329             # if we still have keys in $required, it is
330             # because we are a role (class would have
331             # died above), so we can just stuff in the
332             # required methods ...
333 16         105 $meta->add_required_method( $_ ) for keys %$required_methods;
334              
335 16         190 return;
336             }
337              
338             sub COMPOSE_ALL_ROLE_SLOTS {
339 16     16 0 48 my @roles = @_;
340              
341 16 50       86 Carp::confess('[ARGS] You must specify a least one role to compose slots in')
342             if scalar( @roles ) == 0;
343              
344 16         40 my (%slots, %conflicts);
345              
346 16         45 foreach my $role ( @roles ) {
347 18         79 foreach my $slot ( $role->slots ) {
348 0         0 my $name = $slot->name;
349             # if we have one already, but
350             # it is not the same refaddr ...
351 0 0 0     0 if ( exists $slots{ $name } && $slots{ $name } != $slot->initializer ) {
352             # mark it as a conflict ...
353 0         0 $conflicts{ $name } = undef;
354             # and remove it from our slot set ...
355 0         0 delete $slots{ $name };
356             }
357             # if we don't have it already ...
358             else {
359             # make a note of it
360 0         0 $slots{ $name } = $slot->initializer;
361             }
362             }
363             }
364              
365 16         91 return \%slots, \%conflicts;
366             }
367              
368              
369             # TODO:
370             # We should track the name of the role
371             # where the required method was composed
372             # from, as well as the two classes in
373             # which a method conflicted.
374             # - SL
375             sub COMPOSE_ALL_ROLE_METHODS {
376 16     16 0 45 my @roles = @_;
377              
378 16 50       61 Carp::confess('[ARGS] You must specify a least one role to compose methods in')
379             if scalar( @roles ) == 0;
380              
381 16         37 my (%methods, %conflicts, %required);
382              
383             # flatten the set of required methods ...
384 16         38 foreach my $r ( @roles ) {
385 18         106 foreach my $m ( $r->required_methods ) {
386 3         9 $required{ $m->name } = undef;
387             }
388             }
389              
390             # for every role ...
391 16         1949 foreach my $r ( @roles ) {
392             # and every method in that role ...
393 18         92 foreach my $m ( $r->methods ) {
394 419         979 my $name = $m->name;
395             # if we have already seen the method,
396             # but it is not the same refaddr
397             # it is a conflict, which means:
398 419 50 33     1198 if ( exists $methods{ $name } && $methods{ $name } != $m->body ) {
399             # we need to add it to our required-method map
400 0         0 $required{ $name } = undef;
401             # and note that it is also a conflict ...
402 0         0 $conflicts{ $name } = undef;
403             # and remove it from our method map
404 0         0 delete $methods{ $name };
405             }
406             # if we haven't seen the method ...
407             else {
408             # add it to the method map
409 419         950 $methods{ $name } = $m->body;
410             # and remove it from the required-method map
411             delete $required{ $name }
412             # if it actually exists in it, and ...
413             if exists $required{ $name }
414             # is not also a conflict ...
415 419 50 33     1156 && !exists $conflicts{ $name };
416             }
417             }
418             }
419              
420             #use Data::Dumper;
421             #warn Dumper [ [ map { $_->name } @roles ], \%methods, \%conflicts, \%required ];
422              
423 16         2019 return \%methods, \%conflicts, \%required;
424             }
425              
426             1;
427              
428             __END__