use strict;
use warnings;
use Test::More;
use Test::Requires qw(Data::Visitor PadWalker);
use Class::Load 'load_class';
use Try::Tiny;
my $can_partialdump = try {
load_class('Devel::PartialDump', { -version => 0.14 }); 1;
};
{
package Test::Visitor;
use Moose;
use Moose::Util::TypeConstraints;
extends 'Data::Visitor';
has closed_over => (
traits => ['Array'],
isa => 'ArrayRef',
default => sub { [] },
handles => {
add_closed_over => 'push',
closed_over => 'elements',
pass => 'is_empty',
},
);
before visit_code => sub {
my $self = shift;
my ($code) = @_;
my $closed_over = PadWalker::closed_over($code);
$self->visit_ref($closed_over);
};
after visit => sub {
my $self = shift;
my ($thing) = @_;
$self->add_closed_over($thing)
unless $self->_is_okay_to_close_over($thing);
};
sub _is_okay_to_close_over {
my $self = shift;
my ($thing) = @_;
match_on_type $thing => (
'RegexpRef' => sub { 1 },
'Object' => sub { 0 },
'GlobRef' => sub { 0 },
'FileHandle' => sub { 0 },
'Any' => sub { 1 },
);
}
}
sub close_over_ok {
local $Test::Builder::Level = $Test::Builder::Level + 1;
my ($package, $method) = @_;
my $visitor = Test::Visitor->new;
my $code = $package->meta->find_method_by_name($method)->body;
$visitor->visit($code);
if ($visitor->pass) {
pass("${package}::${method} didn't close over anything complicated");
}
else {
fail("${package}::${method} closed over some stuff:");
my @closed_over = $visitor->closed_over;
for my $i (1..10) {
last unless @closed_over;
my $closed_over = shift @closed_over;
if ($can_partialdump) {
$closed_over = Devel::PartialDump->new->dump($closed_over);
}
diag($closed_over);
}
diag("... and " . scalar(@closed_over) . " more")
if @closed_over;
}
}
{
package Foo;
use Moose;
use Moose::Util::TypeConstraints;
has foo => (
is => 'ro',
isa => 'Str',
);
has bar => (
is => 'ro',
isa => 'Int',
default => 1,
);
has baz => (
is => 'rw',
isa => 'ArrayRef[Num]',
default => sub { [ 1.2 ] },
trigger => sub { warn "blah" },
);
subtype 'Thing',
as 'Int',
where { $_ < 5 },
message { "must be less than 5" };
has quux => (
is => 'rw',
isa => 'Thing',
predicate => 'has_quux',
clearer => 'clear_quux',
);
__PACKAGE__->meta->make_immutable;
}
close_over_ok('Foo', $_) for qw(new foo bar baz quux has_quux clear_quux);
{
package Foo::Sub;
use Moose;
extends 'Foo';
around foo => sub {
my $orig = shift;
my $self = shift;
$self->$orig(@_);
};
after bar => sub { };
before baz => sub { };
override quux => sub { super };
sub blah { inner }
__PACKAGE__->meta->make_immutable;
}
close_over_ok('Foo::Sub', $_) for qw(new foo bar baz quux blah);
{
package Foo::Sub::Sub;
use Moose;
extends 'Foo::Sub';
augment blah => { inner };
__PACKAGE__->meta->make_immutable;
}
close_over_ok('Foo::Sub::Sub', $_) for qw(new blah);
{
my %handles = (
Array => {
count => 'count',
elements => 'elements',
is_empty => 'is_empty',
push => 'push',
push_curried => [ push => 42, 84 ],
unshift => 'unshift',
unshift_curried => [ unshift => 42, 84 ],
pop => 'pop',
shift => 'shift',
get => 'get',
get_curried => [ get => 1 ],
set => 'set',
set_curried_1 => [ set => 1 ],
set_curried_2 => [ set => ( 1, 98 ) ],
accessor => 'accessor',
accessor_curried_1 => [ accessor => 1 ],
accessor_curried_2 => [ accessor => ( 1, 90 ) ],
clear => 'clear',
delete => 'delete',
delete_curried => [ delete => 1 ],
insert => 'insert',
insert_curried => [ insert => ( 1, 101 ) ],
splice => 'splice',
splice_curried_1 => [ splice => 1 ],
splice_curried_2 => [ splice => 1, 2 ],
splice_curried_all => [ splice => 1, 2, ( 3, 4, 5 ) ],
sort => 'sort',
sort_curried => [ sort => ( sub { $_[1] <=> $_[0] } ) ],
sort_in_place => 'sort_in_place',
sort_in_place_curried =>
[ sort_in_place => ( sub { $_[1] <=> $_[0] } ) ],
map => 'map',
map_curried => [ map => ( sub { $_ + 1 } ) ],
grep => 'grep',
grep_curried => [ grep => ( sub { $_ < 5 } ) ],
first => 'first',
first_curried => [ first => ( sub { $_ % 2 } ) ],
join => 'join',
join_curried => [ join => '-' ],
shuffle => 'shuffle',
uniq => 'uniq',
reduce => 'reduce',
reduce_curried => [ reduce => ( sub { $_[0] * $_[1] } ) ],
natatime => 'natatime',
natatime_curried => [ natatime => 2 ],
},
Hash => {
option_accessor => 'accessor',
quantity => [ accessor => 'quantity' ],
clear_options => 'clear',
num_options => 'count',
delete_option => 'delete',
is_defined => 'defined',
options_elements => 'elements',
has_option => 'exists',
get_option => 'get',
has_no_options => 'is_empty',
keys => 'keys',
values => 'values',
key_value => 'kv',
set_option => 'set',
},
Counter => {
inc_counter => 'inc',
inc_counter_2 => [ inc => 2 ],
dec_counter => 'dec',
dec_counter_2 => [ dec => 2 ],
reset_counter => 'reset',
set_counter => 'set',
set_counter_42 => [ set => 42 ],
},
Number => {
abs => 'abs',
add => 'add',
inc => [ add => 1 ],
div => 'div',
cut_in_half => [ div => 2 ],
mod => 'mod',
odd => [ mod => 2 ],
mul => 'mul',
set => 'set',
sub => 'sub',
dec => [ sub => 1 ],
},
Bool => {
illuminate => 'set',
darken => 'unset',
flip_switch => 'toggle',
is_dark => 'not',
},
String => {
inc => 'inc',
append => 'append',
append_curried => [ append => '!' ],
prepend => 'prepend',
prepend_curried => [ prepend => '-' ],
replace => 'replace',
replace_curried => [ replace => qr/(.)$/, sub { uc $1 } ],
chop => 'chop',
chomp => 'chomp',
clear => 'clear',
match => 'match',
match_curried => [ match => qr/\D/ ],
length => 'length',
substr => 'substr',
substr_curried_1 => [ substr => (1) ],
substr_curried_2 => [ substr => ( 1, 3 ) ],
substr_curried_3 => [ substr => ( 1, 3, 'ong' ) ],
},
Code => {
execute => 'execute',
execute_method => 'execute_method',
},
);
my %isa = (
Array => 'ArrayRef[Str]',
Hash => 'HashRef[Int]',
Counter => 'Int',
Number => 'Num',
Bool => 'Bool',
String => 'Str',
Code => 'CodeRef',
);
my %default = (
Array => [],
Hash => {},
Counter => 0,
Number => 0.0,
Bool => 1,
String => '',
Code => sub { },
);
for my $trait (keys %default) {
my $class_name = "Native::$trait";
my $handles = $handles{$trait};
my $attr_class = Moose::Util::with_traits(
'Moose::Meta::Attribute',
"Moose::Meta::Attribute::Native::Trait::$trait",
);
Moose::Meta::Class->create(
$class_name,
superclasses => ['Moose::Object'],
attributes => [
$attr_class->new(
'nonlazy',
is => 'ro',
isa => $isa{$trait},
default => sub { $default{$trait} },
handles => {
map {; "nonlazy_$_" => $handles->{$_} } keys %$handles
},
),
$attr_class->new(
'lazy',
is => 'ro',
isa => $isa{$trait},
lazy => 1,
default => sub { $default{$trait} },
handles => {
map {; "lazy_$_" => $handles->{$_} } keys %$handles
},
),
],
);
close_over_ok($class_name, $_) for (
'new',
map {; "nonlazy_$_", "lazy_$_" } keys %$handles
);
}
}
{
package WithInitializer;
use Moose;
has foo => (
is => 'ro',
isa => 'Str',
initializer => sub { },
);
has bar => (
is => 'ro',
isa => 'Str',
lazy => 1,
default => sub { 'a' },
initializer => sub { },
);
__PACKAGE__->meta->make_immutable;
}
close_over_ok('WithInitializer', 'foo');
{ local $TODO = "initializer still closes over things";
close_over_ok('WithInitializer', $_) for qw(new bar);
}
done_testing;