package Parse::Method::Signatures::TypeConstraint; use Carp qw/croak carp/; use Moose; use MooseX::Types::Util qw/has_available_type_export/; use MooseX::Types::Moose qw/Str HashRef CodeRef ClassName/; use Parse::Method::Signatures::Types qw/TypeConstraint/; use namespace::clean -except => 'meta'; has ppi => ( is => 'ro', isa => 'PPI::Element', required => 1, handles => { 'to_string' => 'content' } ); has tc => ( is => 'ro', isa => TypeConstraint, lazy => 1, builder => '_build_tc', ); has from_namespace => ( is => 'ro', isa => ClassName, predicate => 'has_from_namespace' ); has tc_callback => ( is => 'ro', isa => CodeRef, default => sub { \&find_registered_constraint }, ); sub find_registered_constraint { my ($self, $name) = @_; my $type; if ($self->has_from_namespace) { my $pkg = $self->from_namespace; if ($type = has_available_type_export($pkg, $name)) { croak "The type '$name' was found in $pkg " . "but it hasn't yet been defined. Perhaps you need to move the " . "definition into a type library or a BEGIN block.\n" if $type && $type->isa('MooseX::Types::UndefinedType'); } elsif ($name !~ /::/) { my $meta = Class::MOP::class_of($pkg) || Class::MOP::Class->initialize($pkg); my $func = $meta->get_package_symbol('&' . $name); my $proto = prototype $func if $func; $name = $func->() if $func && defined $proto && !length $proto; } } my $registry = Moose::Util::TypeConstraints->get_type_constraint_registry; return $type || $registry->find_type_constraint($name) || $name; } sub _build_tc { my ($self) = @_; my $tc = $self->_walk_data($self->ppi); # This makes the error appear from the right place local $Carp::Internal{'Class::MOP::Method::Generated'} = 1 unless exists $Carp::Internal{'Class::MOP::Method::Generated'}; croak "'@{[$self->ppi]}' could not be parsed to a type constraint - maybe you need to " . "pre-declare the type with class_type" unless blessed $tc; return $tc; } sub _walk_data { my ($self, $data) = @_; my $res = $self->_union_node($data) || $self->_params_node($data) || $self->_str_node($data) || $self->_leaf($data) or confess 'failed to visit tc'; return $res->(); } sub _leaf { my ($self, $data) = @_; sub { $self->_invoke_callback($data->content) }; } sub _union_node { my ($self, $data) = @_; return unless $data->isa('PPI::Statement::Expression::TCUnion'); my @types = map { $self->_walk_data($_) } $data->children; sub { scalar @types == 1 ? @types : Moose::Meta::TypeConstraint::Union->new(type_constraints => \@types) }; } sub _params_node { my ($self, $data) = @_; return unless $data->isa('PPI::Statement::Expression::TCParams'); my @params = map { $self->_walk_data($_) } @{$data->params}; my $type = $self->_invoke_callback($data->type); sub { $type->parameterize(@params) } } sub _str_node { my ($self, $data) = @_; return unless $data->isa('PPI::Token::StringifiedWord') || $data->isa('PPI::Token::Number') || $data->isa('PPI::Token::Quote'); sub { $data->isa('PPI::Token::Number') ? $data->content : $data->string }; } sub _invoke_callback { my $self = shift; $self->tc_callback->($self, @_); } __PACKAGE__->meta->make_immutable; 1; __END__ =head1 NAME Parse::Method::Signatures::TypeConstraint - turn parsed TC data into Moose TC object =head1 DESCRIPTION Class used to turn PPI elements into L objects. =head1 ATTRIBUTES =head2 tc =over B =back The L object for this type constraint, built when requested. L will be called for each individual component type in turn. =head2 tc_callback =over B CodeRef B L =back Callback used to turn type names into type objects. See L for more details and an example. =head2 from_namespace =over B ClassName =back If provided, then the default C will search for L in this package. =head1 METHODS =head2 find_registered_constraint Will search for an imported L in L (if provided). Failing that it will ask the L for a type with the given name. If all else fails, it will simple return the type as a string, so that Moose's auto-vivification of classnames to type will work. =head2 to_string String representation of the type constraint, approximately as parsed. =head1 SEE ALSO L, L, L. =head1 AUTHORS Florian Ragwitz . Ash Berlin . =head1 LICENSE Licensed under the same terms as Perl itself.