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 23     23   2311763 use 5.008;
  23         90  
4              
5 23     23   734 use strict;
  23         76  
  23         861  
6 23     23   136 use warnings;
  23         41  
  23         1502  
7              
8 23     23   143 use Carp;
  23         94  
  23         1946  
9 23     23   173 use XSLoader;
  23         45  
  23         595  
10 23     23   157 use Scalar::Util;
  23         45  
  23         1052  
11 23     23   11761 use Scope::Guard;
  23         12518  
  23         1376  
12 23     23   15612 use Storable;
  23         107757  
  23         2038  
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 23     23   11062 use version 0.77; our $VERSION = version->declare('v3.0.2');
  23         49314  
  23         194  
17              
18             XSLoader::load 'autobox', $VERSION;
19              
20 23     23   18298 use autobox::universal (); # don't import
  23         81  
  23         9641  
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 493     493   737 my $list = shift;
62 493         1270 my (%seen, @uniq);
63              
64 493         800 for my $element (@$list) {
65 817 100       1548 next if ($seen{$element});
66 781         1164 push @uniq, $element;
67 781         1306 $seen{$element} = 1;
68             }
69              
70 493         1321 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 493     493   1514 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 493 100       1031 if (@$isa == 1) {
84 357         532 my $class = $isa->[0];
85 357         887 _make_class_accessor($class); # NOP if it has already been added
86 357         1209 return $class;
87             }
88              
89 136         321 my $key = Storable::freeze($isa);
90              
91 136   66     5736 return $CLASS_CACHE->{$key} ||= do {
92 48         173 my $class = sprintf('autobox::_shim_%d_', ++$SEQ);
93 48         92 my $synthetic_class_isa = _get_isa($class); # i.e. autovivify
94              
95 48         2738 @$synthetic_class_isa = @$isa;
96 48         4101 _make_class_accessor($class);
97 48         258 $class;
98             };
99             }
100              
101             # expose the autobox class (for can, isa etc.)
102             # https://rt.cpan.org/Ticket/Display.html?id=55565
103             sub _make_class_accessor ($) {
104 405     405   587 my $class = shift;
105 405 50       1102 return unless (defined $class);
106              
107             {
108 23     23   211 no strict 'refs';
  23         55  
  23         6449  
  405         1173  
109 405 100   148   507 *{"$class\::autobox_class"} = sub { $class } unless (*{"$class\::autobox_class"}{CODE});
  121         433  
  148         1197795  
  405         2057  
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   50 my $hash = { %{ shift() } }; # clone the hash to isolate it from the original
  44         199  
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         265 my %synthetic = reverse(%$CLASS_CACHE);
120              
121 44         110 for my $type (keys %$hash) {
122 218         308 my $class = $hash->{$type};
123 218 100       518 $hash->{$type} = $synthetic{$class} ? [ _get_isa($class) ] : [ $class ];
124             }
125              
126 44         204 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 23     23   162 no warnings qw(once);
  23         51  
  23         5587  
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 1102     1102   2168 my ($ref, $class) = @_;
144 1102 50       3011 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   290 my $class = shift;
150 239         236 my $isa = do {
151 23     23   165 no strict 'refs';
  23         62  
  23         32622  
152 239         235 *{"$class\::ISA"}{ARRAY};
  239         813  
153             };
154 239 100       700 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 114     114   178 my $bindings = shift;
164 114         611 $^H{autobox} = $bindings;
165 114         1350 $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 1032     1032   1705 my ($class, $type) = @_;
172              
173             # make sure we can weed out classes that are empty strings or undef by returning an empty list
174 1032 50       1884 Carp::confess("_expand_namespace not called in list context") unless (wantarray);
175              
176 1032 100 66     3055 if ((defined $class) && ($class ne '')) {
177 1026 100       3648 ($class =~ /::$/) ? "$class$type" : $class;
178             } else { # return an empty list
179             ()
180 6         33 }
181             }
182              
183             ############################################# PUBLIC (Methods) ###############################################
184              
185             # enable some flavour of autoboxing in the current scope
186             sub import {
187 107     107   7444 my $class = shift;
188 107 100 66     651 my %args = ((@_ == 1) && _isa($_[0], 'HASH')) ? %{shift()} : @_; # hash or hashref
  4         18  
189 107         228 my $debug = delete $args{DEBUG};
190              
191 107 100       347 %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 107         447 for my $type (keys %TYPES) {
195 1070 100       1740 if (exists $args{$type}) { # exists() as the value may be undef (or ''), meaning "don't default this type"
196 167 100       402 if (_isa($args{$type}, 'ARRAY')) {
197 10         15 $args{$type} = [ @{$args{$type}} ]; # clone the array ref to isolate changes
  10         57  
198             } else {
199 157         443 $args{$type} = [ $args{$type} ];
200             }
201             } else {
202 903         1716 $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 107         271 my $default = delete $args{DEFAULT};
211              
212 107 100       299 if ($default) {
213 31 100       104 $default = [ $default ] unless (_isa($default, 'ARRAY')); # no need to clone as we flatten it each time
214              
215 31         94 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 124 100       166 next if (@{$args{$type}});
  124         287  
226 104         161 push @{$args{$type}}, map { _expand_namespace($_, $type) } @$default;
  104         203  
  112         213  
227             }
228             }
229              
230             # expand the virtual type "macros" from the root to the leaves
231 107         565 for my $vtype (sort { $ISA{$a}->[0] <=> $ISA{$b}->[0] } keys %ISA) {
  277         729  
232 321 50       685 next unless ($args{$vtype});
233              
234 321         464 my @types = @{$ISA{$vtype}->[1]};
  321         914  
235              
236 321         829 for my $type (@types) {
237 856 50       1540 if (_isa($args{$vtype}, 'ARRAY')) {
238 856         1086 push @{$args{$type}}, map { _expand_namespace($_, $vtype) } @{$args{$vtype}};
  856         1431  
  422         755  
  856         1653  
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 321         884 delete $args{$vtype};
246             }
247              
248 107         229 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 107 100       1641 $bindings = $^H{autobox} ? { %{ $^H{autobox} } } : {};
  31         145  
258              
259             # sanity check %args, expand the namespace prefixes into class names,
260             # and copy values to the $bindings hash
261              
262 107         593 my %synthetic = reverse(%$CLASS_CACHE); # synthetic class name => bool - see _pretty_print
263              
264 107         352 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 749 0       1508 Carp::confess("unrecognized option: '", (defined $type ? $type : ''), "'") unless ($TYPES{$type});
    50          
267              
268 749         1150 my (@isa, $class);
269              
270 749 100       1544 if ($class = $bindings->{$type}) {
271 142 100       273 @isa = $synthetic{$class} ? _get_isa($class) : ($class);
272             }
273              
274             # perform namespace expansion; dups are removed in _generate_class below
275 749         972 push @isa, map { _expand_namespace($_, $type) } @{$args{$type}};
  498         928  
  749         3080  
276              
277 749         2063 $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 107         379 for my $type (keys %$bindings) {
286 749         1150 my $isa = $bindings->{$type};
287              
288             # delete empty arrays e.g. use autobox SCALAR => []
289 749 100       1365 if (@$isa == 0) {
290 256         491 delete $bindings->{$type};
291             } else {
292             # associate the synthetic/single class with the specified type
293 493         880 $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 107         445 $^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 107         312 _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 107         342 my $scope = _scope();
312 107 100       310 my $old_scope = exists($^H{autobox_scope})? $^H{autobox_scope} : 0;
313 107         164 my $new_scope; # is this a new (top-level or nested) scope?
314              
315 107 100       251 if ($scope == $old_scope) {
316 25         36 $new_scope = 0;
317             } else {
318 82         310 $^H{autobox_scope} = $scope;
319 82         149 $new_scope = 1;
320             }
321              
322             # warn "OLD ($old_scope) => NEW ($scope): $new_scope ", join(':', (caller(1))[0 .. 2]), $/;
323              
324 107 100       249 if ($debug) {
325 44 50       88 $debug = \&_debug unless (_isa($debug, 'CODE'));
326 44         91 $debug->(_pretty_print($bindings));
327             }
328              
329 107 100       30822 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 82     82   668 my $guard = Scope::Guard->new(sub { _leave() });
  82         165706  
341 82         1507 $^H{autobox_leave} = $guard;
342              
343 82         14143 _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   14522 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       413 return unless ($^H{autobox});
367              
368 20         34 my $bindings;
369              
370 20 100       72 if (@args) {
371 9         14 $bindings = { %{$^H{autobox}} }; # clone the current bindings hash
  9         45  
372 9         22 my %args = map { $_ => 1 } @args;
  10         39  
373              
374             # expand any virtual type "macros"
375 9         42 for my $vtype (sort { $ISA{$a}->[0] <=> $ISA{$b}->[0] } keys %ISA) {
  27         94  
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         9 $args{$_} = 1 for (@{$ISA{$vtype}->[1]});
  7         27  
381              
382 7         14 delete $args{$vtype};
383             }
384              
385 9         65 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       41 Carp::confess("unrecognized option: '", (defined $type ? $type : ''), "'") unless ($TYPES{$type});
    50          
388 17         41 delete $bindings->{$type};
389             }
390             } else { # turn off autoboxing
391 11         22 $bindings = {}; # empty hash to trigger full deletion below
392             }
393              
394 20 100       58 if (%$bindings) {
395 7         20 _install($bindings);
396             } else { # remove all traces of autobox from the current scope
397 13         39 $^H &= ~0x80020000; # unset HINT_LOCALIZE_HH + the additional bit
398 13         50 delete $^H{autobox};
399 13         32 delete $^H{autobox_scope};
400 13         122 delete $^H{autobox_leave}; # triggers the leave handler
401             }
402             }
403              
404             1;