File Coverage

lib/Types/Standard/Tuple.pm
Criterion Covered Total %
statement 158 159 100.0
branch 110 128 85.9
condition 42 54 77.7
subroutine 18 18 100.0
pod n/a
total 328 359 91.6


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