File Coverage

lib/Types/Standard/Tuple.pm
Criterion Covered Total %
statement 178 179 100.0
branch 118 140 84.2
condition 44 60 73.3
subroutine 21 21 100.0
pod n/a
total 361 400 90.5


line stmt bran cond sub pod time code
1             # INTERNAL MODULE: guts for Tuple type from Types::Standard.
2              
3             package Types::Standard::Tuple;
4              
5 15     15   1113 use 5.008001;
  15         67  
6 15     15   97 use strict;
  15         34  
  15         460  
7 15     15   118 use warnings;
  15         31  
  15         1413  
8              
9             BEGIN {
10 15     15   54 $Types::Standard::Tuple::AUTHORITY = 'cpan:TOBYINK';
11 15         800 $Types::Standard::Tuple::VERSION = '2.010001';
12             }
13              
14             $Types::Standard::Tuple::VERSION =~ tr/_//d;
15              
16 15     15   125 use Type::Tiny ();
  15         53  
  15         363  
17 15     15   78 use Types::Standard ();
  15         35  
  15         340  
18 15     15   70 use Types::TypeTiny ();
  15         59  
  15         1781  
19              
20 2     2   22 sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak }
  2         10  
21              
22             my $_Optional = Types::Standard::Optional;
23             my $_Slurpy = Types::Standard::Slurpy;
24              
25 15     15   101 use Exporter::Tiny 1.004001 ();
  15         347  
  15         7291  
26             our @ISA = qw( Exporter::Tiny );
27              
28             sub _exporter_fail {
29 2     2   464 my ( $class, $type_name, $values, $globals ) = @_;
30 2         6 my $caller = $globals->{into};
31            
32 2         6 my @final;
33             {
34 2         3 my $to_type = sub {
35 4 100   4   163 return $_[0] if Types::TypeTiny::is_TypeTiny($_[0]);
36 2         701 require Type::Utils;
37 2         10 Type::Utils::dwim_type( $_[0], for => 'caller' );
38 2         9 };
39 2         6 my $of = $values->{of};
40 2 50       46 Types::TypeTiny::is_ArrayLike($of)
41             or _croak( qq{Expected arrayref option "of" for type "$type_name"} );
42 2         7 @final = map { $to_type->($_) } @$of;
  4         44  
43             }
44            
45 2         41 my $type = Types::Standard::Tuple->of( @final );
46             $type = $type->create_child_type(
47             name => $type_name,
48             $type->has_coercion ? ( coercion => 1 ) : (),
49 2 50       10 exists( $values->{where} ) ? ( constraint => $values->{where} ) : (),
    50          
50             );
51            
52             $INC{'Type/Registry.pm'}
53             ? 'Type::Registry'->for_class( $caller )->add_type( $type, $type_name )
54             : ( $Type::Registry::DELAYED{$caller}{$type_name} = $type )
55 2 100 33     68 unless( ref($caller) or $caller eq '-lexical' or $globals->{'lexical'} );
    50 33        
56 2         5 return map +( $_->{name} => $_->{code} ), @{ $type->exportables };
  2         10  
57             }
58              
59 15     15   129 no warnings;
  15         53  
  15         45475  
60              
61             sub __constraint_generator {
62 57 100 100 57   1535 my $slurpy =
63             @_
64             && Types::TypeTiny::is_TypeTiny( $_[-1] )
65             && $_[-1]->is_strictly_a_type_of( $_Slurpy )
66             ? pop
67             : undef;
68            
69 57         295 my @constraints = @_;
70 57         183 for ( @constraints ) {
71 99 100       13347 Types::TypeTiny::is_TypeTiny( $_ )
72             or
73             _croak( "Parameters to Tuple[...] expected to be type constraints; got $_" );
74             }
75            
76             # By god, the Type::Tiny::XS API is currently horrible
77 56         174 my @xsub;
78 56 100       212 if ( Type::Tiny::_USE_XS and !$slurpy ) {
79             my @known = map {
80 38         130 my $known;
  69         117  
81 69 100       226 $known = Type::Tiny::XS::is_known( $_->compiled_check )
82             unless $_->is_strictly_a_type_of( $_Optional );
83 69 100       760 defined( $known ) ? $known : ();
84             } @constraints;
85            
86 38 100       174 if ( @known == @constraints ) {
87 25         208 my $xsub = Type::Tiny::XS::get_coderef_for(
88             sprintf( "Tuple[%s]", join( ',', @known ) ) );
89 25 100       1863 push @xsub, $xsub if $xsub;
90             }
91             } #/ if ( Type::Tiny::_USE_XS...)
92            
93 56         347 my @is_optional = map !!$_->is_strictly_a_type_of( $_Optional ), @constraints;
94 56   100     243 my $slurp_hash = $slurpy && $slurpy->my_slurp_into eq 'HASH';
95 56   66     225 my $slurp_any = $slurpy && $slurpy->my_unslurpy->equals( Types::Standard::Any );
96            
97 56         266 my @sorted_is_optional = sort @is_optional;
98 56 100       335 join( "|", @sorted_is_optional ) eq join( "|", @is_optional )
99             or _croak(
100             "Optional parameters to Tuple[...] cannot precede required parameters" );
101            
102             sub {
103 123     123   282 my $value = $_[0];
104 123 100       517 if ( $#constraints < $#$value ) {
105 45 100       305 return !!0 unless $slurpy;
106 37         97 my $tmp;
107 37 100       197 if ( $slurp_hash ) {
    50          
108 11 100       64 ( $#$value - $#constraints + 1 ) % 2 or return;
109 6         38 $tmp = +{ @$value[ $#constraints + 1 .. $#$value ] };
110 6 100       22 $slurpy->check( $tmp ) or return;
111             }
112             elsif ( not $slurp_any ) {
113 26         134 $tmp = +[ @$value[ $#constraints + 1 .. $#$value ] ];
114 26 100       115 $slurpy->check( $tmp ) or return;
115             }
116             } #/ if ( $#constraints < $#$value)
117 93         501 for my $i ( 0 .. $#constraints ) {
118 198 100       783 ( $i > $#$value )
119             and return !!$is_optional[$i];
120            
121 180 100       648 $constraints[$i]->check( $value->[$i] )
122             or return !!0;
123             }
124 58         349 return !!1;
125 55         791 }, @xsub;
126             } #/ sub __constraint_generator
127              
128             sub __inline_generator {
129 55 100 100 55   1674 my $slurpy =
130             @_
131             && Types::TypeTiny::is_TypeTiny( $_[-1] )
132             && $_[-1]->is_strictly_a_type_of( $_Slurpy )
133             ? pop
134             : undef;
135 55         491 my @constraints = @_;
136            
137 55 100       146 return if grep { not $_->can_be_inlined } @constraints;
  96         3068  
138 52 100 100     306 return if defined $slurpy && !$slurpy->can_be_inlined;
139            
140 51         115 my $xsubname;
141 51 100       192 if ( Type::Tiny::_USE_XS and !$slurpy ) {
142             my @known = map {
143 34         107 my $known;
  60         146  
144 60 100       204 $known = Type::Tiny::XS::is_known( $_->compiled_check )
145             unless $_->is_strictly_a_type_of( $_Optional );
146 60 100       680 defined( $known ) ? $known : ();
147             } @constraints;
148            
149 34 100       180 if ( @known == @constraints ) {
150 25         286 $xsubname = Type::Tiny::XS::get_subname_for(
151             sprintf( "Tuple[%s]", join( ',', @known ) ) );
152             }
153             } #/ if ( Type::Tiny::_USE_XS...)
154            
155 51         386 my $tmpl = "do { my \$tmp = +[\@{%s}[%d..\$#{%s}]]; %s }";
156 51         112 my $slurpy_any;
157 51 100       286 if ( defined $slurpy ) {
158 17 100       151 $tmpl =
159             'do { my ($orig, $from, $to) = (%s, %d, $#{%s});'
160             . '(($to-$from) %% 2) and do { my $tmp = +{@{$orig}[$from..$to]}; %s }'
161             . '}'
162             if $slurpy->my_slurp_into eq 'HASH';
163 17 50       130 $slurpy_any = 1
164             if $slurpy->my_unslurpy->equals( Types::Standard::Any );
165             }
166            
167 51         263 my @is_optional = map !!$_->is_strictly_a_type_of( $_Optional ), @constraints;
168 51         252 my $min = 0+ grep !$_, @is_optional;
169            
170             return sub {
171 428     428   955 my $v = $_[1];
172 428 100 100     1931 return "$xsubname\($v\)" if $xsubname && !$Type::Tiny::AvoidCallbacks;
173             join " and ",
174             Types::Standard::ArrayRef->inline_check( $v ),
175             (
176             ( scalar @constraints == $min and not $slurpy )
177             ? "\@{$v} == $min"
178             : sprintf(
179 165         500 "(\@{$v} == $min or (\@{$v} > $min and \@{$v} <= ${\(1+$#constraints)}) or (\@{$v} > ${\(1+$#constraints)} and %s))",
  165         1334  
180             (
181             $slurpy_any ? '!!1'
182             : (
183             $slurpy
184             ? sprintf( $tmpl, $v, $#constraints + 1, $v, $slurpy->inline_check( '$tmp' ) )
185             : sprintf( "\@{$v} <= %d", scalar @constraints )
186             )
187             ),
188             )
189             ),
190             map {
191 268 100 100     1180 my $inline = $constraints[$_]->inline_check( "$v\->[$_]" );
  613 50       2533  
    100          
192 613 100       3943 $inline eq '(!!1)' ? ()
    100          
193             : (
194             $is_optional[$_] ? sprintf( '(@{%s} <= %d or %s)', $v, $_, $inline )
195             : $inline
196             );
197             } 0 .. $#constraints;
198 51         857 };
199             } #/ sub __inline_generator
200              
201             sub __deep_explanation {
202 4     4   19 my ( $type, $value, $varname ) = @_;
203            
204 4         7 my @constraints = @{ $type->parameters };
  4         17  
205 4 100 66     167 my $slurpy =
206             @constraints
207             && Types::TypeTiny::is_TypeTiny( $constraints[-1] )
208             && $constraints[-1]->is_strictly_a_type_of( $_Slurpy )
209             ? pop( @constraints )
210             : undef;
211 4         21 @constraints = map Types::TypeTiny::to_TypeTiny( $_ ), @constraints;
212            
213 4 100 100     26 if ( @constraints < @$value and not $slurpy ) {
214             return [
215 2         16 sprintf(
216             '"%s" expects at most %d values in the array', $type, scalar( @constraints )
217             ),
218             sprintf( '%d values found; too many', scalar( @$value ) ),
219             ];
220             }
221            
222 2         28 for my $i ( 0 .. $#constraints ) {
223             next
224 2 50 33     14 if $constraints[$i]
225             ->is_strictly_a_type_of( Types::Standard::Optional )
226             && $i > $#$value;
227 2 100       12 next if $constraints[$i]->check( $value->[$i] );
228            
229             return [
230             sprintf(
231             '"%s" constrains value at index %d of array with "%s"', $type, $i,
232             $constraints[$i]
233             ),
234             @{
235 1         15 $constraints[$i]
  1         7  
236             ->validate_explain( $value->[$i], sprintf( '%s->[%s]', $varname, $i ) )
237             },
238             ];
239             } #/ for my $i ( 0 .. $#constraints)
240            
241 1 50       15 if ( defined( $slurpy ) ) {
242 1 50       13 my $tmp =
243             $slurpy->my_slurp_into eq 'HASH'
244             ? +{ @$value[ $#constraints + 1 .. $#$value ] }
245             : +[ @$value[ $#constraints + 1 .. $#$value ] ];
246             $slurpy->check( $tmp )
247             or return [
248             sprintf(
249             'Array elements from index %d are slurped into a %s which is constrained with "%s"',
250             $#constraints + 1,
251             ( $slurpy->my_slurp_into eq 'HASH' ) ? 'hashref' : 'arrayref',
252             ( $slurpy->my_unslurpy || $slurpy ),
253             ),
254 1 50 33     6 @{ ( $slurpy->my_unslurpy || $slurpy )->validate_explain( $tmp, '$SLURPY' ) },
  1 50 33     7  
255             ];
256             } #/ if ( defined( $slurpy ...))
257            
258             # This should never happen...
259 0         0 return; # uncoverable statement
260             } #/ sub __deep_explanation
261              
262             my $label_counter = 0;
263              
264             sub __coercion_generator {
265 15     15   67 my ( $parent, $child, @tuple ) = @_;
266            
267 15 100 66     513 my $slurpy =
268             @tuple
269             && Types::TypeTiny::is_TypeTiny( $tuple[-1] )
270             && $tuple[-1]->is_strictly_a_type_of( $_Slurpy )
271             ? pop( @tuple )
272             : undef;
273            
274 15         41 my $child_coercions_exist = 0;
275 15         35 my $all_inlinable = 1;
276 15 100       59 for my $tc ( @tuple, ( $slurpy ? $slurpy : () ) ) {
277 29 100       86 $all_inlinable = 0 if !$tc->can_be_inlined;
278 29 100 100     93 $all_inlinable = 0 if $tc->has_coercion && !$tc->coercion->can_be_inlined;
279 29 100       72 $child_coercions_exist++ if $tc->has_coercion;
280             }
281            
282 15 100       69 return unless $child_coercions_exist;
283 10         51 my $C = "Type::Coercion"->new( type_constraint => $child );
284            
285 10   100     45 my $slurpy_is_hashref = $slurpy && $slurpy->my_slurp_into eq 'HASH';
286            
287 10 100       30 if ( $all_inlinable ) {
288             $C->add_type_coercions(
289             $parent => Types::Standard::Stringable {
290 3     3   12 my $label = sprintf( "TUPLELABEL%d", ++$label_counter );
291 3         7 my @code;
292 3         8 push @code, 'do { my ($orig, $return_orig, $tmp, @new) = ($_, 0);';
293 3         11 push @code, "$label: {";
294 3 100       15 push @code,
295             sprintf(
296             '(($return_orig = 1), last %s) if @$orig > %d;', $label,
297             scalar @tuple
298             ) unless $slurpy;
299 3         13 for my $i ( 0 .. $#tuple ) {
300 4         9 my $ct = $tuple[$i];
301 4         9 my $ct_coerce = $ct->has_coercion;
302 4         23 my $ct_optional = $ct->is_a_type_of( Types::Standard::Optional );
303            
304 4 50       17 push @code, sprintf(
305             'if (@$orig > %d) { $tmp = %s; (%s) ? ($new[%d]=$tmp) : (($return_orig=1), last %s) }',
306             $i,
307             $ct_coerce
308             ? $ct->coercion->inline_coercion( "\$orig->[$i]" )
309             : "\$orig->[$i]",
310             $ct->inline_check( '$tmp' ),
311             $i,
312             $label,
313             );
314             } #/ for my $i ( 0 .. $#tuple)
315 3 100       10 if ( $slurpy ) {
316 1         3 my $size = @tuple;
317 1         3 push @code, sprintf( 'if (@$orig > %d) {', $size );
318 1 50       6 push @code, sprintf(
319             (
320             $slurpy_is_hashref
321             ? 'my $tail = do { no warnings; +{ @{$orig}[%d .. $#$orig]} };'
322             : 'my $tail = [ @{$orig}[%d .. $#$orig] ];'
323             ),
324             $size,
325             );
326 1 50       4 push @code,
327             $slurpy->has_coercion
328             ? sprintf(
329             '$tail = %s;',
330             $slurpy->coercion->inline_coercion( '$tail' )
331             )
332             : q();
333 1 50       4 push @code, sprintf(
334             '(%s) ? push(@new, %s$tail) : ($return_orig++);',
335             $slurpy->inline_check( '$tail' ),
336             ( $slurpy_is_hashref ? '%' : '@' ),
337             );
338 1         4 push @code, '}';
339             } #/ if ( $slurpy )
340 3         8 push @code, '}';
341 3         7 push @code, '$return_orig ? $orig : \\@new';
342 3         7 push @code, '}';
343 3         28 "@code";
344             }
345 4         57 );
346             } #/ if ( $all_inlinable )
347            
348             else {
349 6         22 my @is_optional = map !!$_->is_strictly_a_type_of( $_Optional ), @tuple;
350            
351             $C->add_type_coercions(
352             $parent => sub {
353 6 50   6   134 my $value = @_ ? $_[0] : $_;
354            
355 6 100 100     35 if ( !$slurpy and @$value > @tuple ) {
356 2         19 return $value;
357             }
358            
359 4         12 my @new;
360 4         38 for my $i ( 0 .. $#tuple ) {
361 8 0 33     23 return \@new if $i > $#$value and $is_optional[$i];
362            
363 8         25 my $ct = $tuple[$i];
364 8 100       23 my $x = $ct->has_coercion ? $ct->coerce( $value->[$i] ) : $value->[$i];
365            
366 8 50       95 return $value unless $ct->check( $x );
367            
368 8         26 $new[$i] = $x;
369             } #/ for my $i ( 0 .. $#tuple)
370            
371 4 100 66     10 if ( $slurpy and @$value > @tuple ) {
372 15     15   143 no warnings;
  15         32  
  15         4223  
373             my $tmp =
374             $slurpy_is_hashref
375 2         6 ? { @{$value}[ @tuple .. $#$value ] }
376 3 100       12 : [ @{$value}[ @tuple .. $#$value ] ];
  1         3  
377 3 50       9 $tmp = $slurpy->coerce( $tmp ) if $slurpy->has_coercion;
378 3 100       9 $slurpy->check( $tmp )
    50          
379             ? push( @new, $slurpy_is_hashref ? %$tmp : @$tmp )
380             : return ( $value );
381             } #/ if ( $slurpy and @$value...)
382            
383 4         51 return \@new;
384             },
385 6         83 );
386             } #/ else [ if ( $all_inlinable ) ]
387            
388 10         36 return $C;
389             } #/ sub __coercion_generator
390              
391             1;
392              
393             __END__