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 244     244   8523 use 5.008001;
  244         873  
4 244     244   1398 use strict;
  244         494  
  244         5982  
5 244     244   1359 use warnings;
  244         527  
  244         12312  
6              
7             BEGIN {
8 244     244   902 $Type::Coercion::AUTHORITY = 'cpan:TOBYINK';
9 244         11194 $Type::Coercion::VERSION = '2.002001';
10             }
11              
12             $Type::Coercion::VERSION =~ tr/_//d;
13              
14 244     244   2667 use Eval::TypeTiny qw<>;
  244         660  
  244         6400  
15 244     244   1404 use Scalar::Util qw< blessed >;
  244         556  
  244         14238  
16 244     244   1750 use Types::TypeTiny qw<>;
  244         734  
  244         962242  
17              
18 4     4   782 sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak }
  4         23  
19              
20             require Type::Tiny;
21              
22             __PACKAGE__->Type::Tiny::_install_overloads(
23             q("") => sub {
24 612 50   612   6437 caller =~ m{^(Moo::HandleMoose|Sub::Quote)}
25             ? $_[0]->_stringify_no_magic
26             : $_[0]->display_name;
27             },
28 52191     52191   188654 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   36 my $self = shift;
38            
39 16 100 100     147 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     30 if !$self->{_overload_coderef} || !$self->{_sub_quoted}++;
43             }
44             else {
45 13         51 Scalar::Util::weaken( my $weak = $self );
46 13   100 28   76 $self->{_overload_coderef} ||= sub { $weak->coerce( @_ ) };
  28         6906  
47             }
48            
49 16         327 $self->{_overload_coderef};
50             } #/ sub _overload_coderef
51              
52             sub new {
53 11573     11573 1 19240 my $class = shift;
54 11573 100       35352 my %params = ( @_ == 1 ) ? %{ $_[0] } : @_;
  723         4027  
55            
56 11573 100       29594 $params{name} = '__ANON__' unless exists( $params{name} );
57 11573   100     39011 my $C = delete( $params{type_coercion_map} ) || [];
58 11573         17854 my $F = delete( $params{frozen} );
59            
60 11573         20077 my $self = bless \%params, $class;
61 11573 100       23016 $self->add_type_coercions( @$C ) if @$C;
62 11573         25421 $self->_preserve_type_constraint;
63 11573         38226 Scalar::Util::weaken( $self->{type_constraint} ); # break ref cycle
64 11573 100       20671 $self->{frozen} = $F if $F;
65            
66 11573 100       20098 unless ( $self->is_anon ) {
67            
68             # First try a fast ASCII-only expression, but fall back to Unicode
69 757 50 33     1714 $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 11573         28814 return $self;
75             } #/ sub new
76              
77             sub _stringify_no_magic {
78 1     1   18 sprintf(
79             '%s=%s(0x%08x)', blessed( $_[0] ), Scalar::Util::reftype( $_[0] ),
80             Scalar::Util::refaddr( $_[0] )
81             );
82             }
83              
84 15041     15041 1 45792 sub name { $_[0]{name} }
85 612   66 612 1 5162 sub display_name { $_[0]{display_name} ||= $_[0]->_build_display_name }
86 31     31 1 85 sub library { $_[0]{library} }
87              
88             sub type_constraint {
89 4692   100 4692 1 12534 $_[0]{type_constraint} ||= $_[0]->_maybe_restore_type_constraint;
90             }
91 20316   100 20316 1 118819 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 1391   66 1391 1 10497 $_[0]{compiled_coercion} ||= $_[0]->_build_compiled_coercion;
96             }
97 3771   100 3771 1 16114 sub frozen { $_[0]{frozen} ||= 0 }
98 14     14 1 57 sub coercion_generator { $_[0]{coercion_generator} }
99 7     7 1 28 sub parameters { $_[0]{parameters} }
100 7     7 1 26 sub parameterized_from { $_[0]{parameterized_from} }
101              
102 850     850 1 3414 sub has_library { exists $_[0]{library} }
103 2327     2327 1 4840 sub has_type_constraint { defined $_[0]->type_constraint } # sic
104 857     857 1 4957 sub has_coercion_generator { exists $_[0]{coercion_generator} }
105 23     23 1 137 sub has_parameters { exists $_[0]{parameters} }
106              
107             sub _preserve_type_constraint {
108 11508     11508   15273 my $self = shift;
109             $self->{_compiled_type_constraint_check} =
110             $self->{type_constraint}->compiled_check
111 11508 100       41550 if $self->{type_constraint};
112             }
113              
114             sub _maybe_restore_type_constraint {
115 3     3   7 my $self = shift;
116 3 100       11 if ( my $check = $self->{_compiled_type_constraint_check} ) {
117 1         4 return Type::Tiny->new( constraint => $check );
118             }
119 2         10 return; # uncoverable statement
120             }
121              
122             sub add {
123 2     2 1 675 my $class = shift;
124 2         6 my ( $x, $y, $swap ) = @_;
125            
126 2 50       42 Types::TypeTiny::is_TypeTiny( $x ) and return $x->plus_fallback_coercions( $y );
127 2 50       27 Types::TypeTiny::is_TypeTiny( $y ) and return $y->plus_coercions( $x );
128            
129 2 50 33     18 _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       8 ( $y, $x ) = ( $x, $y ) if $swap;
136            
137 2         2 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         8 $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       9 delete $opts{display_name} if $opts{display_name} eq '__ANON__+__ANON__';
153            
154 2         7 my $new = $class->new( %opts );
155 2         3 $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         8 return $new;
158             } #/ sub add
159              
160             sub _build_display_name {
161 97     97   268 shift->name;
162             }
163              
164             sub qualified_name {
165 850     850 1 1485 my $self = shift;
166            
167 850 100 66     2254 if ( $self->has_library and not $self->is_anon ) {
168 31         130 return sprintf( "%s::%s", $self->library, $self->name );
169             }
170            
171 819         2149 return $self->name;
172             }
173              
174             sub is_anon {
175 12486     12486 1 17257 my $self = shift;
176 12486         20441 $self->name eq "__ANON__";
177             }
178              
179             sub _clear_compiled_coercion {
180 1105     1105   2179 delete $_[0]{_overload_coderef};
181 1105         1992 delete $_[0]{compiled_coercion};
182             }
183              
184 11451     11451 1 19380 sub freeze { $_[0]{frozen} = 1; $_[0] }
  11451         18987  
185 0     0 1 0 sub i_really_want_to_unfreeze { $_[0]{frozen} = 0; $_[0] }
  0         0  
186              
187             sub coerce {
188 659     659 1 3377 my $self = shift;
189 659         1479 return $self->compiled_coercion->( @_ );
190             }
191              
192             sub assert_coerce {
193 64     64 1 107 my $self = shift;
194 64         179 my $r = $self->coerce( @_ );
195 64 50       827 $self->type_constraint->assert_valid( $r )
196             if $self->has_type_constraint;
197 63         193 return $r;
198             }
199              
200             sub has_coercion_for_type {
201 16     16 1 139 my $self = shift;
202 16         47 my $type = Types::TypeTiny::to_TypeTiny( $_[0] );
203            
204 16 100 100     51 return "0 but true"
205             if $self->has_type_constraint
206             && $type->is_a_type_of( $self->type_constraint );
207            
208 14         47 my $c = $self->type_coercion_map;
209 14         55 for ( my $i = 0 ; $i <= $#$c ; $i += 2 ) {
210 18 100       47 return !!1 if $type->is_a_type_of( $c->[$i] );
211             }
212 4         23 return;
213             } #/ sub has_coercion_for_type
214              
215             sub has_coercion_for_value {
216 6     6 1 18 my $self = shift;
217 6         10 local $_ = $_[0];
218            
219 6 100 66     14 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         24 for ( my $i = 0 ; $i <= $#$c ; $i += 2 ) {
225 7 100       39 return !!1 if $c->[$i]->check( @_ );
226             }
227 2         24 return;
228             } #/ sub has_coercion_for_value
229              
230             sub add_type_coercions {
231 1108     1108 1 2620 my $self = shift;
232 1108         3139 my @args = @_;
233            
234 1108 100       2926 _croak "Attempt to add coercion code to a Type::Coercion which has been frozen"
235             if $self->frozen;
236            
237 1105         3141 while ( @args ) {
238 1638         5418 my $type = Types::TypeTiny::to_TypeTiny( shift @args );
239            
240 1638 100 66     8244 if ( blessed $type and my $method = $type->can( 'type_coercion_map' ) ) {
241 1         2 push @{ $self->type_coercion_map }, @{ $method->( $type ) };
  1         3  
  1         3  
242             }
243             else {
244 1637         3126 my $coercion = shift @args;
245 1637 50       38428 _croak "Types must be blessed Type::Tiny objects"
246             unless Types::TypeTiny::is_TypeTiny( $type );
247 1637 50 66     12571 _croak "Coercions must be code references or strings"
248             unless Types::TypeTiny::is_StringLike( $coercion )
249             || Types::TypeTiny::is_CodeLike( $coercion );
250 1637         2658 push @{ $self->type_coercion_map }, $type, $coercion;
  1637         4015  
251             }
252             } #/ while ( @args )
253            
254 1105         4099 $self->_clear_compiled_coercion;
255 1105         2427 return $self;
256             } #/ sub add_type_coercions
257              
258             sub _build_compiled_coercion {
259 583     583   1455 my $self = shift;
260            
261 583         1186 my @mishmash = @{ $self->type_coercion_map };
  583         1503  
262 1     1   6 return sub { $_[0] }
263 583 100       2125 unless @mishmash;
264            
265 582 100       2027 if ( $self->can_be_inlined ) {
266 467         2171 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 115         305 my ( @types, @codes );
274 115         395 while ( @mishmash ) {
275 137         315 push @types, shift @mishmash;
276 137         400 push @codes, shift @mishmash;
277             }
278 115 50       325 if ( $self->has_type_constraint ) {
279 115         329 unshift @types, $self->type_constraint;
280 115         316 unshift @codes, undef;
281             }
282            
283 115         218 my @sub;
284            
285 115         454 for my $i ( 0 .. $#types ) {
286 252 100       689 push @sub,
287             $types[$i]->can_be_inlined
288             ? sprintf( 'if (%s)', $types[$i]->inline_check( '$_[0]' ) )
289             : sprintf( 'if ($checks[%d]->(@_))', $i );
290 252 100       1679 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 115         315 push @sub, 'return $_[0];';
301            
302 115         810 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 1498     1498 1 3411 my $self = shift;
314            
315 1498 100       3099 return unless $self->frozen;
316            
317             return
318 1453 100 66     5530 if $self->has_type_constraint
319             && !$self->type_constraint->can_be_inlined;
320            
321 1374         3030 my @mishmash = @{ $self->type_coercion_map };
  1374         2953  
322 1374         4488 while ( @mishmash ) {
323 1402         6000 my ( $type, $converter ) = splice( @mishmash, 0, 2 );
324 1402 100       3633 return unless $type->can_be_inlined;
325 1400 100       6700 return unless Types::TypeTiny::is_StringLike( $converter );
326             }
327 1237         4437 return !!1;
328             } #/ sub can_be_inlined
329              
330             sub _source_type_union {
331 58     58   122 my $self = shift;
332            
333 58         101 my @r;
334 58 50       161 push @r, $self->type_constraint if $self->has_type_constraint;
335            
336 58         152 my @mishmash = @{ $self->type_coercion_map };
  58         143  
337 58         212 while ( @mishmash ) {
338 58         199 my ( $type ) = splice( @mishmash, 0, 2 );
339 58         191 push @r, $type;
340             }
341            
342 58         9580 require Type::Tiny::Union;
343 58         399 return "Type::Tiny::Union"->new( type_constraints => \@r, tmp => 1 );
344             } #/ sub _source_type_union
345              
346             sub inline_coercion {
347 611     611 1 1339 my $self = shift;
348 611         1356 my $varname = $_[0];
349            
350 611 50       1648 _croak "This coercion cannot be inlined" unless $self->can_be_inlined;
351            
352 611         1461 my @mishmash = @{ $self->type_coercion_map };
  611         1876  
353 611 50       2065 return "($varname)" unless @mishmash;
354            
355 611         1288 my ( @types, @codes );
356 611         1863 while ( @mishmash ) {
357 625         1456 push @types, shift @mishmash;
358 625         1908 push @codes, shift @mishmash;
359             }
360 611 50       1940 if ( $self->has_type_constraint ) {
361 611         1803 unshift @types, $self->type_constraint;
362 611         1554 unshift @codes, undef;
363             }
364            
365 611         1158 my @sub;
366            
367 611         2609 for my $i ( 0 .. $#types ) {
368 1236         3939 push @sub, sprintf( '(%s) ?', $types[$i]->inline_check( $varname ) );
369 1236 100 100     10085 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 611         2075 push @sub, "$varname";
380            
381 611         5746 "@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 857     857 1 2612 shift->has_coercion_generator;
427             }
428              
429             sub is_parameterized {
430 23     23 1 55 shift->has_parameters;
431             }
432              
433             sub parameterize {
434 7     7 1 2127 my $self = shift;
435 7 50       21 return $self unless @_;
436 7 50       26 $self->is_parameterizable
437             or _croak "Constraint '%s' does not accept parameters", "$self";
438            
439 7         30 @_ = map Types::TypeTiny::to_TypeTiny( $_ ), @_;
440            
441 7         23 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   15 my $self = shift;
453 7         17 my ( $target_type ) = @_;
454            
455 7 50       17 $self->is_parameterized or return $self;
456 7         29 my $parent = $self->parameterized_from;
457            
458             return ref( $self )->new(
459             type_constraint => $target_type,
460             type_coercion_map => [
461 7         20 $parent->coercion_generator->( $parent, $target_type, @{ $self->parameters } )
  7         16  
462             ],
463             parameters => \@_,
464             frozen => 1,
465             parameterized_from => $parent,
466             );
467             } #/ sub _reparameterize
468              
469             sub isa {
470 35     35 1 316 my $self = shift;
471            
472 35 0 33     92 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     96 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         318 $self->SUPER::isa( @_ );
488             } #/ sub isa
489              
490             sub can {
491 20     20 1 2899 my $self = shift;
492            
493 20         82 my $can = $self->SUPER::can( @_ );
494 20 100       95 return $can if $can;
495            
496 1 0 33     11 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         3 return;
504             } #/ sub can
505              
506             sub AUTOLOAD {
507 658     658   5162 my $self = shift;
508 658         4372 my ( $m ) = ( our $AUTOLOAD =~ /::(\w+)$/ );
509 658 50       20803 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       6 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         5 require Types::Standard;
532 1         8 $self->add_type_coercions( Types::Standard::Any(), $thing );
533             }
534             } #/ if ( @_ )
535 2         6 $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__