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 14     14   319 use 5.008001;
  14         56  
6 14     14   95 use strict;
  14         34  
  14         320  
7 14     14   69 use warnings;
  14         35  
  14         769  
8              
9             BEGIN {
10 14     14   63 $Types::Standard::Tuple::AUTHORITY = 'cpan:TOBYINK';
11 14         523 $Types::Standard::Tuple::VERSION = '2.003_000';
12             }
13              
14             $Types::Standard::Tuple::VERSION =~ tr/_//d;
15              
16 14     14   114 use Type::Tiny ();
  14         34  
  14         250  
17 14     14   92 use Types::Standard ();
  14         42  
  14         252  
18 14     14   70 use Types::TypeTiny ();
  14         28  
  14         1225  
19              
20 2     2   18 sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak }
  2         8  
21              
22             my $_Optional = Types::Standard::Optional;
23             my $_Slurpy = Types::Standard::Slurpy;
24              
25 14     14   95 no warnings;
  14         39  
  14         29691  
26              
27             sub __constraint_generator {
28 56 100 100 56   1187 my $slurpy =
29             @_
30             && Types::TypeTiny::is_TypeTiny( $_[-1] )
31             && $_[-1]->is_strictly_a_type_of( $_Slurpy )
32             ? pop
33             : undef;
34            
35 56         208 my @constraints = @_;
36 56         158 for ( @constraints ) {
37 101 100       1953 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 55         183 my @xsub;
44 55 100       190 if ( Type::Tiny::_USE_XS and !$slurpy ) {
45             my @known = map {
46 37         91 my $known;
  71         111  
47 71 100       180 $known = Type::Tiny::XS::is_known( $_->compiled_check )
48             unless $_->is_strictly_a_type_of( $_Optional );
49 71 100       610 defined( $known ) ? $known : ();
50             } @constraints;
51            
52 37 100       134 if ( @known == @constraints ) {
53 24         183 my $xsub = Type::Tiny::XS::get_coderef_for(
54             sprintf( "Tuple[%s]", join( ',', @known ) ) );
55 24 100       1544 push @xsub, $xsub if $xsub;
56             }
57             } #/ if ( Type::Tiny::_USE_XS...)
58            
59 55         233 my @is_optional = map !!$_->is_strictly_a_type_of( $_Optional ), @constraints;
60 55   100     253 my $slurp_hash = $slurpy && $slurpy->my_slurp_into eq 'HASH';
61 55   66     168 my $slurp_any = $slurpy && $slurpy->my_unslurpy->equals( Types::Standard::Any );
62            
63 55         270 my @sorted_is_optional = sort @is_optional;
64 55 100       242 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   217 my $value = $_[0];
70 123 100       372 if ( $#constraints < $#$value ) {
71 45 100       215 return !!0 unless $slurpy;
72 37         82 my $tmp;
73 37 100       127 if ( $slurp_hash ) {
    50          
74 11 100       76 ( $#$value - $#constraints + 1 ) % 2 or return;
75 6         46 $tmp = +{ @$value[ $#constraints + 1 .. $#$value ] };
76 6 100       25 $slurpy->check( $tmp ) or return;
77             }
78             elsif ( not $slurp_any ) {
79 26         107 $tmp = +[ @$value[ $#constraints + 1 .. $#$value ] ];
80 26 100       86 $slurpy->check( $tmp ) or return;
81             }
82             } #/ if ( $#constraints < $#$value)
83 93         434 for my $i ( 0 .. $#constraints ) {
84 198 100       650 ( $i > $#$value )
85             and return !!$is_optional[$i];
86            
87 180 100       487 $constraints[$i]->check( $value->[$i] )
88             or return !!0;
89             }
90 58         308 return !!1;
91 54         637 }, @xsub;
92             } #/ sub __constraint_generator
93              
94             sub __inline_generator {
95 54 100 100 54   1197 my $slurpy =
96             @_
97             && Types::TypeTiny::is_TypeTiny( $_[-1] )
98             && $_[-1]->is_strictly_a_type_of( $_Slurpy )
99             ? pop
100             : undef;
101 54         217 my @constraints = @_;
102            
103 54 100       148 return if grep { not $_->can_be_inlined } @constraints;
  98         257  
104 51 100 100     232 return if defined $slurpy && !$slurpy->can_be_inlined;
105            
106 50         106 my $xsubname;
107 50 100       138 if ( Type::Tiny::_USE_XS and !$slurpy ) {
108             my @known = map {
109 33         106 my $known;
  62         96  
110 62 100       253 $known = Type::Tiny::XS::is_known( $_->compiled_check )
111             unless $_->is_strictly_a_type_of( $_Optional );
112 62 100       491 defined( $known ) ? $known : ();
113             } @constraints;
114            
115 33 100       125 if ( @known == @constraints ) {
116 24         211 $xsubname = Type::Tiny::XS::get_subname_for(
117             sprintf( "Tuple[%s]", join( ',', @known ) ) );
118             }
119             } #/ if ( Type::Tiny::_USE_XS...)
120            
121 50         327 my $tmpl = "do { my \$tmp = +[\@{%s}[%d..\$#{%s}]]; %s }";
122 50         82 my $slurpy_any;
123 50 100       160 if ( defined $slurpy ) {
124 17 100       113 $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       112 $slurpy_any = 1
130             if $slurpy->my_unslurpy->equals( Types::Standard::Any );
131             }
132            
133 50         215 my @is_optional = map !!$_->is_strictly_a_type_of( $_Optional ), @constraints;
134 50         185 my $min = 0+ grep !$_, @is_optional;
135            
136             return sub {
137 425     425   778 my $v = $_[1];
138 425 100 100     1535 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         582 "(\@{$v} == $min or (\@{$v} > $min and \@{$v} <= ${\(1+$#constraints)}) or (\@{$v} > ${\(1+$#constraints)} and %s))",
  165         888  
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     893 my $inline = $constraints[$_]->inline_check( "$v\->[$_]" );
  613 50       2166  
    100          
158 613 100       3173 $inline eq '(!!1)' ? ()
    100          
159             : (
160             $is_optional[$_] ? sprintf( '(@{%s} <= %d or %s)', $v, $_, $inline )
161             : $inline
162             );
163             } 0 .. $#constraints;
164 50         690 };
165             } #/ sub __inline_generator
166              
167             sub __deep_explanation {
168 4     4   9 my ( $type, $value, $varname ) = @_;
169            
170 4         6 my @constraints = @{ $type->parameters };
  4         12  
171 4 100 66     100 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         12 @constraints = map Types::TypeTiny::to_TypeTiny( $_ ), @constraints;
178            
179 4 100 100     19 if ( @constraints < @$value and not $slurpy ) {
180             return [
181 2         10 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     9 if $constraints[$i]
191             ->is_strictly_a_type_of( Types::Standard::Optional )
192             && $i > $#$value;
193 2 100       7 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         39 $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       11 if ( defined( $slurpy ) ) {
208 1 50       7 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     6 @{ ( $slurpy->my_unslurpy || $slurpy )->validate_explain( $tmp, '$SLURPY' ) },
  1 50 33     6  
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 15     15   59 my ( $parent, $child, @tuple ) = @_;
232            
233 15 100 66     385 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 15         51 my $child_coercions_exist = 0;
241 15         40 my $all_inlinable = 1;
242 15 100       57 for my $tc ( @tuple, ( $slurpy ? $slurpy : () ) ) {
243 33 100       89 $all_inlinable = 0 if !$tc->can_be_inlined;
244 33 100 100     122 $all_inlinable = 0 if $tc->has_coercion && !$tc->coercion->can_be_inlined;
245 33 100       100 $child_coercions_exist++ if $tc->has_coercion;
246             }
247            
248 15 100       71 return unless $child_coercions_exist;
249 10         40 my $C = "Type::Coercion"->new( type_constraint => $child );
250            
251 10   100     26 my $slurpy_is_hashref = $slurpy && $slurpy->my_slurp_into eq 'HASH';
252            
253 10 100       24 if ( $all_inlinable ) {
254             $C->add_type_coercions(
255             $parent => Types::Standard::Stringable {
256 3     3   12 my $label = sprintf( "TUPLELABEL%d", ++$label_counter );
257 3         5 my @code;
258 3         6 push @code, 'do { my ($orig, $return_orig, $tmp, @new) = ($_, 0);';
259 3         6 push @code, "$label: {";
260 3 100       36 push @code,
261             sprintf(
262             '(($return_orig = 1), last %s) if @$orig > %d;', $label,
263             scalar @tuple
264             ) unless $slurpy;
265 3         12 for my $i ( 0 .. $#tuple ) {
266 4         7 my $ct = $tuple[$i];
267 4         13 my $ct_coerce = $ct->has_coercion;
268 4         15 my $ct_optional = $ct->is_a_type_of( Types::Standard::Optional );
269            
270 4 50       33 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       12 if ( $slurpy ) {
282 1         4 my $size = @tuple;
283 1         4 push @code, sprintf( 'if (@$orig > %d) {', $size );
284 1 50       8 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       4 push @code,
293             $slurpy->has_coercion
294             ? sprintf(
295             '$tail = %s;',
296             $slurpy->coercion->inline_coercion( '$tail' )
297             )
298             : q();
299 1 50       14 push @code, sprintf(
300             '(%s) ? push(@new, %s$tail) : ($return_orig++);',
301             $slurpy->inline_check( '$tail' ),
302             ( $slurpy_is_hashref ? '%' : '@' ),
303             );
304 1         7 push @code, '}';
305             } #/ if ( $slurpy )
306 3         6 push @code, '}';
307 3         5 push @code, '$return_orig ? $orig : \\@new';
308 3         5 push @code, '}';
309 3         63 "@code";
310             }
311 4         40 );
312             } #/ if ( $all_inlinable )
313            
314             else {
315 6         34 my @is_optional = map !!$_->is_strictly_a_type_of( $_Optional ), @tuple;
316            
317             $C->add_type_coercions(
318             $parent => sub {
319 6 50   6   157 my $value = @_ ? $_[0] : $_;
320            
321 6 100 100     30 if ( !$slurpy and @$value > @tuple ) {
322 2         13 return $value;
323             }
324            
325 4         8 my @new;
326 4         15 for my $i ( 0 .. $#tuple ) {
327 8 0 33     26 return \@new if $i > $#$value and $is_optional[$i];
328            
329 8         18 my $ct = $tuple[$i];
330 8 100       22 my $x = $ct->has_coercion ? $ct->coerce( $value->[$i] ) : $value->[$i];
331            
332 8 50       138 return $value unless $ct->check( $x );
333            
334 8         43 $new[$i] = $x;
335             } #/ for my $i ( 0 .. $#tuple)
336            
337 4 100 66     14 if ( $slurpy and @$value > @tuple ) {
338 14     14   147 no warnings;
  14         53  
  14         2493  
339             my $tmp =
340             $slurpy_is_hashref
341 2         8 ? { @{$value}[ @tuple .. $#$value ] }
342 3 100       21 : [ @{$value}[ @tuple .. $#$value ] ];
  1         4  
343 3 50       13 $tmp = $slurpy->coerce( $tmp ) if $slurpy->has_coercion;
344 3 100       17 $slurpy->check( $tmp )
    50          
345             ? push( @new, $slurpy_is_hashref ? %$tmp : @$tmp )
346             : return ( $value );
347             } #/ if ( $slurpy and @$value...)
348            
349 4         55 return \@new;
350             },
351 6         70 );
352             } #/ else [ if ( $all_inlinable ) ]
353            
354 10         44 return $C;
355             } #/ sub __coercion_generator
356              
357             1;