package Types::Standard::Tuple; use 5.006001; use strict; use warnings; BEGIN { $Types::Standard::Tuple::AUTHORITY = 'cpan:TOBYINK'; $Types::Standard::Tuple::VERSION = '1.004004'; } use Type::Tiny (); use Types::Standard (); use Types::TypeTiny (); sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak } my $_Optional = Types::Standard::Optional; no warnings; sub __constraint_generator { my @constraints = @_; my $slurpy; if (exists $constraints[-1] and ref $constraints[-1] eq "HASH") { $slurpy = Types::TypeTiny::to_TypeTiny($constraints[-1]{slurpy}); Types::TypeTiny::TypeTiny->check($slurpy) or _croak("Slurpy parameter to Tuple[...] expected to be a type constraint; got $slurpy"); pop(@constraints)->{slurpy} = $slurpy; # keep canonicalized version around } for (@constraints) { Types::TypeTiny::TypeTiny->check($_) or _croak("Parameters to Tuple[...] expected to be type constraints; got $_"); } # By god, the Type::Tiny::XS API is currently horrible my @xsub; if (Type::Tiny::_USE_XS and !$slurpy) { my @known = map { my $known; $known = Type::Tiny::XS::is_known($_->compiled_check) unless $_->is_strictly_a_type_of($_Optional); defined($known) ? $known : (); } @constraints; if (@known == @constraints) { my $xsub = Type::Tiny::XS::get_coderef_for( sprintf "Tuple[%s]", join(',', @known) ); push @xsub, $xsub if $xsub; } } my @is_optional = map !!$_->is_strictly_a_type_of($_Optional), @constraints; my $slurp_hash = $slurpy && $slurpy->is_a_type_of(Types::Standard::HashRef); my $slurp_any = $slurpy && $slurpy->equals(Types::Standard::Any); my @sorted_is_optional = sort @is_optional; join("|", @sorted_is_optional) eq join("|", @is_optional) or _croak("Optional parameters to Tuple[...] cannot precede required parameters"); sub { my $value = $_[0]; if ($#constraints < $#$value) { return !!0 unless $slurpy; my $tmp; if ($slurp_hash) { ($#$value - $#constraints+1) % 2 or return; $tmp = +{@$value[$#constraints+1 .. $#$value]}; $slurpy->check($tmp) or return; } elsif (not $slurp_any) { $tmp = +[@$value[$#constraints+1 .. $#$value]]; $slurpy->check($tmp) or return; } } for my $i (0 .. $#constraints) { ($i > $#$value) and return !!$is_optional[$i]; $constraints[$i]->check($value->[$i]) or return !!0; } return !!1; }, @xsub; } sub __inline_generator { my @constraints = @_; my $slurpy; if (exists $constraints[-1] and ref $constraints[-1] eq "HASH") { $slurpy = pop(@constraints)->{slurpy}; } return if grep { not $_->can_be_inlined } @constraints; return if defined $slurpy && !$slurpy->can_be_inlined; if (Type::Tiny::_USE_XS and !$slurpy) { my @known = map { my $known; $known = Type::Tiny::XS::is_known($_->compiled_check) unless $_->is_strictly_a_type_of($_Optional); defined($known) ? $known : (); } @constraints; if (@known == @constraints) { my $xsub = Type::Tiny::XS::get_subname_for( sprintf "Tuple[%s]", join(',', @known) ); return sub { my $var = $_[1]; "$xsub\($var\)" } if $xsub; } } my $tmpl = "do { my \$tmp = +[\@{%s}[%d..\$#{%s}]]; %s }"; my $slurpy_any; if (defined $slurpy) { $tmpl = 'do { my ($orig, $from, $to) = (%s, %d, $#{%s});' . '($to-$from % 2) and do { my $tmp = +{@{$orig}[$from..$to]}; %s }' . '}' if $slurpy->is_a_type_of(Types::Standard::HashRef); $slurpy_any = 1 if $slurpy->equals(Types::Standard::Any); } my @is_optional = map !!$_->is_strictly_a_type_of($_Optional), @constraints; my $min = 0 + grep !$_, @is_optional; return sub { my $v = $_[1]; join " and ", Types::Standard::ArrayRef->inline_check($v), ( (scalar @constraints == $min and not $slurpy) ? "\@{$v} == $min" : ( "\@{$v} >= $min", ( $slurpy_any ? () : ( $slurpy ? sprintf($tmpl, $v, $#constraints+1, $v, $slurpy->inline_check('$tmp')) : sprintf("\@{$v} <= %d", scalar @constraints) ) ), ) ), map { my $inline = $constraints[$_]->inline_check("$v\->[$_]"); $inline eq '(!!1)' ? () : ( $is_optional[$_] ? sprintf('(@{%s} <= %d or %s)', $v, $_, $inline) : $inline ); } 0 .. $#constraints; }; } sub __deep_explanation { my ($type, $value, $varname) = @_; my @constraints = @{ $type->parameters }; my $slurpy; if (exists $constraints[-1] and ref $constraints[-1] eq "HASH") { $slurpy = Types::TypeTiny::to_TypeTiny(pop(@constraints)->{slurpy}); } @constraints = map Types::TypeTiny::to_TypeTiny($_), @constraints; if (@constraints < @$value and not $slurpy) { return [ sprintf('"%s" expects at most %d values in the array', $type, scalar(@constraints)), sprintf('%d values found; too many', scalar(@$value)), ]; } for my $i (0 .. $#constraints) { next if $constraints[$i]->is_strictly_a_type_of( Types::Standard::Optional ) && $i > $#$value; next if $constraints[$i]->check($value->[$i]); return [ sprintf('"%s" constrains value at index %d of array with "%s"', $type, $i, $constraints[$i]), @{ $constraints[$i]->validate_explain($value->[$i], sprintf('%s->[%s]', $varname, $i)) }, ]; } if (defined($slurpy)) { my $tmp = $slurpy->is_a_type_of(Types::Standard::HashRef) ? +{@$value[$#constraints+1 .. $#$value]} : +[@$value[$#constraints+1 .. $#$value]]; $slurpy->check($tmp) or return [ sprintf( 'Array elements from index %d are slurped into a %s which is constrained with "%s"', $#constraints+1, $slurpy->is_a_type_of(Types::Standard::HashRef) ? 'hashref' : 'arrayref', $slurpy, ), @{ $slurpy->validate_explain($tmp, '$SLURPY') }, ]; } # This should never happen... return; # uncoverable statement } my $label_counter = 0; sub __coercion_generator { my ($parent, $child, @tuple) = @_; my $slurpy; if (exists $tuple[-1] and ref $tuple[-1] eq "HASH") { $slurpy = pop(@tuple)->{slurpy}; } my $child_coercions_exist = 0; my $all_inlinable = 1; for my $tc (@tuple, ($slurpy ? $slurpy : ())) { $all_inlinable = 0 if !$tc->can_be_inlined; $all_inlinable = 0 if $tc->has_coercion && !$tc->coercion->can_be_inlined; $child_coercions_exist++ if $tc->has_coercion; } return unless $child_coercions_exist; my $C = "Type::Coercion"->new(type_constraint => $child); if ($all_inlinable) { $C->add_type_coercions($parent => Types::Standard::Stringable { my $label = sprintf("TUPLELABEL%d", ++$label_counter); my @code; push @code, 'do { my ($orig, $return_orig, $tmp, @new) = ($_, 0);'; push @code, "$label: {"; push @code, sprintf('(($return_orig = 1), last %s) if @$orig > %d;', $label, scalar @tuple) unless $slurpy; for my $i (0 .. $#tuple) { my $ct = $tuple[$i]; my $ct_coerce = $ct->has_coercion; my $ct_optional = $ct->is_a_type_of(Types::Standard::Optional); push @code, sprintf( 'if (@$orig > %d) { $tmp = %s; (%s) ? ($new[%d]=$tmp) : (($return_orig=1), last %s) }', $i, $ct_coerce ? $ct->coercion->inline_coercion("\$orig->[$i]") : "\$orig->[$i]", $ct->inline_check('$tmp'), $i, $label, ); } if ($slurpy) { my $size = @tuple; push @code, sprintf('if (@$orig > %d) {', $size); push @code, sprintf('my $tail = [ @{$orig}[%d .. $#$orig] ];', $size); push @code, $slurpy->has_coercion ? sprintf('$tail = %s;', $slurpy->coercion->inline_coercion('$tail')) : q(); push @code, sprintf( '(%s) ? push(@new, @$tail) : ($return_orig++);', $slurpy->inline_check('$tail'), ); push @code, '}'; } push @code, '}'; push @code, '$return_orig ? $orig : \\@new'; push @code, '}'; "@code"; }); } else { my @is_optional = map !!$_->is_strictly_a_type_of($_Optional), @tuple; $C->add_type_coercions( $parent => sub { my $value = @_ ? $_[0] : $_; if (!$slurpy and @$value > @tuple) { return $value; } my @new; for my $i (0 .. $#tuple) { return \@new if $i > $#$value and $is_optional[$i]; my $ct = $tuple[$i]; my $x = $ct->has_coercion ? $ct->coerce($value->[$i]) : $value->[$i]; return $value unless $ct->check($x); $new[$i] = $x; } if ($slurpy and @$value > @tuple) { my $tmp = $slurpy->has_coercion ? $slurpy->coerce([ @{$value}[@tuple .. $#$value] ]) : [ @{$value}[@tuple .. $#$value] ]; $slurpy->check($tmp) ? push(@new, @$tmp) : return($value); } return \@new; }, ); }; return $C; } 1; __END__ =pod =encoding utf-8 =head1 NAME Types::Standard::Tuple - internals for the Types::Standard Tuple type constraint =head1 STATUS This module is considered part of Type-Tiny's internals. It is not covered by the L. =head1 DESCRIPTION This file contains some of the guts for L. It will be loaded on demand. You may ignore its presence. =head1 BUGS Please report any bugs to L. =head1 SEE ALSO L. =head1 AUTHOR Toby Inkster Etobyink@cpan.orgE. =head1 COPYRIGHT AND LICENCE This software is copyright (c) 2013-2014, 2017-2019 by Toby Inkster. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =head1 DISCLAIMER OF WARRANTIES THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.