File Coverage

blib/lib/Eval/TypeTiny.pm
Criterion Covered Total %
statement 145 153 94.7
branch 87 102 85.2
condition 39 61 63.9
subroutine 25 25 100.0
pod 4 4 100.0
total 300 345 86.9


line stmt bran cond sub pod time code
1             package Eval::TypeTiny;
2              
3 309     309   99756 use strict;
  309         696  
  309         24468  
4              
5             sub _clean_eval {
6 31994     31994   44312 local $@;
7 31994         120810 local $SIG{__DIE__};
8 31994 50   20   6773602 my $r = eval $_[0];
  11     11   3082  
  11     5   52  
  11     7   527  
  6     1   35  
  4     1   208  
  7     1   5137  
  6     1   58  
  4     1   14  
  4         206  
  8         43  
  7         63  
  2         43  
  3         12  
  3         52  
  3         123  
  3         12  
  3         77  
  2         74  
  2         13  
  1         2  
  1         123  
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
9 31994         2519281 my $e = $@;
10 31994         172488 return ( $r, $e );
11             }
12              
13 309     309   2048 use warnings;
  309         781  
  309         39667  
14              
15             BEGIN {
16 309 50   309   2587 *HAS_LEXICAL_SUBS = ( $] >= 5.018 ) ? sub () { !!1 } : sub () { !!0 };
17 309 50       109494 *NICE_PROTOTYPES = ( $] >= 5.014 ) ? sub () { !!1 } : sub () { !!0 };
18             }
19              
20             sub _pick_alternative {
21 289     289   4330 my $ok = 0;
22 289         1195 while ( @_ ) {
23 289         1432 my ( $type, $condition, $result ) = splice @_, 0, 3;
24 289 100       1145 if ( $type eq 'needs' ) {
    50          
25 286 50       20151 ++$ok if eval "require $condition; 1";
26             }
27             elsif ( $type eq 'if' ) {
28 3 100       40 ++$ok if $condition;
29             }
30 289 100       1567 next unless $ok;
31 288 100       14891 return ref( $result ) eq 'CODE' ? $result->() : ref( $result ) eq 'SCALAR' ? eval( $$result ) : $result;
    50          
32             }
33 1         3 return;
34             }
35              
36             {
37             sub IMPLEMENTATION_DEVEL_LEXALIAS () { 'Devel::LexAlias' }
38             sub IMPLEMENTATION_PADWALKER () { 'PadWalker' }
39             sub IMPLEMENTATION_TIE () { 'tie' }
40             sub IMPLEMENTATION_NATIVE () { 'perl' }
41            
42             my $implementation;
43            
44             #<<<
45             # uncoverable subroutine
46             sub ALIAS_IMPLEMENTATION () {
47 104   66 104 1 370 $implementation ||= _pick_alternative(
48             if => ( $] ge '5.022' ) => IMPLEMENTATION_NATIVE,
49             needs => 'Devel::LexAlias' => IMPLEMENTATION_DEVEL_LEXALIAS,
50             needs => 'PadWalker' => IMPLEMENTATION_PADWALKER,
51             if => !!1 => IMPLEMENTATION_TIE,
52             );
53             }
54             #>>>
55            
56             sub _force_implementation {
57 5     5   3744 $implementation = shift;
58             }
59             }
60              
61             BEGIN {
62 309 100   309   38524 *_EXTENDED_TESTING = $ENV{EXTENDED_TESTING} ? sub() { !!1 } : sub() { !!0 };
63             }
64              
65             our $AUTHORITY = 'cpan:TOBYINK';
66             our $VERSION = '2.003_000';
67             our @EXPORT = qw( eval_closure );
68             our @EXPORT_OK = qw(
69             HAS_LEXICAL_SUBS HAS_LEXICAL_VARS ALIAS_IMPLEMENTATION
70             IMPLEMENTATION_DEVEL_LEXALIAS IMPLEMENTATION_PADWALKER
71             IMPLEMENTATION_NATIVE IMPLEMENTATION_TIE
72             set_subname type_to_coderef NICE_PROTOTYPES
73             );
74              
75             $VERSION =~ tr/_//d;
76              
77             # See Types::TypeTiny for an explanation of this import method.
78             #
79             # uncoverable subroutine
80             sub import {
81 309     309   2488 no warnings "redefine";
  309         785  
  309         580463  
82 11     18   9369 our @ISA = qw( Exporter::Tiny );
83 11         33 require Exporter::Tiny;
84 9         370 my $next = \&Exporter::Tiny::import;
85 9         1535 *import = $next;
86 8         45 my $class = shift;
87 8 50       2639 my $opts = { ref( $_[0] ) ? %{ +shift } : () };
  8         37  
88 9   66     75 $opts->{into} ||= scalar( caller );
89 10         330 return $class->$next( $opts, @_ );
90             } #/ sub import
91              
92             {
93             my $subname;
94             my %already; # prevent renaming established functions
95             sub set_subname ($$) {
96 71015 100   71015 1 138118 $subname = _pick_alternative(
97             needs => 'Sub::Util' => \ q{ \&Sub::Util::set_subname },
98             needs => 'Sub::Name' => \ q{ \&Sub::Name::subname },
99             if => !!1 => 0,
100             ) unless defined $subname;
101 71015 100 66     692522 $subname and !$already{$_[1]}++ and return &$subname;
102 21225         41633 $_[1];
103             } #/ sub set_subname ($$)
104             }
105              
106             sub type_to_coderef {
107 15418     15418 1 32298 my ( $type, %args ) = @_;
108 15418   50     46528 my $post_method = $args{post_method} || q();
109            
110 15418         23613 my ( $coderef, $qualified_name );
111            
112 15418 100       29524 if ( ! defined $type ) {
113 811         1198 my $library = $args{type_library};
114 811         1185 my $name = $args{type_name};
115            
116 811         1555 $qualified_name = "$library\::$name";
117             $coderef = sub (;@) {
118 426     426   2611 my $params;
119 426 50       1341 $params = shift if ref( $_[0] ) eq "ARRAY";
120            
121 426   100     1340 $type ||= do {
122 424 50 0     2723 $library->can( 'get_type' )
123             or require Error::TypeTiny
124             && Error::TypeTiny::croak( "Expected $library to be a type library, but it doesn't seem to be" );
125 424         1601 $library->get_type( $name );
126             };
127            
128 426         816 my $t;
129 426 100       936 if ( $type ) {
130 3 50       9 $t = $params ? $type->parameterize( @$params ) : $type;
131 3 50       7 $t = $t->$post_method if $post_method;
132             }
133             else {
134 423 50 0     927 require Error::TypeTiny && Error::TypeTiny::croak( "Cannot parameterize a non-existant type" )
135             if $params;
136 423         21916 require Type::Tiny::_DeclaredType;
137 423         2543 $t = Type::Tiny::_DeclaredType->new( library => $library, name => $name );
138             }
139            
140 426 100 66     3419 @_ && wantarray ? return ( $t, @_ ) : return $t;
141 811         4804 };
142            
143 811 50       7044 require Scalar::Util && &Scalar::Util::set_prototype( $coderef, ';$' )
144             if Eval::TypeTiny::NICE_PROTOTYPES;
145             }
146             else {
147            
148             #<<<
149 14607 100       35595 my $source = $type->is_parameterizable ?
150             sprintf(
151             q{
152             sub (%s) {
153             if (ref($_[0]) eq 'Type::Tiny::_HalfOp') {
154             my $complete_type = shift->complete($type);
155             @_ && wantarray ? return($complete_type, @_) : return $complete_type;
156             }
157             my $params; $params = shift if ref($_[0]) eq q(ARRAY);
158             my $t = $params ? $type->parameterize(@$params) : $type;
159             @_ && wantarray ? return($t%s, @_) : return $t%s;
160             }
161             },
162             NICE_PROTOTYPES ? q(;$) : q(;@),
163             $post_method,
164             $post_method,
165             ) :
166             sprintf( q{ sub () { $type%s if $] } }, $post_method );
167             #>>>
168            
169 14607         38607 $qualified_name = $type->qualified_name;
170             $coderef = eval_closure(
171             source => $source,
172 14607   33     89650 description => $args{description} || sprintf( "exportable function '%s'", $qualified_name ),
173             environment => { '$type' => \$type },
174             );
175             }
176            
177 15418 50       61129 $args{anonymous} ? $coderef : set_subname( $qualified_name, $coderef );
178             }
179              
180             sub eval_closure {
181 31995     31995 1 130485 my ( %args ) = @_;
182             my $src =
183             ref $args{source} eq "ARRAY"
184 4         41 ? join( "\n", @{ $args{source} } )
185 31995 100       85810 : $args{source};
186            
187 31995 100       85105 $args{alias} = 0 unless defined $args{alias};
188 31995 100       73051 $args{line} = 1 unless defined $args{line};
189             $args{description} =~ s/[^\w .:-\[\]\(\)\{\}\']//g
190 31995 100       111490 if defined $args{description};
191             $src = qq{#line $args{line} "$args{description}"\n$src}
192 31995 100 66     201979 if defined $args{description} && !( $^P & 0x10 );
193 31995   100     108679 $args{environment} ||= {};
194            
195 31995         43806 if ( _EXTENDED_TESTING ) {
196 31995         139637 require Scalar::Util;
197 31995         48022 for my $k ( sort keys %{ $args{environment} } ) {
  31995         125477  
198             next
199             if $k =~ /^\$/
200 15442 100 66     137067 && Scalar::Util::reftype( $args{environment}{$k} ) =~ /^(SCALAR|REF)$/;
201             next
202             if $k =~ /^\@/
203 563 100 66     2662 && Scalar::Util::reftype( $args{environment}{$k} ) eq q(ARRAY);
204             next
205             if $k =~ /^\%/
206 274 100 100     930 && Scalar::Util::reftype( $args{environment}{$k} ) eq q(HASH);
207             next
208             if $k =~ /^\&/
209 267 100 66     662 && Scalar::Util::reftype( $args{environment}{$k} ) eq q(CODE);
210            
211 1         5 require Error::TypeTiny;
212             Error::TypeTiny::croak(
213             "Expected a variable name and ref; got %s => %s", $k,
214 265         490 $args{environment}{$k}
215             );
216             } #/ for my $k ( sort keys %...)
217             } #/ if ( _EXTENDED_TESTING)
218            
219 31994         61137 my $sandpkg = 'Eval::TypeTiny::Sandbox';
220 31730 100       63902 my $alias = exists( $args{alias} ) ? $args{alias} : 0;
221 31730         40800 my @keys = sort keys %{ $args{environment} };
  31730         74645  
222 31730         48408 my $i = 0;
223 31994         104651 my $source = join "\n" => (
224             "package $sandpkg;",
225             "sub {",
226             map( _make_lexical_assignment( $_, $i++, $alias ), @keys ),
227             $src,
228             "}",
229             );
230            
231 31994 100 100     78715 if ( $alias and ALIAS_IMPLEMENTATION eq IMPLEMENTATION_TIE ) {
232 269         1700 _manufacture_ties();
233             }
234            
235 31994         63183 my ( $compiler, $e ) = _clean_eval( $source );
236 31730 100       91433 if ( $e ) {
237 4         12 chomp $e;
238 268         1927 require Error::TypeTiny::Compilation;
239             "Error::TypeTiny::Compilation"->throw(
240             code => (
241 1         8 ref $args{source} eq "ARRAY" ? join( "\n", @{ $args{source} } ) : $args{source}
242             ),
243             errstr => $e,
244             environment => $args{environment},
245 4 100       89 );
246             } #/ if ( $e )
247            
248 31726         55611 my $code = $compiler->( @{ $args{environment} }{@keys} );
  31990         421549  
249 31726         178763 undef( $compiler );
250            
251 31726 100 100     75926 if ( $alias and ALIAS_IMPLEMENTATION eq IMPLEMENTATION_DEVEL_LEXALIAS ) {
252 3         15 require Devel::LexAlias;
253             Devel::LexAlias::lexalias( $code, $_ => $args{environment}{$_} )
254 3         22 for grep !/^\&/, @keys;
255             }
256            
257 31726 100 100     64528 if ( $alias and ALIAS_IMPLEMENTATION eq IMPLEMENTATION_PADWALKER ) {
258 3         15 require PadWalker;
259 3         19 my %env = map +( $_ => $args{environment}{$_} ), grep !/^\&/, @keys;
260 3         12 PadWalker::set_closed_over( $code, \%env );
261             }
262            
263 31726         175456 return $code;
264             } #/ sub eval_closure
265              
266             my $tmp;
267              
268             sub _make_lexical_assignment {
269 15297     15297   32029 my ( $key, $index, $alias ) = @_;
270 15297         30815 my $name = substr( $key, 1 );
271            
272 15297 100       35327 if ( HAS_LEXICAL_SUBS and $key =~ /^\&/ ) {
273 2         4 $tmp++;
274 2         7 my $tmpname = '$__LEXICAL_SUB__' . $tmp;
275             return
276 2         11 "no warnings 'experimental::lexical_subs';"
277             . "use feature 'lexical_subs';"
278             . "my $tmpname = \$_[$index];"
279             . "my sub $name { goto $tmpname };";
280             }
281            
282 15295 100       28333 if ( !$alias ) {
    100          
    100          
    100          
283 15271         26364 my $sigil = substr( $key, 0, 1 );
284 15271         86966 return "my $key = $sigil\{ \$_[$index] };";
285             }
286             elsif ( ALIAS_IMPLEMENTATION eq IMPLEMENTATION_NATIVE ) {
287             return
288 7         51 "no warnings 'experimental::refaliasing';"
289             . "use feature 'refaliasing';"
290             . "my $key; \\$key = \$_[$index];";
291             }
292             elsif ( ALIAS_IMPLEMENTATION eq IMPLEMENTATION_DEVEL_LEXALIAS ) {
293 5         24 return "my $key;";
294             }
295             elsif ( ALIAS_IMPLEMENTATION eq IMPLEMENTATION_PADWALKER ) {
296 5         25 return "my $key;";
297             }
298             else {
299             my $tieclass = {
300             '@' => 'Eval::TypeTiny::_TieArray',
301             '%' => 'Eval::TypeTiny::_TieHash',
302             '$' => 'Eval::TypeTiny::_TieScalar',
303 7         35 }->{ substr( $key, 0, 1 ) };
304            
305 7         60 return sprintf(
306             'tie(my(%s), "%s", $_[%d]);',
307             $key,
308             $tieclass,
309             $index,
310             );
311             } #/ else [ if ( !$alias ) ]
312             } #/ sub _make_lexical_assignment
313              
314             {
315             my $tie;
316            
317             sub _manufacture_ties {
318 5 100 66 5   148 $tie ||= eval <<'FALLBACK'; } }
    50 0        
    100 66        
    100          
    100          
    100          
319             no warnings qw(void once uninitialized numeric);
320             use Type::Tiny ();
321              
322             {
323             package #
324             Eval::TypeTiny::_TieArray;
325             require Tie::Array;
326             our @ISA = qw( Tie::StdArray );
327             sub TIEARRAY {
328             my $class = shift;
329             bless $_[0] => $class;
330             }
331             sub AUTOLOAD {
332             my $self = shift;
333             my ($method) = (our $AUTOLOAD =~ /(\w+)$/);
334             defined tied(@$self) and return tied(@$self)->$method(@_);
335             require Carp;
336             Carp::croak(qq[Can't call method "$method" on an undefined value]) unless $method eq 'DESTROY';
337             }
338             sub can {
339             my $self = shift;
340             my $code = $self->SUPER::can(@_)
341             || (defined tied(@$self) and tied(@$self)->can(@_));
342             return $code;
343             }
344             __PACKAGE__->Type::Tiny::_install_overloads(
345             q[bool] => sub { !! tied @{$_[0]} },
346             q[""] => sub { '' . tied @{$_[0]} },
347             q[0+] => sub { 0 + tied @{$_[0]} },
348             );
349             }
350             {
351             package #
352             Eval::TypeTiny::_TieHash;
353             require Tie::Hash;
354             our @ISA = qw( Tie::StdHash );
355             sub TIEHASH {
356             my $class = shift;
357             bless $_[0] => $class;
358             }
359             sub AUTOLOAD {
360             my $self = shift;
361             my ($method) = (our $AUTOLOAD =~ /(\w+)$/);
362             defined tied(%$self) and return tied(%$self)->$method(@_);
363             require Carp;
364             Carp::croak(qq[Can't call method "$method" on an undefined value]) unless $method eq 'DESTROY';
365             }
366             sub can {
367             my $self = shift;
368             my $code = $self->SUPER::can(@_)
369             || (defined tied(%$self) and tied(%$self)->can(@_));
370             return $code;
371             }
372             __PACKAGE__->Type::Tiny::_install_overloads(
373             q[bool] => sub { !! tied %{$_[0]} },
374             q[""] => sub { '' . tied %{$_[0]} },
375             q[0+] => sub { 0 + tied %{$_[0]} },
376             );
377             }
378             {
379             package #
380             Eval::TypeTiny::_TieScalar;
381             require Tie::Scalar;
382             our @ISA = qw( Tie::StdScalar );
383             sub TIESCALAR {
384             my $class = shift;
385             bless $_[0] => $class;
386             }
387             sub AUTOLOAD {
388             my $self = shift;
389             my ($method) = (our $AUTOLOAD =~ /(\w+)$/);
390             defined tied($$self) and return tied($$self)->$method(@_);
391             require Carp;
392             Carp::croak(qq[Can't call method "$method" on an undefined value]) unless $method eq 'DESTROY';
393             }
394             sub can {
395             my $self = shift;
396             my $code = $self->SUPER::can(@_)
397             || (defined tied($$self) and tied($$self)->can(@_));
398             return $code;
399             }
400             __PACKAGE__->Type::Tiny::_install_overloads(
401             q[bool] => sub { !! tied ${$_[0]} },
402             q[""] => sub { '' . tied ${$_[0]} },
403             q[0+] => sub { 0 + tied ${$_[0]} },
404             );
405             }
406              
407             1;
408             FALLBACK
409              
410             1;
411              
412             __END__
413              
414             =pod
415              
416             =encoding utf-8
417              
418             =for stopwords pragmas coderefs
419              
420             =head1 NAME
421              
422             Eval::TypeTiny - utility to evaluate a string of Perl code in a clean environment
423              
424             =head1 STATUS
425            
426             This module is covered by the
427             L<Type-Tiny stability policy|Type::Tiny::Manual::Policies/"STABILITY">.
428              
429             =head1 DESCRIPTION
430              
431             This module is used by Type::Tiny to compile coderefs from strings of
432             Perl code, and hashrefs of variables to close over.
433              
434             =head2 Functions
435              
436             By default this module exports one function, which works much like the
437             similarly named function from L<Eval::Closure>:
438              
439             =over
440              
441             =item C<< eval_closure(source => $source, environment => \%env, %opt) >>
442              
443             =back
444              
445             Other functions can be imported on request:
446              
447             =over
448              
449             =item C<< set_subname( $fully_qualified_name, $coderef ) >>
450              
451             Works like the similarly named function from L<Sub::Util>, but will
452             fallback to doing nothing if neither L<Sub::Util> nor L<Sub::Name> are
453             available. Also will cowardly refuse the set the name of a coderef
454             a second time if it's already named it.
455              
456             =item C<< type_to_coderef( $type, %options ) >>
457              
458             Turns a L<Type::Tiny> object into a coderef, suitable for installing
459             into a symbol table to create a function like C<ArrayRef> or C<Int>.
460             (Actually should work for any object which provides C<is_parameterizable>,
461             C<parameterize>, and C<qualified_name> methods, such as L<Type::Coercion>.)
462              
463             C<< $options{post_method} >> can be a string of Perl indicating a
464             method to call on the type constraint before returning it. For
465             example C<< '->moose_type' >>.
466              
467             C<< $options{description} >> can be a description of the coderef which
468             may be shown in stack traces, etc.
469              
470             The coderef will be named using C<set_subname> unless
471             C<< $options{anonymous} >> is true.
472              
473             If C<< $type >> is undef, then it is assumed that the type constraint
474             hasn't been defined yet but will later, yet you still want a function now.
475             C<< $options{type_library} >> and C<< $options{type_name} >> will be
476             used to find the type constraint when the function gets called.
477              
478             =back
479              
480             =head2 Constants
481              
482             The following constants may be exported, but are not by default.
483              
484             =over
485              
486             =item C<< HAS_LEXICAL_SUBS >>
487              
488             Boolean indicating whether Eval::TypeTiny has support for lexical subs.
489             (This feature requires Perl 5.18.)
490              
491             =item C<< ALIAS_IMPLEMENTATION >>
492              
493             Returns a string indicating what implementation of C<< alias => 1 >> is
494             being used. Eval::TypeTiny will automatically choose the best implementation.
495             This constant can be matched against the C<< IMPLEMENTAION_* >> constants.
496              
497             =item C<< IMPLEMENTATION_NATIVE >>
498              
499             If C<< ALIAS_IMPLEMENTATION eq IMPLEMENTATION_NATIVE >> then Eval::TypeTiny is
500             currently using Perl 5.22's native alias feature. This requires Perl 5.22.
501              
502             =item C<< IMPLEMENTATION_DEVEL_LEXALIAS >>
503              
504             If C<< ALIAS_IMPLEMENTATION eq IMPLEMENTATION_DEVEL_LEXALIAS >> then
505             Eval::TypeTiny is currently using L<Devel::LexAlias> to provide aliases.
506              
507             =item C<< IMPLEMENTATION_PADWALKER >>
508              
509             If C<< ALIAS_IMPLEMENTATION eq IMPLEMENTATION_PADWALKER >> then
510             Eval::TypeTiny is currently using L<PadWalker> to provide aliases.
511              
512             =item C<< IMPLEMENTATION_TIE >>
513              
514             If C<< ALIAS_IMPLEMENTATION eq IMPLEMENTATION_TIE >> then Eval::TypeTiny is
515             using the fallback implementation of aliases using C<tie>. This is the
516             slowest implementation, and may cause problems in certain edge cases, like
517             trying to alias already-tied variables, but it's the only way to implement
518             C<< alias => 1 >> without a recent version of Perl or one of the two optional
519             modules mentioned above.
520              
521             =item C<< NICE_PROTOTYPES >>
522              
523             If this is true, then type_to_coderef will give parameterizable type
524             constraints the slightly nicer prototype of C<< (;$) >> instead of the
525             default C<< (;@) >>. This allows constructs like:
526              
527             ArrayRef[Int] | HashRef[Int]
528              
529             ... to "just work".
530              
531             =back
532              
533             =head1 EVALUATION ENVIRONMENT
534              
535             The evaluation is performed in the presence of L<strict>, but the absence of
536             L<warnings>. (This is different to L<Eval::Closure> which enables warnings for
537             compiled closures.)
538              
539             The L<feature> pragma is not active in the evaluation environment, so the
540             following will not work:
541              
542             use feature qw(say);
543             use Eval::TypeTiny qw(eval_closure);
544            
545             my $say_all = eval_closure(
546             source => 'sub { say for @_ }',
547             );
548             $say_all->("Hello", "World");
549              
550             The L<feature> pragma does not "carry over" into the stringy eval. It is
551             of course possible to import pragmas into the evaluated string as part of the
552             string itself:
553              
554             use Eval::TypeTiny qw(eval_closure);
555            
556             my $say_all = eval_closure(
557             source => 'sub { use feature qw(say); say for @_ }',
558             );
559             $say_all->("Hello", "World");
560              
561             =head1 BUGS
562              
563             Please report any bugs to
564             L<https://github.com/tobyink/p5-type-tiny/issues>.
565              
566             =head1 SEE ALSO
567              
568             L<Eval::Closure>, L<Error::TypeTiny::Compilation>.
569              
570             =head1 AUTHOR
571              
572             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
573              
574             =head1 COPYRIGHT AND LICENCE
575              
576             This software is copyright (c) 2013-2014, 2017-2023 by Toby Inkster.
577              
578             This is free software; you can redistribute it and/or modify it under
579             the same terms as the Perl 5 programming language system itself.
580              
581             =head1 DISCLAIMER OF WARRANTIES
582              
583             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
584             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
585             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.