From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

=pod
=encoding utf-8
=head1 PURPOSE
Test L<Types::TypeTiny::to_TypeTiny> pseudo-coercion and the
L<Types::TypeTiny::_ForeignTypeConstraint> type.
=head1 DEPENDENCIES
This test requires L<Moose> 2.0000, L<Mouse> 1.00, and L<Moo> 1.000000.
Otherwise, it is skipped.
=head1 AUTHOR
Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
=head1 COPYRIGHT AND LICENCE
This software is copyright (c) 2014, 2017-2025 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.
=cut
use strict;
use lib qw( ./lib ./t/lib ../inc ./inc );
# Test::Requires calls ->import on Moose/Mouse, so be sure
# to import them into dummy packages.
{ package XXX; use Test::Requires { Moose => '2.0000' } };
{ package YYY; use Test::Requires { Mouse => '1.00' } };
{ package ZZZ; use Test::Requires { Moo => '1.000000' } };
use Test::TypeTiny -all;
use Types::TypeTiny -all;
use Types::Standard qw(Int);
use Moose::Util::TypeConstraints qw(find_type_constraint);
ok(TypeTiny->has_coercion, "TypeTiny->has_coercion");
subtest "Coercion from built-in Moose type constraint object" => sub
{
my $orig = find_type_constraint("Int");
my $type = to_TypeTiny $orig;
should_pass($orig, _ForeignTypeConstraint);
should_fail($type, _ForeignTypeConstraint);
should_pass($type, TypeTiny, 'to_TypeTiny converted a Moose type constraint to a Type::Tiny one');
is($type->name, 'Int', '... which has the correct name');
ok($type->can_be_inlined, '... and which can be inlined');
note $type->inline_check('$X');
subtest "... and it works" => sub
{
should_pass(123, $type);
should_fail(3.3, $type);
};
# This doesn't provide the same message because Type::Tiny isn't
# really coercing a Moose type constraint, it's just grabbing `Int`
# from Types::Standard.
#
# is(
# $type->get_message(3.3),
# $orig->get_message(3.3),
# '... and provides proper message',
# );
};
subtest "Coercion from custom Moose type constraint object" => sub
{
my $orig = 'Moose::Meta::TypeConstraint'->new(
name => 'EvenInt',
parent => find_type_constraint("Int"),
constraint => sub {
my ( $value ) = @_;
$value % 2 == 0;
},
inlined => sub {
my ( $self, $var ) = @_;
return sprintf(
'do { %s } && !( %s %% 2 )',
$self->parent->_inline_check( $var ),
$var,
);
},
message => sub {
my ( $value ) = @_;
return find_type_constraint("Int")->check( $value )
? "$value isn't an integer at all"
: "$value is odd";
},
);
my $type = to_TypeTiny $orig;
should_pass($orig, _ForeignTypeConstraint);
should_fail($type, _ForeignTypeConstraint);
should_pass($type, TypeTiny, 'to_TypeTiny converted a Moose type constraint to a Type::Tiny one');
is($type->display_name, 'EvenInt', '... which has the correct display_name');
ok($type->can_be_inlined, '... and which can be inlined');
note $type->inline_check('$X');
subtest "... and it works" => sub
{
should_fail(3.3, $type);
should_fail(123, $type);
should_pass(124, $type);
};
is(
$type->get_message(3.3),
$orig->get_message(3.3),
'... and provides proper message',
);
};
my %moose_ptype_opts = (
name => 'ArrayOrHashRef',
parent => find_type_constraint('Ref'),
constraint => sub {
my $value = @_ ? pop : $_;
ref($value) eq 'HASH' or ref($value) eq 'ARRAY';
},
constraint_generator => sub {
my $param = shift;
return sub {
my $value = @_ ? pop : $_;
if (ref($value) eq 'ARRAY') {
($param->check($_) or return) for @$value;
return 1;
}
elsif (ref($value) eq 'HASH') {
($param->check($_) or return) for values %$value;
return 1;
}
return;
};
},
);
my $ptype_tests = sub {
my $moose = Moose::Meta::TypeConstraint::Parameterizable->new(%moose_ptype_opts);
# wow, the Moose API is stupid; need to do this
Moose::Util::TypeConstraints::register_type_constraint($moose);
Moose::Util::TypeConstraints::add_parameterizable_type($moose);
note "Moose native type, no parameters";
ok( $moose->check([]) );
ok( $moose->check({}) );
ok( $moose->check([1..10]) );
ok( $moose->check({foo => 1, bar => 2}) );
ok( $moose->check(['hello world']) );
ok( ! $moose->check(\1) );
ok( ! $moose->check(42) );
note "Moose native type, parameterized with Moose type";
my $moose_with_moose = $moose->parameterize( find_type_constraint('Int') );
ok( $moose_with_moose->check([]) );
ok( $moose_with_moose->check({}) );
ok( $moose_with_moose->check([1..10]) );
ok( $moose_with_moose->check({foo => 1, bar => 2}) );
ok( ! $moose_with_moose->check(['hello world']) );
ok( ! $moose_with_moose->check(\1) );
ok( ! $moose_with_moose->check(42) );
note "Moose native type, parameterized with TT type";
my $moose_with_tt = $moose->parameterize( Int );
ok( $moose_with_tt->check([]) );
ok( $moose_with_tt->check({}) );
ok( $moose_with_tt->check([1..10]) );
ok( $moose_with_tt->check({foo => 1, bar => 2}) );
ok( ! $moose_with_tt->check(['hello world']) );
ok( ! $moose_with_tt->check(\1) );
ok( ! $moose_with_tt->check(42) );
note 'TT type, no parameters';
my $tt = Types::TypeTiny::to_TypeTiny($moose);
isa_ok($tt, 'Type::Tiny');
is($tt->display_name, $moose_ptype_opts{name});
should_pass([], $tt);
should_pass({}, $tt);
should_pass([1..10], $tt);
should_pass({foo => 1, bar => 2}, $tt);
should_pass(['hello world'], $tt);
should_fail(\1, $tt);
should_fail(42, $tt);
note 'TT type, parameterized with Moose type';
my $tt_with_moose = $tt->of( find_type_constraint('Int') );
should_pass([], $tt_with_moose);
should_pass({}, $tt_with_moose);
should_pass([1..10], $tt_with_moose);
should_pass({foo => 1, bar => 2}, $tt_with_moose);
should_fail(['hello world'], $tt_with_moose);
should_fail(\1, $tt_with_moose);
should_fail(42, $tt_with_moose);
note 'TT type, parameterized with TT type';
my $tt_with_tt = $tt->of( Int );
should_pass([], $tt_with_tt);
should_pass({}, $tt_with_tt);
should_pass([1..10], $tt_with_tt);
should_pass({foo => 1, bar => 2}, $tt_with_tt);
should_fail(['hello world'], $tt_with_tt);
should_fail(\1, $tt_with_tt);
should_fail(42, $tt_with_tt);
return (
$moose,
$moose_with_moose,
$moose_with_tt,
$tt,
$tt_with_moose,
$tt_with_tt,
);
};
subtest "Coercion from Moose parameterizable type constraint object" => sub {
$ptype_tests->();
};
# Moose cannot handle two parameterizable types sharing a name
$moose_ptype_opts{name} .= '2';
$moose_ptype_opts{inlined} = sub {
my $var = pop;
sprintf('ref(%s) =~ /^(HASH|ARRAY)$/', $var);
};
$moose_ptype_opts{inline_generator} = sub {
my ($base, $param, $var) = @_;
my $code = sprintf qq{do{
if (ref($var) eq 'ARRAY') {
my \$okay = 1;
(%s or ((\$okay=0), last)) for \@{$var};
\$okay;
}
elsif (ref($var) eq 'HASH') {
my \$okay = 1;
(%s or ((\$okay=0), last)) for values %%{$var};
\$okay;
}
else {
0;
}
}}, ($param->_inline_check('$_')) x 2;
$code;
};
subtest "Coercion from Moose parameterizable type constraint object with inlining" => sub
{
my @types = $ptype_tests->();
note 'check everything can be inlined';
for my $type (@types) {
ok( $type->can_be_inlined );
ok( length($type->_inline_check('$xxx')) );
}
note( $types[-1]->inline_check('$VALUE') );
};
subtest "Coercion from Moose enum type constraint" => sub {
my $moose = Moose::Util::TypeConstraints::enum(Foo => [qw/ foo bar baz /]);
ok( $moose->check("foo") );
ok( ! $moose->check("quux") );
ok( ! $moose->check(\1) );
ok( ! $moose->check(undef) );
my $tt = Types::TypeTiny::to_TypeTiny($moose);
ok( $tt->check("foo") );
ok( ! $tt->check("quux") );
ok( ! $tt->check(\1) );
ok( ! $tt->check(undef) );
isa_ok($tt, 'Type::Tiny::Enum');
is_deeply($tt->values, $moose->values);
ok $tt->can_be_inlined;
note( $tt->inline_check('$STR') );
};
subtest "Coercion from Moose class type constraint" => sub {
my $moose = Moose::Util::TypeConstraints::class_type(FooObj => { class => 'MyApp::Foo' });
my $tt = Types::TypeTiny::to_TypeTiny($moose);
isa_ok($tt, 'Type::Tiny::Class');
is($tt->class, $moose->class);
ok $tt->can_be_inlined;
note( $tt->inline_check('$OBJECT') );
};
subtest "Coercion from Moose role type constraint" => sub {
my $moose = Moose::Util::TypeConstraints::role_type(DoesFoo => { role => 'MyApp::Foo' });
my $tt = Types::TypeTiny::to_TypeTiny($moose);
isa_ok($tt, 'Type::Tiny::Role');
is($tt->role, $moose->role);
ok $tt->can_be_inlined;
note( $tt->inline_check('$OBJECT') );
};
subtest "Coercion from Moose duck type constraint" => sub {
my $moose = Moose::Util::TypeConstraints::duck_type(FooInterface => [qw/foo bar baz/]);
my $tt = Types::TypeTiny::to_TypeTiny($moose);
isa_ok($tt, 'Type::Tiny::Duck');
is_deeply([ sort @{$tt->methods} ], [ sort @{$moose->methods} ]);
ok $tt->can_be_inlined;
note( $tt->inline_check('$OBJECT') );
};
subtest "Coercion from Moose union type constraint" => sub {
my $moose = Moose::Util::TypeConstraints::union(
'ContainerThang',
[ find_type_constraint('ArrayRef'), find_type_constraint('HashRef'), ]
);
my $tt = Types::TypeTiny::to_TypeTiny($moose);
is($tt->display_name, 'ContainerThang');
isa_ok($tt, 'Type::Tiny::Union');
ok($tt->[0] == Types::Standard::ArrayRef);
ok($tt->[1] == Types::Standard::HashRef);
ok $tt->can_be_inlined;
note( $tt->inline_check('$REF') );
};
subtest "Coercion from Mouse type constraint object" => sub
{
my $orig = Mouse::Util::TypeConstraints::find_type_constraint("Int");
my $type = to_TypeTiny $orig;
should_pass($orig, _ForeignTypeConstraint);
should_fail($type, _ForeignTypeConstraint);
should_pass($type, TypeTiny, 'to_TypeTiny converted a Mouse type constraint to a Type::Tiny one');
subtest "... and it works" => sub
{
should_pass(123, $type);
should_fail(3.3, $type);
};
is(
$type->get_message(3.3),
$orig->get_message(3.3),
'... and provides proper message',
);
};
subtest "Coercion from predicate-like coderef" => sub
{
my $orig = sub { $_[0] =~ /\A-?[0-9]+\z/ };
my $type = to_TypeTiny $orig;
should_pass($orig, _ForeignTypeConstraint);
should_fail($type, _ForeignTypeConstraint);
should_pass($type, TypeTiny, 'to_TypeTiny converted the coderef to a Type::Tiny object');
subtest "... and it works" => sub
{
should_pass(123, $type);
should_fail(3.3, $type);
};
};
subtest "Coercion from assertion-like coderef" => sub
{
my $orig = sub { $_[0] =~ /\A-?[0-9]+\z/ or die("not an integer") };
my $type = to_TypeTiny $orig;
should_pass($orig, _ForeignTypeConstraint);
should_fail($type, _ForeignTypeConstraint);
should_pass($type, TypeTiny, 'to_TypeTiny converted the coderef to a Type::Tiny object');
subtest "... and it works" => sub
{
should_pass(123, $type);
should_fail(3.3, $type);
};
like(
$type->validate(3.3),
qr/\Anot an integer/,
'... and provides proper message',
);
};
subtest "Coercion from Sub::Quote coderef" => sub
{
require Sub::Quote;
my $orig = Sub::Quote::quote_sub(q{ $_[0] =~ /\A-?[0-9]+\z/ });
my $type = to_TypeTiny $orig;
should_pass($orig, _ForeignTypeConstraint);
should_fail($type, _ForeignTypeConstraint);
should_pass($type, TypeTiny, 'to_TypeTiny converted the coderef to a Type::Tiny object');
ok($type->can_be_inlined, '... which can be inlined');
note $type->inline_check('$X');
subtest "... and it works" => sub
{
should_pass(123, $type);
should_fail(3.3, $type);
};
};
done_testing;