File Coverage

blib/lib/autobox.pm
Criterion Covered Total %
statement 178 184 96.7
branch 60 72 83.3
condition 6 9 66.6
subroutine 25 26 96.1
pod n/a
total 269 291 92.4


line stmt bran cond sub pod time code
1             package autobox;
2              
3 21     21   1400832 use 5.008;
  21         237  
4              
5 21     21   133 use strict;
  21         48  
  21         720  
6 21     21   145 use warnings;
  21         60  
  21         804  
7              
8 21     21   138 use Carp;
  21         56  
  21         1580  
9 21     21   175 use XSLoader;
  21         52  
  21         661  
10 21     21   156 use Scalar::Util;
  21         48  
  21         995  
11 21     21   10365 use Scope::Guard;
  21         11002  
  21         1007  
12 21     21   13680 use Storable;
  21         77100  
  21         1542  
13              
14             # XXX this declaration must be on a single line
15             # https://metacpan.org/pod/version#How-to-declare()-a-dotted-decimal-version
16 21     21   10136 use version 0.77; our $VERSION = version->declare('v3.0.1');
  21         46476  
  21         213  
17              
18             XSLoader::load 'autobox', $VERSION;
19              
20 21     21   12064 use autobox::universal (); # don't import
  21         65  
  21         8033  
21              
22             ############################################# PRIVATE ###############################################
23              
24             my $SEQ = 0; # unique identifier for synthetic classes
25             my $BINDINGS_CACHE = {}; # hold a reference to the bindings hashes
26             my $CLASS_CACHE = {}; # reuse the same synthetic class if the @isa has been seen before
27              
28             # all supported types
29             # the boolean indicates whether the type is a real internal type (as opposed to a virtual type)
30             my %TYPES = (
31             UNDEF => 1,
32             INTEGER => 1,
33             FLOAT => 1,
34             NUMBER => 0,
35             STRING => 1,
36             SCALAR => 0,
37             ARRAY => 1,
38             HASH => 1,
39             CODE => 1,
40             UNIVERSAL => 0
41             );
42              
43             # type hierarchy: keys are parents, values are (depth, children) pairs
44             my %ISA = (
45             UNIVERSAL => [ 0, [ qw(SCALAR ARRAY HASH CODE) ] ],
46             SCALAR => [ 1, [ qw(STRING NUMBER) ] ],
47             NUMBER => [ 2, [ qw(INTEGER FLOAT) ] ]
48             );
49              
50             # default bindings when no args are supplied
51             my %DEFAULT = (
52             SCALAR => 'SCALAR',
53             ARRAY => 'ARRAY',
54             HASH => 'HASH',
55             CODE => 'CODE'
56             );
57              
58             # reinvent List::MoreUtils::uniq to keep the dependencies light - return a reference
59             # to an array containing (in order) the unique members of the supplied list
60             sub _uniq($) {
61 487     487   758 my $list = shift;
62 487         756 my (%seen, @uniq);
63              
64 487         863 for my $element (@$list) {
65 811 100       1821 next if ($seen{$element});
66 775         1437 push @uniq, $element;
67 775         1722 $seen{$element} = 1;
68             }
69              
70 487         1555 return [ @uniq ];
71             }
72              
73             # create a shim class - actual methods are implemented by the classes in its @ISA
74             #
75             # as an optimization, return the previously-generated class
76             # if we've seen the same (canonicalized) @isa before
77             sub _generate_class($) {
78 487     487   1000 my $isa = _uniq(shift);
79              
80             # As an optimization, simply return the class if there's only one.
81             # This speeds up method lookup as the method can (often) be found directly in the stash
82             # rather than in the ISA hierarchy with its attendant AUTOLOAD-related overhead
83 487 100       1233 if (@$isa == 1) {
84 351         588 my $class = $isa->[0];
85 351         784 _make_class_accessor($class); # NOP if it has already been added
86 351         1313 return $class;
87             }
88              
89 136         451 my $key = Storable::freeze($isa);
90              
91 136   66     6321 return $CLASS_CACHE->{$key} ||= do {
92 48         242 my $class = sprintf('autobox::_shim_%d_', ++$SEQ);
93 48         132 my $synthetic_class_isa = _get_isa($class); # i.e. autovivify
94              
95 48         1298 @$synthetic_class_isa = @$isa;
96 48         223 _make_class_accessor($class);
97 48         328 $class;
98             };
99             }
100              
101             # expose the autobox class (for can, isa &c.)
102             # https://rt.cpan.org/Ticket/Display.html?id=55565
103             sub _make_class_accessor ($) {
104 399     399   657 my $class = shift;
105 399 50       835 return unless (defined $class);
106              
107             {
108 21     21   171 no strict 'refs';
  21         65  
  21         5377  
  399         602  
109 399 100   148   537 *{"$class\::autobox_class"} = sub { $class } unless (*{"$class\::autobox_class"}{CODE});
  120         562  
  148         97015  
  399         2334  
110             }
111             }
112              
113             # pretty-print the bindings hash by showing its values as the inherited classes rather than the synthetic class
114             sub _pretty_print($) {
115 44     44   80 my $hash = { %{ shift() } }; # clone the hash to isolate it from the original
  44         251  
116              
117             # reverse() turns a hash that maps an isa signature to a class name into a hash that maps
118             # a class name into a boolean
119 44         301 my %synthetic = reverse(%$CLASS_CACHE);
120              
121 44         197 for my $type (keys %$hash) {
122 218         447 my $class = $hash->{$type};
123 218 100       641 $hash->{$type} = $synthetic{$class} ? [ _get_isa($class) ] : [ $class ];
124             }
125              
126 44         233 return $hash;
127             }
128              
129             # default sub called when the DEBUG option is supplied with a true value
130             # prints the assigned bindings for the current scope
131             sub _debug ($) {
132 0     0   0 my $bindings = shift;
133 0         0 require Data::Dumper;
134 21     21   174 no warnings qw(once);
  21         52  
  21         4753  
135 0         0 local ($|, $Data::Dumper::Indent, $Data::Dumper::Terse, $Data::Dumper::Sortkeys) = (1, 1, 1, 1);
136 0         0 print STDERR Data::Dumper::Dumper($bindings), $/;
137             }
138              
139             # return true if $ref ISA $class - works with non-references, unblessed references and objects
140             # we can't use UNIVERSAL::isa to test if a value is an array ref;
141             # if the value is 'ARRAY', and that package exists, then UNIVERSAL::isa('ARRAY', 'ARRAY') is true!
142             sub _isa($$) {
143 1092     1092   2118 my ($ref, $class) = @_;
144 1092 50       3925 return Scalar::Util::blessed($ref) ? $ref->isa($class) : ref($ref) eq $class;
145             }
146              
147             # get/autovivify the @ISA for the specified class
148             sub _get_isa($) {
149 239     239   453 my $class = shift;
150 239         376 my $isa = do {
151 21     21   162 no strict 'refs';
  21         51  
  21         28776  
152 239         368 *{"$class\::ISA"}{ARRAY};
  239         1195  
153             };
154 239 100       1087 return wantarray ? @$isa : $isa;
155             }
156              
157             # install a new set of bindings for the current scope
158             #
159             # XXX this could be refined to reuse the same hashref if its contents have already been seen,
160             # but that requires each (frozen) hash to be cached; at best, it may not be much of a win, and at
161             # worst it will increase bloat
162             sub _install ($) {
163 113     113   220 my $bindings = shift;
164 113         511 $^H{autobox} = $bindings;
165 113         1122 $BINDINGS_CACHE->{$bindings} = $bindings; # keep the $bindings hash alive
166             }
167              
168             # return the supplied class name or a new class name made by appending the specified
169             # type to the namespace prefix
170             sub _expand_namespace($$) {
171 1018     1018   1904 my ($class, $type) = @_;
172              
173             # make sure we can weed out classes that are empty strings or undef by returning an empty list
174 1018 50       2093 Carp::confess("_expand_namespace not called in list context") unless (wantarray);
175              
176 1018 100 66     3458 if ((defined $class) && ($class ne '')) {
177 1012 100       4186 ($class =~ /::$/) ? "$class$type" : $class;
178             } else { # return an empty list
179             ()
180 6         15 }
181             }
182              
183             ############################################# PUBLIC (Methods) ###############################################
184              
185             # enable some flavour of autoboxing in the current scope
186             sub import {
187 106     106   8198 my $class = shift;
188 106 100 66     626 my %args = ((@_ == 1) && _isa($_[0], 'HASH')) ? %{shift()} : @_; # hash or hashref
  3         19  
189 106         468 my $debug = delete $args{DEBUG};
190              
191 106 100       402 %args = %DEFAULT unless (%args); # wait till DEBUG has been deleted
192              
193             # normalize %args so that it has a (possibly empty) array ref for all types, both real and virtual
194 106         463 for my $type (keys %TYPES) {
195 1060 100       2055 if (exists $args{$type}) { # exists() as the value may be undef (or ''), meaning "don't default this type"
196 167 100       375 if (_isa($args{$type}, 'ARRAY')) {
197 10         22 $args{$type} = [ @{$args{$type}} ]; # clone the array ref to isolate changes
  10         37  
198             } else {
199 157         484 $args{$type} = [ $args{$type} ];
200             }
201             } else {
202 893         1872 $args{$type} = [];
203             }
204             }
205              
206             # if supplied, fill in defaults for unspecified SCALAR, ARRAY, HASH and CODE bindings
207             # must be done before the virtual type expansion below as one of the defaults, SCALAR, is a
208             # virtual type
209              
210 106         290 my $default = delete $args{DEFAULT};
211              
212 106 100       307 if ($default) {
213 30 100       82 $default = [ $default ] unless (_isa($default, 'ARRAY')); # no need to clone as we flatten it each time
214              
215 30         108 for my $type (keys %DEFAULT) {
216             # don't default if a binding has already been supplied; this may include an undef value meaning
217             # "don't default this type" e.g.
218             #
219             # use autobox
220             # DEFAULT => 'MyDefault',
221             # HASH => undef;
222             #
223             # undefs are winnowed out by _expand_namespace
224              
225 120 100       203 next if (@{$args{$type}});
  120         326  
226 100         188 push @{$args{$type}}, map { _expand_namespace($_, $type) } @$default;
  100         6877  
  108         229  
227             }
228             }
229              
230             # expand the virtual type "macros" from the root to the leaves
231 106         514 for my $vtype (sort { $ISA{$a}->[0] <=> $ISA{$b}->[0] } keys %ISA) {
  291         752  
232 318 50       783 next unless ($args{$vtype});
233              
234 318         483 my @types = @{$ISA{$vtype}->[1]};
  318         863  
235              
236 318         649 for my $type (@types) {
237 848 50       1717 if (_isa($args{$vtype}, 'ARRAY')) {
238 848         1282 push @{$args{$type}}, map { _expand_namespace($_, $vtype) } @{$args{$vtype}};
  848         1473  
  418         786  
  848         1781  
239             } else {
240             # _expand_namespace returns an empty list if $args{$vtype} is undef (or '')
241 0         0 push @{$args{$type}}, _expand_namespace($args{$vtype}, $vtype);
  0         0  
242             }
243             }
244              
245 318         856 delete $args{$vtype};
246             }
247              
248 106         210 my $bindings; # custom typemap
249              
250             # clone the bindings hash if available
251             #
252             # we may be assigning to it, and we don't want to contaminate outer/previous bindings
253             # with nested/new bindings
254             #
255             # as of 5.10, references in %^H get stringified at runtime, but we don't need them then
256              
257 106 100       338 $bindings = $^H{autobox} ? { %{ $^H{autobox} } } : {};
  31         185  
258              
259             # sanity check %args, expand the namespace prefixes into class names,
260             # and copy values to the $bindings hash
261              
262 106         499 my %synthetic = reverse(%$CLASS_CACHE); # synthetic class name => bool - see _pretty_print
263              
264 106         348 for my $type (keys %args) {
265             # we've handled the virtual types, so we only need to check that this is a valid (real) type
266 742 0       1719 Carp::confess("unrecognized option: '", (defined $type ? $type : ''), "'") unless ($TYPES{$type});
    50          
267              
268 742         1124 my (@isa, $class);
269              
270 742 100       1567 if ($class = $bindings->{$type}) {
271 142 100       410 @isa = $synthetic{$class} ? _get_isa($class) : ($class);
272             }
273              
274             # perform namespace expansion; dups are removed in _generate_class below
275 742         1083 push @isa, map { _expand_namespace($_, $type) } @{$args{$type}};
  492         940  
  742         1365  
276              
277 742         2273 $bindings->{$type} = [ @isa ]; # assign the (possibly) new @isa for this type
278             }
279              
280             # replace each array ref of classes with the name of the generated class.
281             # if there's only one class in the type's @ISA (e.g. SCALAR => 'MyScalar') then
282             # that class is used; otherwise a shim class whose @ISA contains the two or more classes
283             # is created
284              
285 106         473 for my $type (keys %$bindings) {
286 742         1378 my $isa = $bindings->{$type};
287              
288             # delete empty arrays e.g. use autobox SCALAR => []
289 742 100       1445 if (@$isa == 0) {
290 255         516 delete $bindings->{$type};
291             } else {
292             # associate the synthetic/single class with the specified type
293 487         1039 $bindings->{$type} = _generate_class($isa);
294             }
295             }
296              
297             # This turns on autoboxing i.e. the method call checker sets a flag on the method call op
298             # and replaces its default handler with the autobox implementation.
299             #
300             # It needs to be set unconditionally because it may have been unset in unimport
301              
302 106         422 $^H |= 0x80020000; # set HINT_LOCALIZE_HH + an unused bit to work around a %^H bug
303              
304             # install the specified bindings in the current scope
305 106         370 _install($bindings);
306              
307             # this is %^H as an integer - it changes as scopes are entered/exited
308             # we don't need to stack/unstack it in %^H as %^H itself takes care of that
309             # note: we need to call this *after* %^H is referenced (and possibly created) above
310              
311 106         359 my $scope = _scope();
312 106 100       337 my $old_scope = exists($^H{autobox_scope})? $^H{autobox_scope} : 0;
313 106         187 my $new_scope; # is this a new (top-level or nested) scope?
314              
315 106 100       251 if ($scope == $old_scope) {
316 25         49 $new_scope = 0;
317             } else {
318 81         333 $^H{autobox_scope} = $scope;
319 81         156 $new_scope = 1;
320             }
321              
322             # warn "OLD ($old_scope) => NEW ($scope): $new_scope ", join(':', (caller(1))[0 .. 2]), $/;
323              
324 106 100       290 if ($debug) {
325 44 50       106 $debug = \&_debug unless (_isa($debug, 'CODE'));
326 44         125 $debug->(_pretty_print($bindings));
327             }
328              
329 106 100       46617 return unless ($new_scope);
330              
331             # This sub is called when this scope's $^H{autobox_leave} is deleted, usually when
332             # %^H is destroyed at the end of the scope, but possibly directly in unimport()
333             #
334             # _enter splices in the autobox method call checker and method call op
335             # if they're not already enabled
336             #
337             # _leave performs the necessary housekeeping to ensure that the default
338             # checker and op are restored when autobox is no longer in scope
339              
340 81     81   584 my $guard = Scope::Guard->new(sub { _leave() });
  81         116771  
341 81         1446 $^H{autobox_leave} = $guard;
342              
343 81         11110 _enter();
344             }
345              
346             # delete one or more bindings; if none remain, disable autobox in the current scope
347             #
348             # note: if bindings remain, we need to create a new hash (initially a clone of the current
349             # hash) so that the previous hash (if any) is not contaminated by new deletion(s)
350             #
351             # use autobox;
352             #
353             # "foo"->bar;
354             #
355             # no autobox qw(SCALAR); # don't clobber the default bindings for "foo"->bar
356             #
357             # however, if there are no more bindings we can remove all traces of autobox from the
358             # current scope.
359              
360             sub unimport {
361 34     34   14923 my ($class, @args) = @_;
362              
363             # the only situation in which there is no bindings hash is if this is a "no autobox"
364             # that precedes any "use autobox", in which case we don't need to turn autoboxing off as it's
365             # not yet been turned on
366 34 100       650 return unless ($^H{autobox});
367              
368 20         44 my $bindings;
369              
370 20 100       64 if (@args) {
371 9         17 $bindings = { %{$^H{autobox}} }; # clone the current bindings hash
  9         54  
372 9         28 my %args = map { $_ => 1 } @args;
  10         38  
373              
374             # expand any virtual type "macros"
375 9         42 for my $vtype (sort { $ISA{$a}->[0] <=> $ISA{$b}->[0] } keys %ISA) {
  27         64  
376 27 100       63 next unless ($args{$vtype});
377              
378             # we could delete the types directly from $bindings here, but we may as well pipe them
379             # through the option checker below to ensure correctness
380 7         12 $args{$_} = 1 for (@{$ISA{$vtype}->[1]});
  7         27  
381              
382 7         18 delete $args{$vtype};
383             }
384              
385 9         26 for my $type (keys %args) {
386             # we've handled the virtual types, so we only need to check that this is a valid (real) type
387 17 0       47 Carp::confess("unrecognized option: '", (defined $type ? $type : ''), "'") unless ($TYPES{$type});
    50          
388 17         40 delete $bindings->{$type};
389             }
390             } else { # turn off autoboxing
391 11         43 $bindings = {}; # empty hash to trigger full deletion below
392             }
393              
394 20 100       66 if (%$bindings) {
395 7         20 _install($bindings);
396             } else { # remove all traces of autobox from the current scope
397 13         49 $^H &= ~0x80020000; # unset HINT_LOCALIZE_HH + the additional bit
398 13         55 delete $^H{autobox};
399 13         82 delete $^H{autobox_scope};
400 13         143 delete $^H{autobox_leave}; # triggers the leave handler
401             }
402             }
403              
404             1;