File Coverage

blib/lib/Type/Coercion.pm
Criterion Covered Total %
statement 228 259 87.6
branch 82 124 66.1
condition 51 102 50.0
subroutine 56 61 91.8
pod 35 35 100.0
total 452 581 77.6


line stmt bran cond sub pod time code
1             package Type::Coercion;
2              
3 284     284   10656 use 5.008001;
  284         1150  
4 284     284   1771 use strict;
  284         571  
  284         9137  
5 284     284   1520 use warnings;
  284         675  
  284         24484  
6              
7             BEGIN {
8 284     284   1137 $Type::Coercion::AUTHORITY = 'cpan:TOBYINK';
9 284         16131 $Type::Coercion::VERSION = '2.010001';
10             }
11              
12             $Type::Coercion::VERSION =~ tr/_//d;
13              
14 284     284   3082 use Eval::TypeTiny qw<>;
  284         3088  
  284         9970  
15 284     284   2013 use Scalar::Util qw< blessed >;
  284         688  
  284         25417  
16 284     284   1852 use Types::TypeTiny qw<>;
  284         726  
  284         1364997  
17              
18 4     4   28 sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak }
  4         26  
19              
20             require Type::Tiny;
21              
22             __PACKAGE__->Type::Tiny::_install_overloads(
23             q("") => sub {
24 771 50   771   9550 caller =~ m{^(Moo::HandleMoose|Sub::Quote)}
25             ? $_[0]->_stringify_no_magic
26             : $_[0]->display_name;
27             },
28 68244     68244   260470 q(bool) => sub { 1 },
29             q(&{}) => "_overload_coderef",
30             );
31              
32             __PACKAGE__->Type::Tiny::_install_overloads(
33             q(~~) => sub { $_[0]->has_coercion_for_value( $_[1] ) },
34             ) if Type::Tiny::SUPPORT_SMARTMATCH();
35              
36             sub _overload_coderef {
37 16     16   32 my $self = shift;
38            
39 16 100 100     177 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     31 if !$self->{_overload_coderef} || !$self->{_sub_quoted}++;
43             }
44             else {
45 13         27 Scalar::Util::weaken( my $weak = $self );
46 13   66 28   65 $self->{_overload_coderef} ||= sub { $weak->coerce( @_ ) };
  28         8018  
47             }
48            
49 16         407 $self->{_overload_coderef};
50             } #/ sub _overload_coderef
51              
52             sub new {
53 14013     14013 1 26753 my $class = shift;
54 14013 100       52889 my %params = ( @_ == 1 ) ? %{ $_[0] } : @_;
  843         5023  
55            
56 14013 100       44059 $params{name} = '__ANON__' unless exists( $params{name} );
57 14013   100     65163 my $C = delete( $params{type_coercion_map} ) || [];
58 14013         23937 my $F = delete( $params{frozen} );
59            
60 14013         28748 my $self = bless \%params, $class;
61 14013 100       35161 $self->add_type_coercions( @$C ) if @$C;
62 14013         42049 $self->_preserve_type_constraint;
63 14013         35471 Scalar::Util::weaken( $self->{type_constraint} ); # break ref cycle
64 14013 100       29702 $self->{frozen} = $F if $F;
65            
66 14013 100       31485 unless ( $self->is_anon ) {
67            
68             # First try a fast ASCII-only expression, but fall back to Unicode
69 877 50 33     2208 $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 14013         37716 return $self;
75             } #/ sub new
76              
77             sub _stringify_no_magic {
78 1     1   20 sprintf(
79             '%s=%s(0x%08x)', blessed( $_[0] ), Scalar::Util::reftype( $_[0] ),
80             Scalar::Util::refaddr( $_[0] )
81             );
82             }
83              
84 18108     18108 1 62338 sub name { $_[0]{name} }
85 771   66 771 1 7061 sub display_name { $_[0]{display_name} ||= $_[0]->_build_display_name }
86 31     31 1 87 sub library { $_[0]{library} }
87              
88             sub type_constraint {
89 5612   100 5612 1 15345 $_[0]{type_constraint} ||= $_[0]->_maybe_restore_type_constraint;
90             }
91 25815   100 25815 1 180119 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 1798   66 1798 1 11364 $_[0]{compiled_coercion} ||= $_[0]->_build_compiled_coercion;
96             }
97 4931   100 4931 1 24187 sub frozen { $_[0]{frozen} ||= 0 }
98 14     14 1 60 sub coercion_generator { $_[0]{coercion_generator} }
99 7     7 1 31 sub parameters { $_[0]{parameters} }
100 7     7 1 16 sub parameterized_from { $_[0]{parameterized_from} }
101              
102 1018     1018 1 4141 sub has_library { exists $_[0]{library} }
103 2781     2781 1 8362 sub has_type_constraint { defined $_[0]->type_constraint } # sic
104 1025     1025 1 4725 sub has_coercion_generator { exists $_[0]{coercion_generator} }
105 23     23 1 95 sub has_parameters { exists $_[0]{parameters} }
106              
107             # It is possible for the target type constraint to have been garbage collected
108             # but the type coercion object to still exist, as we only keep a weak
109             # reference to the target type constraint to prevent cyclical references.
110             # For this reason, we also keep a copy of the type's compiled check which
111             # will just be a simple coderef that unlikely has any references to anything.
112             # We preserve it during the constructor, then in the getter for the type
113             # constraint, we can quickly rebuild a dummy type constraint using the same
114             # compiled check we preserved, which will usually be sufficient for our needs.
115              
116             sub _preserve_type_constraint {
117 13948     13948   20418 my $self = shift;
118             $self->{_compiled_type_constraint_check} =
119             $self->{type_constraint}->compiled_check
120 13948 100       60235 if $self->{type_constraint};
121             }
122              
123             sub _maybe_restore_type_constraint {
124 4     4   7 my $self = shift;
125 4 100       16 if ( my $check = $self->{_compiled_type_constraint_check} ) {
126 2         10 return Type::Tiny->new( constraint => $check );
127             }
128 2         16 return; # uncoverable statement
129             }
130              
131             sub add {
132 2     2 1 565 my $class = shift;
133 2         7 my ( $x, $y, $swap ) = @_;
134            
135 2 50       86 Types::TypeTiny::is_TypeTiny( $x ) and return $x->plus_fallback_coercions( $y );
136 2 50       42 Types::TypeTiny::is_TypeTiny( $y ) and return $y->plus_coercions( $x );
137            
138 2 50 33     16 _croak "Attempt to add $class to something that is not a $class"
      33        
      33        
139             unless blessed( $x )
140             && blessed( $y )
141             && $x->isa( $class )
142             && $y->isa( $class );
143            
144 2 50       6 ( $y, $x ) = ( $x, $y ) if $swap;
145            
146 2         4 my %opts;
147 2 50 33     7 if ( $x->has_type_constraint
    0 33        
      0        
148             and $y->has_type_constraint
149             and $x->type_constraint == $y->type_constraint )
150             {
151 2         5 $opts{type_constraint} = $x->type_constraint;
152             }
153             elsif ( $x->has_type_constraint and $y->has_type_constraint ) {
154            
155             # require Type::Tiny::Union;
156             # $opts{type_constraint} = "Type::Tiny::Union"->new(
157             # type_constraints => [ $x->type_constraint, $y->type_constraint ],
158             # );
159             }
160 2   33     11 $opts{display_name} ||= "$x+$y";
161 2 50       6 delete $opts{display_name} if $opts{display_name} eq '__ANON__+__ANON__';
162            
163 2         7 my $new = $class->new( %opts );
164 2         5 $new->add_type_coercions( @{ $x->type_coercion_map } );
  2         3  
165 2         2 $new->add_type_coercions( @{ $y->type_coercion_map } );
  2         3  
166 2         5 return $new;
167             } #/ sub add
168              
169             sub _build_display_name {
170 100     100   315 shift->name;
171             }
172              
173             sub qualified_name {
174 1018     1018 1 1833 my $self = shift;
175            
176 1018 100 66     3149 if ( $self->has_library and not $self->is_anon ) {
177 31         91 return sprintf( "%s::%s", $self->library, $self->name );
178             }
179            
180 987         2754 return $self->name;
181             }
182              
183             sub is_anon {
184 15094     15094 1 22465 my $self = shift;
185 15094         30401 $self->name eq "__ANON__";
186             }
187              
188             sub _clear_compiled_coercion {
189 1310     1310   4266 delete $_[0]{_overload_coderef};
190 1310         3963 delete $_[0]{compiled_coercion};
191             }
192              
193 14839     14839 1 35286 sub freeze { $_[0]{frozen} = 1; $_[0] }
  14839         26319  
194 0     0 1 0 sub i_really_want_to_unfreeze { $_[0]{frozen} = 0; $_[0] }
  0         0  
195              
196             sub coerce {
197 674     674 1 6556 my $self = shift;
198 674         3823 return $self->compiled_coercion->( @_ );
199             }
200              
201             sub check_coerce {
202 8     8 1 14 my $self = shift;
203 8         22 my $r = $self->coerce( @_ );
204            
205 8 50       108 if ( $self->has_type_constraint ) {
206 8         14 my $tc = $self->type_constraint;
207 8 100       23 return $tc->check( $r ) ? $r : undef;
208             }
209             else {
210 0         0 return $r;
211             }
212             }
213              
214             sub assert_coerce {
215 64     64 1 116 my $self = shift;
216 64         201 my $r = $self->coerce( @_ );
217 64 50       928 $self->type_constraint->assert_valid( $r )
218             if $self->has_type_constraint;
219 63         223 return $r;
220             }
221              
222             sub has_coercion_for_type {
223 17     17 1 146 my $self = shift;
224 17         69 my $type = Types::TypeTiny::to_TypeTiny( $_[0] );
225            
226 17 100 100     80 return "0 but true"
227             if $self->has_type_constraint
228             && $type->is_a_type_of( $self->type_constraint );
229            
230 15         82 my $c = $self->type_coercion_map;
231 15         152 for ( my $i = 0 ; $i <= $#$c ; $i += 2 ) {
232 19 100       61 return !!1 if $type->is_a_type_of( $c->[$i] );
233             }
234 4         31 return;
235             } #/ sub has_coercion_for_type
236              
237             sub has_coercion_for_value {
238 4     4 1 6 my $self = shift;
239 4         7 local $_ = $_[0];
240            
241 4 100 66     10 return "0 but true"
242             if $self->has_type_constraint
243             && $self->type_constraint->check( @_ );
244            
245 3         30 my $c = $self->type_coercion_map;
246 3         9 for ( my $i = 0 ; $i <= $#$c ; $i += 2 ) {
247 5 100       22 return !!1 if $c->[$i]->check( @_ );
248             }
249 1         6 return;
250             } #/ sub has_coercion_for_value
251              
252             sub add_type_coercions {
253 1313     1313 1 3738 my $self = shift;
254 1313         4553 my @args = @_;
255            
256 1313 100       4072 _croak "Attempt to add coercion code to a Type::Coercion which has been frozen"
257             if $self->frozen;
258            
259 1310         3817 while ( @args ) {
260 1923         8128 my $type = Types::TypeTiny::to_TypeTiny( shift @args );
261            
262 1923 100 66     11338 if ( blessed $type and my $method = $type->can( 'type_coercion_map' ) ) {
263 1         3 push @{ $self->type_coercion_map }, @{ $method->( $type ) };
  1         5  
  1         3  
264             }
265             else {
266 1922         3895 my $coercion = shift @args;
267 1922 50       61791 _croak "Types must be blessed Type::Tiny objects"
268             unless Types::TypeTiny::is_TypeTiny( $type );
269 1922 50 66     17380 _croak "Coercions must be code references or strings"
270             unless Types::TypeTiny::is_StringLike( $coercion )
271             || Types::TypeTiny::is_CodeLike( $coercion );
272 1922         3670 push @{ $self->type_coercion_map }, $type, $coercion;
  1922         5734  
273             }
274             } #/ while ( @args )
275            
276 1310         5535 $self->_clear_compiled_coercion;
277 1310         5512 return $self;
278             } #/ sub add_type_coercions
279              
280             sub _build_compiled_coercion {
281 739     739   1692 my $self = shift;
282            
283 739         1611 my @mishmash = @{ $self->type_coercion_map };
  739         2261  
284 1     1   8 return sub { $_[0] }
285 739 100       2606 unless @mishmash;
286            
287 738 100       2969 if ( $self->can_be_inlined ) {
288 604         2857 return Eval::TypeTiny::eval_closure(
289             source => sprintf( 'sub ($) { %s }', $self->inline_coercion( '$_[0]' ) ),
290             description => sprintf( "compiled coercion '%s'", $self ),
291             );
292             }
293            
294             # These arrays will be closed over.
295 134         351 my ( @types, @codes );
296 134         473 while ( @mishmash ) {
297 156         388 push @types, shift @mishmash;
298 156         539 push @codes, shift @mishmash;
299             }
300 134 50       421 if ( $self->has_type_constraint ) {
301 134         391 unshift @types, $self->type_constraint;
302 134         329 unshift @codes, undef;
303             }
304            
305 134         332 my @sub;
306            
307 134         568 for my $i ( 0 .. $#types ) {
308 290 100       948 push @sub,
309             $types[$i]->can_be_inlined
310             ? sprintf( 'if (%s)', $types[$i]->inline_check( '$_[0]' ) )
311             : sprintf( 'if ($checks[%d]->(@_))', $i );
312 290 100       2195 push @sub,
    100          
313             !defined( $codes[$i] )
314             ? sprintf( ' { return $_[0] }' )
315             : Types::TypeTiny::is_StringLike( $codes[$i] ) ? sprintf(
316             ' { local $_ = $_[0]; return scalar(%s); }',
317             $codes[$i]
318             )
319             : sprintf( ' { local $_ = $_[0]; return scalar($codes[%d]->(@_)) }', $i );
320             } #/ for my $i ( 0 .. $#types)
321            
322 134         369 push @sub, 'return $_[0];';
323            
324 134         1020 return Eval::TypeTiny::eval_closure(
325             source => sprintf( 'sub ($) { %s }', join qq[\n], @sub ),
326             description => sprintf( "compiled coercion '%s'", $self ),
327             environment => {
328             '@checks' => [ map $_->compiled_check, @types ],
329             '@codes' => \@codes,
330             },
331             );
332             } #/ sub _build_compiled_coercion
333              
334             sub can_be_inlined {
335 1791     1791 1 3217 my $self = shift;
336            
337 1791 100       4500 return unless $self->frozen;
338            
339             return
340 1744 100 66     5195 if $self->has_type_constraint
341             && !$self->type_constraint->can_be_inlined;
342            
343 1664         3402 my @mishmash = @{ $self->type_coercion_map };
  1664         3764  
344 1664         4638 while ( @mishmash ) {
345 1692         5561 my ( $type, $converter ) = splice( @mishmash, 0, 2 );
346 1692 100       4439 return unless $type->can_be_inlined;
347 1690 100       8557 return unless Types::TypeTiny::is_StringLike( $converter );
348             }
349 1511         6408 return !!1;
350             } #/ sub can_be_inlined
351              
352             sub _source_type_union {
353 58     58   117 my $self = shift;
354            
355 58         100 my @r;
356 58 50       182 push @r, $self->type_constraint if $self->has_type_constraint;
357            
358 58         117 my @mishmash = @{ $self->type_coercion_map };
  58         123  
359 58         180 while ( @mishmash ) {
360 58         184 my ( $type ) = splice( @mishmash, 0, 2 );
361 58         174 push @r, $type;
362             }
363            
364 58         11325 require Type::Tiny::Union;
365 58         429 return "Type::Tiny::Union"->new( type_constraints => \@r, tmp => 1 );
366             } #/ sub _source_type_union
367              
368             sub inline_coercion {
369 748     748 1 1456 my $self = shift;
370 748         1589 my $varname = $_[0];
371            
372 748 50       2496 _croak "This coercion cannot be inlined" unless $self->can_be_inlined;
373            
374 748         1659 my @mishmash = @{ $self->type_coercion_map };
  748         2452  
375 748 50       2353 return "($varname)" unless @mishmash;
376            
377 748         1699 my ( @types, @codes );
378 748         2306 while ( @mishmash ) {
379 762         2046 push @types, shift @mishmash;
380 762         2465 push @codes, shift @mishmash;
381             }
382 748 50       2210 if ( $self->has_type_constraint ) {
383 748         3184 unshift @types, $self->type_constraint;
384 748         1977 unshift @codes, undef;
385             }
386            
387 748         2111 my @sub;
388            
389 748         4397 for my $i ( 0 .. $#types ) {
390 1510         5927 push @sub, sprintf( '(%s) ?', $types[$i]->inline_check( $varname ) );
391 1510 100 100     10860 push @sub,
    100          
392             ( defined( $codes[$i] ) && ( $varname eq '$_' ) )
393             ? sprintf( 'scalar(do { %s }) :', $codes[$i] )
394             : defined( $codes[$i] ) ? sprintf(
395             'scalar(do { local $_ = %s; %s }) :', $varname,
396             $codes[$i]
397             )
398             : sprintf( '%s :', $varname );
399             } #/ for my $i ( 0 .. $#types)
400            
401 748         2328 push @sub, "$varname";
402            
403 748         7450 return join q[ ], @sub;
404             } #/ sub inline_coercion
405              
406             sub _build_moose_coercion {
407 0     0   0 my $self = shift;
408            
409 0         0 my %options = ();
410             $options{type_coercion_map} =
411 0         0 [ $self->freeze->_codelike_type_coercion_map( 'moose_type' ) ];
412 0 0       0 $options{type_constraint} = $self->type_constraint
413             if $self->has_type_constraint;
414            
415 0         0 require Moose::Meta::TypeCoercion;
416 0         0 my $r = "Moose::Meta::TypeCoercion"->new( %options );
417            
418 0         0 return $r;
419             } #/ sub _build_moose_coercion
420              
421             sub _codelike_type_coercion_map {
422 0     0   0 my $self = shift;
423 0         0 my $modifier = $_[0];
424            
425 0         0 my @orig = @{ $self->type_coercion_map };
  0         0  
426 0         0 my @new;
427            
428 0         0 while ( @orig ) {
429 0         0 my ( $type, $converter ) = splice( @orig, 0, 2 );
430            
431 0 0       0 push @new, $modifier ? $type->$modifier : $type;
432            
433 0 0       0 if ( Types::TypeTiny::is_CodeLike( $converter ) ) {
434 0         0 push @new, $converter;
435             }
436             else {
437 0         0 push @new, Eval::TypeTiny::eval_closure(
438             source => sprintf( 'sub { local $_ = $_[0]; %s }', $converter ),
439             description => sprintf( "temporary compiled converter from '%s'", $type ),
440             );
441             }
442             } #/ while ( @orig )
443            
444 0         0 return @new;
445             } #/ sub _codelike_type_coercion_map
446              
447             sub is_parameterizable {
448 1025     1025 1 3373 shift->has_coercion_generator;
449             }
450              
451             sub is_parameterized {
452 23     23 1 59 shift->has_parameters;
453             }
454              
455             sub parameterize {
456 7     7 1 2018 my $self = shift;
457 7 50       44 return $self unless @_;
458 7 50       38 $self->is_parameterizable
459             or _croak "Constraint '%s' does not accept parameters", "$self";
460            
461 7         40 @_ = map Types::TypeTiny::to_TypeTiny( $_ ), @_;
462            
463 7         32 return ref( $self )->new(
464             type_constraint => $self->type_constraint,
465             type_coercion_map =>
466             [ $self->coercion_generator->( $self, $self->type_constraint, @_ ) ],
467             parameters => \@_,
468             frozen => 1,
469             parameterized_from => $self,
470             );
471             } #/ sub parameterize
472              
473             sub _reparameterize {
474 7     7   28 my $self = shift;
475 7         19 my ( $target_type ) = @_;
476            
477 7 50       16 $self->is_parameterized or return $self;
478 7         21 my $parent = $self->parameterized_from;
479            
480             return ref( $self )->new(
481             type_constraint => $target_type,
482             type_coercion_map => [
483 7         50 $parent->coercion_generator->( $parent, $target_type, @{ $self->parameters } )
  7         30  
484             ],
485             parameters => \@_,
486             frozen => 1,
487             parameterized_from => $parent,
488             );
489             } #/ sub _reparameterize
490              
491             sub isa {
492 35     35 1 291 my $self = shift;
493            
494 35 0 33     125 if ( $INC{"Moose/Meta/TypeCoercion.pm"}
      33        
495             and blessed( $self )
496             and $_[0] eq 'Moose::Meta::TypeCoercion' )
497             {
498 0         0 return !!1;
499             }
500            
501 35 0 33     103 if ( $INC{"Moose/Meta/TypeCoercion.pm"}
      33        
502             and blessed( $self )
503             and $_[0] =~ /^(Class::MOP|MooseX?)::/ )
504             {
505 0         0 my $r = $self->moose_coercion->isa( @_ );
506 0 0       0 return $r if $r;
507             }
508            
509 35         311 $self->SUPER::isa( @_ );
510             } #/ sub isa
511              
512             sub can {
513 20     20 1 4736 my $self = shift;
514            
515 20         131 my $can = $self->SUPER::can( @_ );
516 20 100       132 return $can if $can;
517            
518 1 0 33     25 if ( $INC{"Moose/Meta/TypeCoercion.pm"}
      33        
519             and blessed( $self )
520             and my $method = $self->moose_coercion->can( @_ ) )
521             {
522 0     0   0 return sub { $method->( shift->moose_coercion, @_ ) };
  0         0  
523             }
524            
525 1         6 return;
526             } #/ sub can
527              
528             sub AUTOLOAD {
529 782     782   8609 my $self = shift;
530 782         6910 my ( $m ) = ( our $AUTOLOAD =~ /::(\w+)$/ );
531 782 50       24820 return if $m eq 'DESTROY';
532            
533 0 0 0     0 if ( $INC{"Moose/Meta/TypeCoercion.pm"}
      0        
534             and blessed( $self )
535             and my $method = $self->moose_coercion->can( $m ) )
536             {
537 0         0 return $method->( $self->moose_coercion, @_ );
538             }
539            
540 0   0     0 _croak q[Can't locate object method "%s" via package "%s"], $m,
541             ref( $self ) || $self;
542             } #/ sub AUTOLOAD
543              
544             # Private Moose method, but Moo uses this...
545             sub _compiled_type_coercion {
546 2     2   4 my $self = shift;
547 2 50       6 if ( @_ ) {
548 2         4 my $thing = $_[0];
549 2 100 66     32 if ( blessed( $thing ) and $thing->isa( "Type::Coercion" ) ) {
    50          
550 1         3 $self->add_type_coercions( @{ $thing->type_coercion_map } );
  1         2  
551             }
552             elsif ( Types::TypeTiny::is_CodeLike( $thing ) ) {
553 1         8 require Types::Standard;
554 1         7 $self->add_type_coercions( Types::Standard::Any(), $thing );
555             }
556             } #/ if ( @_ )
557 2         9 $self->compiled_coercion;
558             } #/ sub _compiled_type_coercion
559              
560             *compile_type_coercion = \&compiled_coercion;
561 1     1 1 4 sub meta { _croak( "Not really a Moose::Meta::TypeCoercion. Sorry!" ) }
562              
563             1;
564              
565             __END__