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<Type-Tiny stability policy|Type::Tiny::Manual::Policies/"STABILITY">.
=head1 DESCRIPTION
This file contains some of the guts for L<Types::Standard>.
It will be loaded on demand. You may ignore its presence.
=head1 BUGS
Please report any bugs to
L<http://rt.cpan.org/Dist/Display.html?Queue=Type-Tiny>.
=head1 SEE ALSO
L<Types::Standard>.
=head1 AUTHOR
Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
=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.