File Coverage

blib/lib/Type/Coercion.pm
Criterion Covered Total %
statement 224 254 87.8
branch 79 120 65.8
condition 51 101 50.5
subroutine 56 61 91.8
pod 34 34 100.0
total 444 570 77.7


line stmt bran cond sub pod time code
1             package Type::Coercion;
2              
3 256     256   8442 use 5.008001;
  256         919  
4 256     256   1498 use strict;
  256         565  
  256         6177  
5 256     256   1348 use warnings;
  256         609  
  256         12292  
6              
7             BEGIN {
8 256     256   971 $Type::Coercion::AUTHORITY = 'cpan:TOBYINK';
9 256         10565 $Type::Coercion::VERSION = '2.004000';
10             }
11              
12             $Type::Coercion::VERSION =~ tr/_//d;
13              
14 256     256   2729 use Eval::TypeTiny qw<>;
  256         706  
  256         7042  
15 256     256   1567 use Scalar::Util qw< blessed >;
  256         596  
  256         14870  
16 256     256   1740 use Types::TypeTiny qw<>;
  256         700  
  256         959377  
17              
18 4     4   22 sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak }
  4         17  
19              
20             require Type::Tiny;
21              
22             __PACKAGE__->Type::Tiny::_install_overloads(
23             q("") => sub {
24 650 50   650   7639 caller =~ m{^(Moo::HandleMoose|Sub::Quote)}
25             ? $_[0]->_stringify_no_magic
26             : $_[0]->display_name;
27             },
28 55697     55697   195513 q(bool) => sub { 1 },
29             q(&{}) => "_overload_coderef",
30             );
31              
32             __PACKAGE__->Type::Tiny::_install_overloads(
33 2     2   7 q(~~) => sub { $_[0]->has_coercion_for_value( $_[1] ) },
34             ) if Type::Tiny::SUPPORT_SMARTMATCH();
35              
36             sub _overload_coderef {
37 16     16   41 my $self = shift;
38            
39 16 100 100     157 if ( "Sub::Quote"->can( "quote_sub" ) && $self->can_be_inlined ) {
40             $self->{_overload_coderef} =
41             Sub::Quote::quote_sub( $self->inline_coercion( '$_[0]' ) )
42 3 50 66     27 if !$self->{_overload_coderef} || !$self->{_sub_quoted}++;
43             }
44             else {
45 13         55 Scalar::Util::weaken( my $weak = $self );
46 13   100 28   84 $self->{_overload_coderef} ||= sub { $weak->coerce( @_ ) };
  28         7307  
47             }
48            
49 16         366 $self->{_overload_coderef};
50             } #/ sub _overload_coderef
51              
52             sub new {
53 12189     12189 1 19744 my $class = shift;
54 12189 100       36936 my %params = ( @_ == 1 ) ? %{ $_[0] } : @_;
  759         4340  
55            
56 12189 100       31288 $params{name} = '__ANON__' unless exists( $params{name} );
57 12189   100     40704 my $C = delete( $params{type_coercion_map} ) || [];
58 12189         18632 my $F = delete( $params{frozen} );
59            
60 12189         21557 my $self = bless \%params, $class;
61 12189 100       24154 $self->add_type_coercions( @$C ) if @$C;
62 12189         26987 $self->_preserve_type_constraint;
63 12189         40727 Scalar::Util::weaken( $self->{type_constraint} ); # break ref cycle
64 12189 100       21201 $self->{frozen} = $F if $F;
65            
66 12189 100       21141 unless ( $self->is_anon ) {
67            
68             # First try a fast ASCII-only expression, but fall back to Unicode
69 793 50 33     1831 $self->name =~ /^_{0,2}[A-Z][A-Za-z0-9_]+$/sm
70             or eval q( use 5.008; $self->name =~ /^_{0,2}\p{Lu}[\p{L}0-9_]+$/sm )
71             or _croak '"%s" is not a valid coercion name', $self->name;
72             }
73            
74 12189         29510 return $self;
75             } #/ sub new
76              
77             sub _stringify_no_magic {
78 1     1   23 sprintf(
79             '%s=%s(0x%08x)', blessed( $_[0] ), Scalar::Util::reftype( $_[0] ),
80             Scalar::Util::refaddr( $_[0] )
81             );
82             }
83              
84 15829     15829 1 47793 sub name { $_[0]{name} }
85 650   66 650 1 9147 sub display_name { $_[0]{display_name} ||= $_[0]->_build_display_name }
86 31     31 1 81 sub library { $_[0]{library} }
87              
88             sub type_constraint {
89 4901   100 4901 1 21329 $_[0]{type_constraint} ||= $_[0]->_maybe_restore_type_constraint;
90             }
91 21546   100 21546 1 126700 sub type_coercion_map { $_[0]{type_coercion_map} ||= [] }
92 0   0 0 1 0 sub moose_coercion { $_[0]{moose_coercion} ||= $_[0]->_build_moose_coercion }
93              
94             sub compiled_coercion {
95 1472   66 1472 1 7648 $_[0]{compiled_coercion} ||= $_[0]->_build_compiled_coercion;
96             }
97 4029   100 4029 1 18712 sub frozen { $_[0]{frozen} ||= 0 }
98 14     14 1 58 sub coercion_generator { $_[0]{coercion_generator} }
99 7     7 1 79 sub parameters { $_[0]{parameters} }
100 7     7 1 17 sub parameterized_from { $_[0]{parameterized_from} }
101              
102 895     895 1 3307 sub has_library { exists $_[0]{library} }
103 2426     2426 1 5474 sub has_type_constraint { defined $_[0]->type_constraint } # sic
104 902     902 1 5120 sub has_coercion_generator { exists $_[0]{coercion_generator} }
105 23     23 1 128 sub has_parameters { exists $_[0]{parameters} }
106              
107             sub _preserve_type_constraint {
108 12124     12124   16040 my $self = shift;
109             $self->{_compiled_type_constraint_check} =
110             $self->{type_constraint}->compiled_check
111 12124 100       44193 if $self->{type_constraint};
112             }
113              
114             sub _maybe_restore_type_constraint {
115 3     3   9 my $self = shift;
116 3 100       13 if ( my $check = $self->{_compiled_type_constraint_check} ) {
117 1         5 return Type::Tiny->new( constraint => $check );
118             }
119 2         12 return; # uncoverable statement
120             }
121              
122             sub add {
123 2     2 1 756 my $class = shift;
124 2         5 my ( $x, $y, $swap ) = @_;
125            
126 2 50       43 Types::TypeTiny::is_TypeTiny( $x ) and return $x->plus_fallback_coercions( $y );
127 2 50       33 Types::TypeTiny::is_TypeTiny( $y ) and return $y->plus_coercions( $x );
128            
129 2 50 33     17 _croak "Attempt to add $class to something that is not a $class"
      33        
      33        
130             unless blessed( $x )
131             && blessed( $y )
132             && $x->isa( $class )
133             && $y->isa( $class );
134            
135 2 50       7 ( $y, $x ) = ( $x, $y ) if $swap;
136            
137 2         5 my %opts;
138 2 50 33     5 if ( $x->has_type_constraint
    0 33        
      0        
139             and $y->has_type_constraint
140             and $x->type_constraint == $y->type_constraint )
141             {
142 2         7 $opts{type_constraint} = $x->type_constraint;
143             }
144             elsif ( $x->has_type_constraint and $y->has_type_constraint ) {
145            
146             # require Type::Tiny::Union;
147             # $opts{type_constraint} = "Type::Tiny::Union"->new(
148             # type_constraints => [ $x->type_constraint, $y->type_constraint ],
149             # );
150             }
151 2   33     14 $opts{display_name} ||= "$x+$y";
152 2 50       42 delete $opts{display_name} if $opts{display_name} eq '__ANON__+__ANON__';
153            
154 2         10 my $new = $class->new( %opts );
155 2         5 $new->add_type_coercions( @{ $x->type_coercion_map } );
  2         5  
156 2         4 $new->add_type_coercions( @{ $y->type_coercion_map } );
  2         4  
157 2         13 return $new;
158             } #/ sub add
159              
160             sub _build_display_name {
161 98     98   319 shift->name;
162             }
163              
164             sub qualified_name {
165 895     895 1 1606 my $self = shift;
166            
167 895 100 66     4404 if ( $self->has_library and not $self->is_anon ) {
168 31         119 return sprintf( "%s::%s", $self->library, $self->name );
169             }
170            
171 864         2187 return $self->name;
172             }
173              
174             sub is_anon {
175 13147     13147 1 17982 my $self = shift;
176 13147         21536 $self->name eq "__ANON__";
177             }
178              
179             sub _clear_compiled_coercion {
180 1161     1161   2314 delete $_[0]{_overload_coderef};
181 1161         2171 delete $_[0]{compiled_coercion};
182             }
183              
184 12203     12203 1 20641 sub freeze { $_[0]{frozen} = 1; $_[0] }
  12203         20219  
185 0     0 1 0 sub i_really_want_to_unfreeze { $_[0]{frozen} = 0; $_[0] }
  0         0  
186              
187             sub coerce {
188 662     662 1 3788 my $self = shift;
189 662         1592 return $self->compiled_coercion->( @_ );
190             }
191              
192             sub assert_coerce {
193 64     64 1 116 my $self = shift;
194 64         151 my $r = $self->coerce( @_ );
195 64 50       943 $self->type_constraint->assert_valid( $r )
196             if $self->has_type_constraint;
197 63         202 return $r;
198             }
199              
200             sub has_coercion_for_type {
201 17     17 1 135 my $self = shift;
202 17         57 my $type = Types::TypeTiny::to_TypeTiny( $_[0] );
203            
204 17 100 100     60 return "0 but true"
205             if $self->has_type_constraint
206             && $type->is_a_type_of( $self->type_constraint );
207            
208 15         60 my $c = $self->type_coercion_map;
209 15         67 for ( my $i = 0 ; $i <= $#$c ; $i += 2 ) {
210 19 100       59 return !!1 if $type->is_a_type_of( $c->[$i] );
211             }
212 4         47 return;
213             } #/ sub has_coercion_for_type
214              
215             sub has_coercion_for_value {
216 6     6 1 10 my $self = shift;
217 6         11 local $_ = $_[0];
218            
219 6 100 66     13 return "0 but true"
220             if $self->has_type_constraint
221             && $self->type_constraint->check( @_ );
222            
223 5         39 my $c = $self->type_coercion_map;
224 5         18 for ( my $i = 0 ; $i <= $#$c ; $i += 2 ) {
225 7 100       35 return !!1 if $c->[$i]->check( @_ );
226             }
227 2         22 return;
228             } #/ sub has_coercion_for_value
229              
230             sub add_type_coercions {
231 1164     1164 1 2856 my $self = shift;
232 1164         3274 my @args = @_;
233            
234 1164 100       3345 _croak "Attempt to add coercion code to a Type::Coercion which has been frozen"
235             if $self->frozen;
236            
237 1161         3388 while ( @args ) {
238 1718         5967 my $type = Types::TypeTiny::to_TypeTiny( shift @args );
239            
240 1718 100 66     8697 if ( blessed $type and my $method = $type->can( 'type_coercion_map' ) ) {
241 1         3 push @{ $self->type_coercion_map }, @{ $method->( $type ) };
  1         3  
  1         3  
242             }
243             else {
244 1717         3275 my $coercion = shift @args;
245 1717 50       40991 _croak "Types must be blessed Type::Tiny objects"
246             unless Types::TypeTiny::is_TypeTiny( $type );
247 1717 50 66     14207 _croak "Coercions must be code references or strings"
248             unless Types::TypeTiny::is_StringLike( $coercion )
249             || Types::TypeTiny::is_CodeLike( $coercion );
250 1717         2832 push @{ $self->type_coercion_map }, $type, $coercion;
  1717         4810  
251             }
252             } #/ while ( @args )
253            
254 1161         4365 $self->_clear_compiled_coercion;
255 1161         2661 return $self;
256             } #/ sub add_type_coercions
257              
258             sub _build_compiled_coercion {
259 618     618   1789 my $self = shift;
260            
261 618         2065 my @mishmash = @{ $self->type_coercion_map };
  618         2796  
262 1     1   5 return sub { $_[0] }
263 618 100       2191 unless @mishmash;
264            
265 617 100       2166 if ( $self->can_be_inlined ) {
266 497         2463 return Eval::TypeTiny::eval_closure(
267             source => sprintf( 'sub ($) { %s }', $self->inline_coercion( '$_[0]' ) ),
268             description => sprintf( "compiled coercion '%s'", $self ),
269             );
270             }
271            
272             # These arrays will be closed over.
273 120         302 my ( @types, @codes );
274 120         423 while ( @mishmash ) {
275 142         322 push @types, shift @mishmash;
276 142         435 push @codes, shift @mishmash;
277             }
278 120 50       329 if ( $self->has_type_constraint ) {
279 120         337 unshift @types, $self->type_constraint;
280 120         357 unshift @codes, undef;
281             }
282            
283 120         235 my @sub;
284            
285 120         986 for my $i ( 0 .. $#types ) {
286 262 100       1138 push @sub,
287             $types[$i]->can_be_inlined
288             ? sprintf( 'if (%s)', $types[$i]->inline_check( '$_[0]' ) )
289             : sprintf( 'if ($checks[%d]->(@_))', $i );
290 262 100       1729 push @sub,
    100          
291             !defined( $codes[$i] )
292             ? sprintf( ' { return $_[0] }' )
293             : Types::TypeTiny::is_StringLike( $codes[$i] ) ? sprintf(
294             ' { local $_ = $_[0]; return scalar(%s); }',
295             $codes[$i]
296             )
297             : sprintf( ' { local $_ = $_[0]; return scalar($codes[%d]->(@_)) }', $i );
298             } #/ for my $i ( 0 .. $#types)
299            
300 120         332 push @sub, 'return $_[0];';
301            
302 120         853 return Eval::TypeTiny::eval_closure(
303             source => sprintf( 'sub ($) { %s }', join qq[\n], @sub ),
304             description => sprintf( "compiled coercion '%s'", $self ),
305             environment => {
306             '@checks' => [ map $_->compiled_check, @types ],
307             '@codes' => \@codes,
308             },
309             );
310             } #/ sub _build_compiled_coercion
311              
312             sub can_be_inlined {
313 1563     1563 1 2693 my $self = shift;
314            
315 1563 100       3193 return unless $self->frozen;
316            
317             return
318 1516 100 66     3776 if $self->has_type_constraint
319             && !$self->type_constraint->can_be_inlined;
320            
321 1437         4325 my @mishmash = @{ $self->type_coercion_map };
  1437         3282  
322 1437         4160 while ( @mishmash ) {
323 1465         3998 my ( $type, $converter ) = splice( @mishmash, 0, 2 );
324 1465 100       4106 return unless $type->can_be_inlined;
325 1463 100       6951 return unless Types::TypeTiny::is_StringLike( $converter );
326             }
327 1297         4728 return !!1;
328             } #/ sub can_be_inlined
329              
330             sub _source_type_union {
331 58     58   119 my $self = shift;
332            
333 58         100 my @r;
334 58 50       166 push @r, $self->type_constraint if $self->has_type_constraint;
335            
336 58         146 my @mishmash = @{ $self->type_coercion_map };
  58         134  
337 58         179 while ( @mishmash ) {
338 58         176 my ( $type ) = splice( @mishmash, 0, 2 );
339 58         194 push @r, $type;
340             }
341            
342 58         10033 require Type::Tiny::Union;
343 58         393 return "Type::Tiny::Union"->new( type_constraints => \@r, tmp => 1 );
344             } #/ sub _source_type_union
345              
346             sub inline_coercion {
347 641     641 1 1347 my $self = shift;
348 641         1461 my $varname = $_[0];
349            
350 641 50       3633 _croak "This coercion cannot be inlined" unless $self->can_be_inlined;
351            
352 641         1405 my @mishmash = @{ $self->type_coercion_map };
  641         1669  
353 641 50       2029 return "($varname)" unless @mishmash;
354            
355 641         1414 my ( @types, @codes );
356 641         1946 while ( @mishmash ) {
357 655         1625 push @types, shift @mishmash;
358 655         1924 push @codes, shift @mishmash;
359             }
360 641 50       1695 if ( $self->has_type_constraint ) {
361 641         1911 unshift @types, $self->type_constraint;
362 641         1730 unshift @codes, undef;
363             }
364            
365 641         1467 my @sub;
366            
367 641         2674 for my $i ( 0 .. $#types ) {
368 1296         4181 push @sub, sprintf( '(%s) ?', $types[$i]->inline_check( $varname ) );
369 1296 100 100     10170 push @sub,
    100          
370             ( defined( $codes[$i] ) && ( $varname eq '$_' ) )
371             ? sprintf( 'scalar(do { %s }) :', $codes[$i] )
372             : defined( $codes[$i] ) ? sprintf(
373             'scalar(do { local $_ = %s; %s }) :', $varname,
374             $codes[$i]
375             )
376             : sprintf( '%s :', $varname );
377             } #/ for my $i ( 0 .. $#types)
378            
379 641         2202 push @sub, "$varname";
380            
381 641         5947 "@sub";
382             } #/ sub inline_coercion
383              
384             sub _build_moose_coercion {
385 0     0   0 my $self = shift;
386            
387 0         0 my %options = ();
388             $options{type_coercion_map} =
389 0         0 [ $self->freeze->_codelike_type_coercion_map( 'moose_type' ) ];
390 0 0       0 $options{type_constraint} = $self->type_constraint
391             if $self->has_type_constraint;
392            
393 0         0 require Moose::Meta::TypeCoercion;
394 0         0 my $r = "Moose::Meta::TypeCoercion"->new( %options );
395            
396 0         0 return $r;
397             } #/ sub _build_moose_coercion
398              
399             sub _codelike_type_coercion_map {
400 0     0   0 my $self = shift;
401 0         0 my $modifier = $_[0];
402            
403 0         0 my @orig = @{ $self->type_coercion_map };
  0         0  
404 0         0 my @new;
405            
406 0         0 while ( @orig ) {
407 0         0 my ( $type, $converter ) = splice( @orig, 0, 2 );
408            
409 0 0       0 push @new, $modifier ? $type->$modifier : $type;
410            
411 0 0       0 if ( Types::TypeTiny::is_CodeLike( $converter ) ) {
412 0         0 push @new, $converter;
413             }
414             else {
415 0         0 push @new, Eval::TypeTiny::eval_closure(
416             source => sprintf( 'sub { local $_ = $_[0]; %s }', $converter ),
417             description => sprintf( "temporary compiled converter from '%s'", $type ),
418             );
419             }
420             } #/ while ( @orig )
421            
422 0         0 return @new;
423             } #/ sub _codelike_type_coercion_map
424              
425             sub is_parameterizable {
426 902     902 1 2617 shift->has_coercion_generator;
427             }
428              
429             sub is_parameterized {
430 23     23 1 61 shift->has_parameters;
431             }
432              
433             sub parameterize {
434 7     7 1 2540 my $self = shift;
435 7 50       22 return $self unless @_;
436 7 50       25 $self->is_parameterizable
437             or _croak "Constraint '%s' does not accept parameters", "$self";
438            
439 7         60 @_ = map Types::TypeTiny::to_TypeTiny( $_ ), @_;
440            
441 7         27 return ref( $self )->new(
442             type_constraint => $self->type_constraint,
443             type_coercion_map =>
444             [ $self->coercion_generator->( $self, $self->type_constraint, @_ ) ],
445             parameters => \@_,
446             frozen => 1,
447             parameterized_from => $self,
448             );
449             } #/ sub parameterize
450              
451             sub _reparameterize {
452 7     7   16 my $self = shift;
453 7         14 my ( $target_type ) = @_;
454            
455 7 50       17 $self->is_parameterized or return $self;
456 7         22 my $parent = $self->parameterized_from;
457            
458             return ref( $self )->new(
459             type_constraint => $target_type,
460             type_coercion_map => [
461 7         19 $parent->coercion_generator->( $parent, $target_type, @{ $self->parameters } )
  7         26  
462             ],
463             parameters => \@_,
464             frozen => 1,
465             parameterized_from => $parent,
466             );
467             } #/ sub _reparameterize
468              
469             sub isa {
470 35     35 1 313 my $self = shift;
471            
472 35 0 33     99 if ( $INC{"Moose.pm"}
      33        
473             and blessed( $self )
474             and $_[0] eq 'Moose::Meta::TypeCoercion' )
475             {
476 0         0 return !!1;
477             }
478            
479 35 0 33     90 if ( $INC{"Moose.pm"}
      33        
480             and blessed( $self )
481             and $_[0] =~ /^(Class::MOP|MooseX?)::/ )
482             {
483 0         0 my $r = $self->moose_coercion->isa( @_ );
484 0 0       0 return $r if $r;
485             }
486            
487 35         286 $self->SUPER::isa( @_ );
488             } #/ sub isa
489              
490             sub can {
491 20     20 1 2815 my $self = shift;
492            
493 20         81 my $can = $self->SUPER::can( @_ );
494 20 100       162 return $can if $can;
495            
496 1 0 33     5 if ( $INC{"Moose.pm"}
      33        
497             and blessed( $self )
498             and my $method = $self->moose_coercion->can( @_ ) )
499             {
500 0     0   0 return sub { $method->( shift->moose_coercion, @_ ) };
  0         0  
501             }
502            
503 1         4 return;
504             } #/ sub can
505              
506             sub AUTOLOAD {
507 665     665   6067 my $self = shift;
508 665         4591 my ( $m ) = ( our $AUTOLOAD =~ /::(\w+)$/ );
509 665 50       19944 return if $m eq 'DESTROY';
510            
511 0 0 0     0 if ( $INC{"Moose.pm"}
      0        
512             and blessed( $self )
513             and my $method = $self->moose_coercion->can( $m ) )
514             {
515 0         0 return $method->( $self->moose_coercion, @_ );
516             }
517            
518 0   0     0 _croak q[Can't locate object method "%s" via package "%s"], $m,
519             ref( $self ) || $self;
520             } #/ sub AUTOLOAD
521              
522             # Private Moose method, but Moo uses this...
523             sub _compiled_type_coercion {
524 2     2   4 my $self = shift;
525 2 50       7 if ( @_ ) {
526 2         3 my $thing = $_[0];
527 2 100 66     24 if ( blessed( $thing ) and $thing->isa( "Type::Coercion" ) ) {
    50          
528 1         2 $self->add_type_coercions( @{ $thing->type_coercion_map } );
  1         2  
529             }
530             elsif ( Types::TypeTiny::is_CodeLike( $thing ) ) {
531 1         6 require Types::Standard;
532 1         5 $self->add_type_coercions( Types::Standard::Any(), $thing );
533             }
534             } #/ if ( @_ )
535 2         7 $self->compiled_coercion;
536             } #/ sub _compiled_type_coercion
537              
538             *compile_type_coercion = \&compiled_coercion;
539 1     1 1 4 sub meta { _croak( "Not really a Moose::Meta::TypeCoercion. Sorry!" ) }
540              
541             1;
542              
543             __END__