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 12     12   272 use 5.008001;
  12         44  
6 12     12   84 use strict;
  12         27  
  12         282  
7 12     12   58 use warnings;
  12         25  
  12         675  
8              
9             BEGIN {
10 12     12   46 $Types::Standard::Tuple::AUTHORITY = 'cpan:TOBYINK';
11 12         506 $Types::Standard::Tuple::VERSION = '2.002001';
12             }
13              
14             $Types::Standard::Tuple::VERSION =~ tr/_//d;
15              
16 12     12   77 use Type::Tiny ();
  12         37  
  12         235  
17 12     12   73 use Types::Standard ();
  12         39  
  12         248  
18 12     12   63 use Types::TypeTiny ();
  12         38  
  12         1151  
19              
20 2     2   21 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 12     12   88 no warnings;
  12         27  
  12         26337  
26              
27             sub __constraint_generator {
28 54 100 100 54   1134 my $slurpy =
29             @_
30             && Types::TypeTiny::is_TypeTiny( $_[-1] )
31             && $_[-1]->is_strictly_a_type_of( $_Slurpy )
32             ? pop
33             : undef;
34            
35 54         195 my @constraints = @_;
36 54         143 for ( @constraints ) {
37 93 100       1736 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 53         113 my @xsub;
44 53 100       150 if ( Type::Tiny::_USE_XS and !$slurpy ) {
45             my @known = map {
46 35         77 my $known;
  63         101  
47 63 100       172 $known = Type::Tiny::XS::is_known( $_->compiled_check )
48             unless $_->is_strictly_a_type_of( $_Optional );
49 63 100       508 defined( $known ) ? $known : ();
50             } @constraints;
51            
52 35 100       141 if ( @known == @constraints ) {
53 23         173 my $xsub = Type::Tiny::XS::get_coderef_for(
54             sprintf( "Tuple[%s]", join( ',', @known ) ) );
55 23 100       1448 push @xsub, $xsub if $xsub;
56             }
57             } #/ if ( Type::Tiny::_USE_XS...)
58            
59 53         205 my @is_optional = map !!$_->is_strictly_a_type_of( $_Optional ), @constraints;
60 53   100     187 my $slurp_hash = $slurpy && $slurpy->my_slurp_into eq 'HASH';
61 53   66     186 my $slurp_any = $slurpy && $slurpy->my_unslurpy->equals( Types::Standard::Any );
62            
63 53         298 my @sorted_is_optional = sort @is_optional;
64 53 100       215 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   257 my $value = $_[0];
70 123 100       399 if ( $#constraints < $#$value ) {
71 45 100       196 return !!0 unless $slurpy;
72 37         74 my $tmp;
73 37 100       141 if ( $slurp_hash ) {
    50          
74 11 100       68 ( $#$value - $#constraints + 1 ) % 2 or return;
75 6         46 $tmp = +{ @$value[ $#constraints + 1 .. $#$value ] };
76 6 100       22 $slurpy->check( $tmp ) or return;
77             }
78             elsif ( not $slurp_any ) {
79 26         92 $tmp = +[ @$value[ $#constraints + 1 .. $#$value ] ];
80 26 100       80 $slurpy->check( $tmp ) or return;
81             }
82             } #/ if ( $#constraints < $#$value)
83 93         419 for my $i ( 0 .. $#constraints ) {
84 198 100       628 ( $i > $#$value )
85             and return !!$is_optional[$i];
86            
87 180 100       437 $constraints[$i]->check( $value->[$i] )
88             or return !!0;
89             }
90 58         293 return !!1;
91 52         524 }, @xsub;
92             } #/ sub __constraint_generator
93              
94             sub __inline_generator {
95 52 100 100 52   1123 my $slurpy =
96             @_
97             && Types::TypeTiny::is_TypeTiny( $_[-1] )
98             && $_[-1]->is_strictly_a_type_of( $_Slurpy )
99             ? pop
100             : undef;
101 52         165 my @constraints = @_;
102            
103 52 100       121 return if grep { not $_->can_be_inlined } @constraints;
  90         241  
104 49 100 100     417 return if defined $slurpy && !$slurpy->can_be_inlined;
105            
106 48         225 my $xsubname;
107 48 100       142 if ( Type::Tiny::_USE_XS and !$slurpy ) {
108             my @known = map {
109 31         62 my $known;
  54         84  
110 54 100       145 $known = Type::Tiny::XS::is_known( $_->compiled_check )
111             unless $_->is_strictly_a_type_of( $_Optional );
112 54 100       409 defined( $known ) ? $known : ();
113             } @constraints;
114            
115 31 100       122 if ( @known == @constraints ) {
116 23         148 $xsubname = Type::Tiny::XS::get_subname_for(
117             sprintf( "Tuple[%s]", join( ',', @known ) ) );
118             }
119             } #/ if ( Type::Tiny::_USE_XS...)
120            
121 48         318 my $tmpl = "do { my \$tmp = +[\@{%s}[%d..\$#{%s}]]; %s }";
122 48         85 my $slurpy_any;
123 48 100       129 if ( defined $slurpy ) {
124 17 100       134 $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       129 $slurpy_any = 1
130             if $slurpy->my_unslurpy->equals( Types::Standard::Any );
131             }
132            
133 48         177 my @is_optional = map !!$_->is_strictly_a_type_of( $_Optional ), @constraints;
134 48         175 my $min = 0+ grep !$_, @is_optional;
135            
136             return sub {
137 421     421   743 my $v = $_[1];
138 421 100 100     1509 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         516 "(\@{$v} == $min or (\@{$v} > $min and \@{$v} <= ${\(1+$#constraints)}) or (\@{$v} > ${\(1+$#constraints)} and %s))",
  165         819  
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 266 100 100     796 my $inline = $constraints[$_]->inline_check( "$v\->[$_]" );
  609 50       2156  
    100          
158 609 100       3043 $inline eq '(!!1)' ? ()
    100          
159             : (
160             $is_optional[$_] ? sprintf( '(@{%s} <= %d or %s)', $v, $_, $inline )
161             : $inline
162             );
163             } 0 .. $#constraints;
164 48         590 };
165             } #/ sub __inline_generator
166              
167             sub __deep_explanation {
168 4     4   11 my ( $type, $value, $varname ) = @_;
169            
170 4         6 my @constraints = @{ $type->parameters };
  4         10  
171 4 100 66     102 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     21 if ( @constraints < @$value and not $slurpy ) {
180             return [
181 2         9 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         20 for my $i ( 0 .. $#constraints ) {
189             next
190 2 50 33     10 if $constraints[$i]
191             ->is_strictly_a_type_of( Types::Standard::Optional )
192             && $i > $#$value;
193 2 100       6 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         13 $constraints[$i]
  1         6  
202             ->validate_explain( $value->[$i], sprintf( '%s->[%s]', $varname, $i ) )
203             },
204             ];
205             } #/ for my $i ( 0 .. $#constraints)
206            
207 1 50       12 if ( defined( $slurpy ) ) {
208 1 50       6 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     5  
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 13     13   40 my ( $parent, $child, @tuple ) = @_;
232            
233 13 100 66     308 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 13         27 my $child_coercions_exist = 0;
241 13         23 my $all_inlinable = 1;
242 13 100       63 for my $tc ( @tuple, ( $slurpy ? $slurpy : () ) ) {
243 25 100       63 $all_inlinable = 0 if !$tc->can_be_inlined;
244 25 100 100     71 $all_inlinable = 0 if $tc->has_coercion && !$tc->coercion->can_be_inlined;
245 25 100       67 $child_coercions_exist++ if $tc->has_coercion;
246             }
247            
248 13 100       40 return unless $child_coercions_exist;
249 10         41 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       27 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         4 my @code;
258 3         7 push @code, 'do { my ($orig, $return_orig, $tmp, @new) = ($_, 0);';
259 3         9 push @code, "$label: {";
260 3 100       13 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         11 my $ct_coerce = $ct->has_coercion;
268 4         18 my $ct_optional = $ct->is_a_type_of( Types::Standard::Optional );
269            
270 4 50       15 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       14 if ( $slurpy ) {
282 1         4 my $size = @tuple;
283 1         5 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       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         5 push @code, '}';
307 3         8 push @code, '$return_orig ? $orig : \\@new';
308 3         6 push @code, '}';
309 3         83 "@code";
310             }
311 4         47 );
312             } #/ if ( $all_inlinable )
313            
314             else {
315 6         18 my @is_optional = map !!$_->is_strictly_a_type_of( $_Optional ), @tuple;
316            
317             $C->add_type_coercions(
318             $parent => sub {
319 6 50   6   146 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     24 return \@new if $i > $#$value and $is_optional[$i];
328            
329 8         16 my $ct = $tuple[$i];
330 8 100       23 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         48 $new[$i] = $x;
335             } #/ for my $i ( 0 .. $#tuple)
336            
337 4 100 66     16 if ( $slurpy and @$value > @tuple ) {
338 12     12   122 no warnings;
  12         48  
  12         2142  
339             my $tmp =
340             $slurpy_is_hashref
341 2         8 ? { @{$value}[ @tuple .. $#$value ] }
342 3 100       22 : [ @{$value}[ @tuple .. $#$value ] ];
  1         2  
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         59 return \@new;
350             },
351 6         62 );
352             } #/ else [ if ( $all_inlinable ) ]
353            
354 10         40 return $C;
355             } #/ sub __coercion_generator
356              
357             1;