File Coverage

lib/Types/Standard/CycleTuple.pm
Criterion Covered Total %
statement 126 128 99.2
branch 46 56 82.1
condition 5 9 55.5
subroutine 20 20 100.0
pod n/a
total 197 213 92.9


line stmt bran cond sub pod time code
1             # INTERNAL MODULE: guts for CycleTuple type from Types::Standard.
2              
3             package Types::Standard::CycleTuple;
4              
5 5     5   1152 use 5.008001;
  5         24  
6 5     5   35 use strict;
  5         12  
  5         183  
7 5     5   25 use warnings;
  5         11  
  5         571  
8              
9             BEGIN {
10 5     5   23 $Types::Standard::CycleTuple::AUTHORITY = 'cpan:TOBYINK';
11 5         351 $Types::Standard::CycleTuple::VERSION = '2.010001';
12             }
13              
14             $Types::Standard::CycleTuple::VERSION =~ tr/_//d;
15              
16 5     5   42 use Type::Tiny ();
  5         13  
  5         139  
17 5     5   26 use Types::Standard ();
  5         10  
  5         132  
18 5     5   25 use Types::TypeTiny ();
  5         10  
  5         720  
19              
20 6     6   87 sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak }
  6         44  
21              
22             my $_Optional = Types::Standard::Optional;
23             my $_arr = Types::Standard::ArrayRef;
24             my $_Slurpy = Types::Standard::Slurpy;
25              
26 5     5   39 use Exporter::Tiny 1.004001 ();
  5         114  
  5         2311  
27             our @ISA = qw( Exporter::Tiny );
28              
29             sub _exporter_fail {
30 2     2   466 my ( $class, $type_name, $values, $globals ) = @_;
31 2         5 my $caller = $globals->{into};
32            
33 2         5 my @final;
34             {
35 2         4 my $to_type = sub {
36 4 100   4   194 return $_[0] if Types::TypeTiny::is_TypeTiny($_[0]);
37 2         624 require Type::Utils;
38 2         11 Type::Utils::dwim_type( $_[0], for => 'caller' );
39 2         10 };
40 2         5 my $of = $values->{of};
41 2 50       44 Types::TypeTiny::is_ArrayLike($of)
42             or _croak( qq{Expected arrayref option "of" for type "$type_name"} );
43 2         8 @final = map { $to_type->($_) } @$of;
  4         13  
44             }
45            
46 2         12 my $type = Types::Standard::CycleTuple->of( @final );
47             $type = $type->create_child_type(
48             name => $type_name,
49             $type->has_coercion ? ( coercion => 1 ) : (),
50 2 50       11 exists( $values->{where} ) ? ( constraint => $values->{where} ) : (),
    50          
51             );
52            
53             $INC{'Type/Registry.pm'}
54             ? 'Type::Registry'->for_class( $caller )->add_type( $type, $type_name )
55             : ( $Type::Registry::DELAYED{$caller}{$type_name} = $type )
56 2 100 33     23 unless( ref($caller) or $caller eq '-lexical' or $globals->{'lexical'} );
    50 33        
57 2         3 return map +( $_->{name} => $_->{code} ), @{ $type->exportables };
  2         10  
58             }
59              
60 5     5   45 no warnings;
  5         13  
  5         8357  
61              
62             my $cycleuniq = 0;
63              
64             sub __constraint_generator {
65             my @params = map {
66 14     14   38 my $param = $_;
  29         53  
67 29 100       577 Types::TypeTiny::is_TypeTiny( $param )
68             or _croak(
69             "Parameters to CycleTuple[...] expected to be type constraints; got $param" );
70 28         103 $param;
71             } @_;
72 13         62 my $count = @params;
73 13         65 my $tuple = Types::Standard::Tuple()->of( @params );
74            
75 13 100       77 _croak( "Parameters to CycleTuple[...] cannot be optional" )
76             if grep !!$_->is_strictly_a_type_of( $_Optional ), @params;
77 10 100       44 _croak( "Parameters to CycleTuple[...] cannot be slurpy" )
78             if grep !!$_->is_strictly_a_type_of( $_Slurpy ), @params;
79            
80             sub {
81 74     74   1540 my $value = shift;
82 74 50       256 return unless $_arr->check( $value );
83 74 100       461 return if @$value % $count;
84 33         90 my $i = 0;
85 33         91 while ( $i < $#$value ) {
86 40         155 my $tmp = [ @$value[ $i .. $i + $count - 1 ] ];
87 40 100       115 return unless $tuple->check( $tmp );
88 37         219 $i += $count;
89             }
90 30         91 !!1;
91             }
92 8         142 } #/ sub __constraint_generator
93              
94             sub __inline_generator {
95             my @params = map {
96 8     8   24 my $param = $_;
  21         36  
97 21 50       492 Types::TypeTiny::is_TypeTiny( $param )
98             or _croak(
99             "Parameter to CycleTuple[`a] expected to be a type constraint; got $param" );
100 21         61 $param;
101             } @_;
102 8         23 my $count = @params;
103 8         42 my $tuple = Types::Standard::Tuple()->of( @params );
104            
105 8 100       45 return unless $tuple->can_be_inlined;
106            
107             sub {
108 72     72   165 $cycleuniq++;
109            
110 72         114 my $v = $_[1];
111 72         205 my @checks = $_arr->inline_check( $v );
112 72 100       638 push @checks, sprintf(
113             'not(@%s %% %d)',
114             ( $v =~ /\A\$[a-z0-9_]+\z/i ? $v : "{$v}" ),
115             $count,
116             );
117             push @checks, sprintf(
118             'do { my $cyclecount%d = 0; my $cycleok%d = 1; while ($cyclecount%d < $#{%s}) { my $cycletmp%d = [@{%s}[$cyclecount%d .. $cyclecount%d+%d]]; unless (%s) { $cycleok%d = 0; last; }; $cyclecount%d += %d; }; $cycleok%d; }',
119             $cycleuniq,
120             $cycleuniq,
121             $cycleuniq,
122             $v,
123             $cycleuniq,
124             $v,
125             $cycleuniq,
126             $cycleuniq,
127             $count - 1,
128             $tuple->inline_check( "\$cycletmp$cycleuniq" ),
129             $cycleuniq,
130             $cycleuniq,
131             $count,
132             $cycleuniq,
133 72 50       154 ) if grep { $_->inline_check( '$xyz' ) ne '(!!1)' } @params;
  209         531  
134 72         344 join( ' && ', @checks );
135             }
136 6         158 } #/ sub __inline_generator
137              
138             sub __deep_explanation {
139 3     3   14 my ( $type, $value, $varname ) = @_;
140            
141             my @constraints =
142 3         8 map Types::TypeTiny::to_TypeTiny( $_ ), @{ $type->parameters };
  3         14  
143            
144 3 100       17 if ( @$value % @constraints ) {
145             return [
146 1         7 sprintf(
147             '"%s" expects a multiple of %d values in the array', $type,
148             scalar( @constraints )
149             ),
150             sprintf( '%d values found', scalar( @$value ) ),
151             ];
152             }
153            
154 2         12 for my $i ( 0 .. $#$value ) {
155 4         10 my $constraint = $constraints[ $i % @constraints ];
156 4 100       96 next if $constraint->check( $value->[$i] );
157            
158             return [
159             sprintf(
160             '"%s" constrains value at index %d of array with "%s"', $type, $i, $constraint
161             ),
162             @{
163 2         17 $constraint->validate_explain(
  2         12  
164             $value->[$i], sprintf( '%s->[%s]', $varname, $i )
165             )
166             },
167             ];
168             } #/ for my $i ( 0 .. $#$value)
169            
170             # This should never happen...
171 0         0 return; # uncoverable statement
172             } #/ sub __deep_explanation
173              
174             my $label_counter = 0;
175              
176             sub __coercion_generator {
177 6     6   29 my ( $parent, $child, @tuple ) = @_;
178            
179 6         12 my $child_coercions_exist = 0;
180 6         16 my $all_inlinable = 1;
181 6         17 for my $tc ( @tuple ) {
182 16 100       52 $all_inlinable = 0 if !$tc->can_be_inlined;
183 16 100 100     47 $all_inlinable = 0 if $tc->has_coercion && !$tc->coercion->can_be_inlined;
184 16 100       47 $child_coercions_exist++ if $tc->has_coercion;
185             }
186            
187 6 100       25 return unless $child_coercions_exist;
188 5         26 my $C = "Type::Coercion"->new( type_constraint => $child );
189            
190 5 100       34 if ( $all_inlinable ) {
191             $C->add_type_coercions(
192             $parent => Types::Standard::Stringable {
193 2     2   12 my $label = sprintf( "CTUPLELABEL%d", ++$label_counter );
194 2         7 my $label2 = sprintf( "CTUPLEINNER%d", $label_counter );
195 2         6 my @code;
196 2         8 push @code, 'do { my ($orig, $return_orig, $tmp, @new) = ($_, 0);';
197 2         6 push @code, "$label: {";
198 2         12 push @code,
199             sprintf(
200             '(($return_orig = 1), last %s) if scalar(@$orig) %% %d != 0;', $label,
201             scalar @tuple
202             );
203 2         7 push @code, sprintf( 'my $%s = 0; while ($%s < @$orig) {', $label2, $label2 );
204 2         9 for my $i ( 0 .. $#tuple ) {
205 5         14 my $ct = $tuple[$i];
206 5         36 my $ct_coerce = $ct->has_coercion;
207            
208 5 100       31 push @code, sprintf(
209             'do { $tmp = %s; (%s) ? ($new[$%s + %d]=$tmp) : (($return_orig=1), last %s) };',
210             $ct_coerce
211             ? $ct->coercion->inline_coercion( "\$orig->[\$$label2 + $i]" )
212             : "\$orig->[\$$label2 + $i]",
213             $ct->inline_check( '$tmp' ),
214             $label2,
215             $i,
216             $label,
217             );
218             } #/ for my $i ( 0 .. $#tuple)
219 2         11 push @code, sprintf( '$%s += %d;', $label2, scalar( @tuple ) );
220 2         6 push @code, '}';
221 2         7 push @code, '}';
222 2         5 push @code, '$return_orig ? $orig : \\@new';
223 2         5 push @code, '}';
224 2         28 "@code";
225             }
226 2         30 );
227             } #/ if ( $all_inlinable )
228            
229             else {
230             $C->add_type_coercions(
231             $parent => sub {
232 3 50   3   52 my $value = @_ ? $_[0] : $_;
233            
234 3 50       14 if ( scalar( @$value ) % scalar( @tuple ) != 0 ) {
235 0         0 return $value;
236             }
237            
238 3         7 my @new;
239 3         10 for my $i ( 0 .. $#$value ) {
240 18         35 my $ct = $tuple[ $i % @tuple ];
241 18 100       69 my $x = $ct->has_coercion ? $ct->coerce( $value->[$i] ) : $value->[$i];
242            
243 18 50       131 return $value unless $ct->check( $x );
244            
245 18         46 $new[$i] = $x;
246             }
247            
248 3         36 return \@new;
249             },
250 3         34 );
251             } #/ else [ if ( $all_inlinable ) ]
252            
253 5         21 return $C;
254             } #/ sub __coercion_generator
255              
256             1;
257              
258             __END__