diff --git a/lib/Method/Signatures.pm b/lib/Method/Signatures.pm index 32b40f6..8356209 100644 --- a/lib/Method/Signatures.pm +++ b/lib/Method/Signatures.pm @@ -5,24 +5,16 @@ use warnings; use Lexical::SealRequireHints; use base 'Devel::Declare::MethodInstaller::Simple'; -use Method::Signatures::Parser; +use Method::Signatures::Utils; use Method::Signatures::Parameter; +use Method::Signatures::Signature; our $VERSION = '20140224'; our $DEBUG = $ENV{METHOD_SIGNATURES_DEBUG} || 0; -our @CARP_NOT; - our $INF = ( 0 + "inf" ) == 0 ? 9e9999 : "inf"; -sub DEBUG { - return unless $DEBUG; - - require Data::Dumper; - print STDERR "DEBUG: ", map { ref $_ ? Data::Dumper::Dumper($_) : $_ } @_; -} - # copied from Devel::Pragma sub my_hints() { $^H |= 0x20000; @@ -754,12 +746,6 @@ sub _do_compile_at_BEGIN { } -sub _strip_ws { - $_[0] =~ s/^\s+//; - $_[0] =~ s/\s+$//; -} - - # Sometimes a compilation error will happen but not throw an error causing the # code to continue compiling and producing an unrelated error down the road. # @@ -801,192 +787,16 @@ sub parse_proto { # Before we try to compile signatures, make sure there isn't a hidden compilation error. die $@ if _parser_is_fucked; - return $self->parse_signature( - proto => $proto, - invocant => $self->{invocant}, - pre_invocant => $self->{pre_invocant} + $self->{signature} = Method::Signatures::Signature->new( + signature_string => defined $proto ? $proto : "", + invocant => $self->{invocant}, + pre_invocant => $self->{pre_invocant} ); -} - - -# Parse a signature -sub parse_signature { - my $self = shift; - my %args = @_; - my @protos = $self->_split_proto($args{proto} || []); - my $signature = $args{signature} || {}; - - # JIC there's anything we need to pull out before the invocant - # (primary example would be the $orig for around modifiers in Moose/Mouse - $signature->{pre_invocant} = $args{pre_invocant}; - - # Special case for methods, they will pass in an invocant to use as the default - if( $signature->{invocant} = $args{invocant} ) { - if( @protos ) { - $signature->{invocant} = $_ for extract_invocant(\$protos[0]); - shift @protos unless $protos[0] =~ /\S/; - } - } - - return $self->parse_func( proto => \@protos, signature => $signature ); -} - - -sub _split_proto { - my $self = shift; - my $proto = shift; - - my @protos; - if( ref $proto ) { - @protos = @$proto; - } - else { - _strip_ws($proto); - @protos = split_proto($proto); - } - - return @protos; -} - - -# Parse a subroutine signature -sub parse_func { - my $self = shift; - my %args = @_; - my @protos = $self->_split_proto($args{proto} || []); - my $signature = $args{signature} || {}; - - $signature->{named} = []; - $signature->{positional} = []; - $signature->{overall} = { - num_optional => 0, - num_optional_positional => 0, - num_named => 0, - num_positional => 0, - has_invocant => $signature->{invocant} ? 1 : 0, - num_slurpy => 0 - }; - - my $idx = 0; - for my $proto (@protos) { - DEBUG( "proto: $proto\n" ); - - my $sig = Method::Signatures::Parameter->new( - original_code => $proto, - position => $idx, - ); - $idx++ if $sig->is_positional; - - # Handle "don't care" specifier - if ($sig->is_yadayada) { - $signature->{overall}{num_slurpy}++; - $signature->{overall}{yadayada}++; - next; - } - - $self->_check_sig($sig, $signature); - - if( $sig->is_named ) { - push @{$signature->{named}}, $sig; - } - else { - push @{$signature->{positional}}, $sig; - } - - my $overall = $signature->{overall}; - $overall->{num_optional}++ if $sig->is_optional; - $overall->{num_named}++ if $sig->is_named; - $overall->{num_positional}++ if $sig->is_positional; - $overall->{num_optional_positional}++ if $sig->is_optional and $sig->is_positional; - $overall->{num_slurpy}++ if $sig->is_slurpy; - - DEBUG( "sig: ", $sig ); - } - - $self->{signature} = $signature; - - $self->_calculate_max_args; - $self->_check_signature; # Then turn it into Perl code - my $inject = $self->inject_from_signature($signature); - return $inject; -} - - -sub _calculate_max_args { - my $self = shift; - my $overall = $self->{signature}{overall}; - - # If there's a slurpy argument, the max is infinity. - if( $overall->{num_slurpy} ) { - $overall->{max_argv_size} = $INF; - $overall->{max_args} = $INF; - - return; - } - - # How big can @_ be? - $overall->{max_argv_size} = ($overall->{num_named} * 2) + $overall->{num_positional}; + my $inject = $self->inject_from_signature($self->{signature}); - # The maximum logical arguments (name => value counts as one argument) - $overall->{max_args} = $overall->{num_named} + $overall->{num_positional}; - - return; -} - - -# Check the integrity of one piece of the signature -sub _check_sig { - my($self, $sig, $signature) = @_; - - if( $sig->is_slurpy ) { - sig_parsing_error("Signature can only have one slurpy parameter") - if $signature->{overall}{num_slurpy} >= 1; - sig_parsing_error("Slurpy parameter '@{[$sig->variable]}' cannot be named; use a reference instead") - if $sig->is_named; - } - - if( $sig->is_named ) { - if( $signature->{overall}{num_optional_positional} ) { - my $pos_var = $signature->{positional}[-1]->variable; - my $var = $sig->variable; - sig_parsing_error("Named parameter '$var' mixed with optional positional '$pos_var'"); - } - } - else { - if( $signature->{overall}{num_named} ) { - my $named_var = $signature->{named}[-1]->variable; - my $var = $sig->variable; - sig_parsing_error("Positional parameter '$var' after named param '$named_var'"); - } - } -} - - -# Check the integrity of the signature as a whole -sub _check_signature { - my $self = shift; - my $signature = $self->{signature}; - my $overall = $signature->{overall}; - - # Check that slurpy arguments come at the end - if( - $overall->{num_slurpy} && - !($overall->{yadayada} || $signature->{positional}[-1]->is_slurpy) - ) - { - my($slurpy_param) = $self->_find_slurpy_params; - sig_parsing_error("Slurpy parameter '@{[$slurpy_param->variable]}' must come at the end"); - } -} - - -sub _find_slurpy_params { - my $self = shift; - my $signature = $self->{signature}; - - return grep { $_->is_slurpy } @{ $signature->{named} }, @{ $signature->{positional} }; + return $inject; } @@ -997,16 +807,16 @@ sub inject_from_signature { my $signature = shift; my @code; - push @code, "my $signature->{pre_invocant} = shift;" if $signature->{pre_invocant}; - push @code, "my $signature->{invocant} = shift;" if $signature->{invocant}; + push @code, "my @{[$signature->pre_invocant]} = shift;" if $signature->pre_invocant; + push @code, "my @{[$signature->invocant]} = shift;" if $signature->invocant; - for my $sig (@{$signature->{positional}}) { + for my $sig (@{$signature->positional_parameters}) { push @code, $self->inject_for_sig($sig); } - if( @{$signature->{named}} ) { - my $first_named_idx = @{$signature->{positional}}; - if (grep { $_->is_ref_alias or $_->traits->{alias} } @{$signature->{named}}) + if( @{$signature->named_parameters} ) { + my $first_named_idx = @{$signature->positional_parameters}; + if (grep { $_->is_ref_alias or $_->traits->{alias} } @{$signature->named_parameters}) { require Data::Alias; push @code, "Data::Alias::alias( my (\%args) = \@_[$first_named_idx..\$#_] );"; @@ -1016,18 +826,16 @@ sub inject_from_signature { push @code, "my (\%args) = \@_[$first_named_idx..\$#_];"; } - for my $sig (@{$signature->{named}}) { + for my $sig (@{$signature->named_parameters}) { push @code, $self->inject_for_sig($sig); } push @code, $class . '->named_param_error(\%args) if keys %args;' - if $signature->{overall}{num_named} && !$signature->{overall}{yadayada}; + if $signature->num_named && !$signature->num_yadayada; } - push @code, $class . '->named_param_error(\%args) if keys %args;' if $signature->{overall}{has_named}; - - my $max_argv = $signature->{overall}{max_argv_size}; - my $max_args = $signature->{overall}{max_args}; + my $max_argv = $signature->max_argv_size; + my $max_args = $signature->max_args; push @code, qq[$class->too_many_args_error($max_args) if \@_ > $max_argv; ] unless $max_argv == $INF; @@ -1143,16 +951,14 @@ sub inject_for_sig { push @code, "delete( $deletion_target );" if $deletion_target; # Handle 'where' constraints (after defaults are resolved) - if ( $sig->where ) { - for my $constraint ( keys %{$sig->where} ) { - # Handle 'where { block using $_ }' - my $constraint_impl = - $constraint =~ m{^ \s* \{ (?: .* ; .* | (?:(?! => ). )* ) \} \s* $}xs - ? "sub $constraint" - : $constraint; - my $error = sprintf q{ %s->where_error(%s, '%s', '%s') }, $class, $var, $var, $constraint; - push @code, "$error unless do { no if \$] >= 5.017011, warnings => 'experimental::smartmatch'; grep { \$_ ~~ $constraint_impl } $var }; "; - } + for my $constraint ( @{$sig->where} ) { + # Handle 'where { block using $_ }' + my $constraint_impl = + $constraint =~ m{^ \s* \{ (?: .* ; .* | (?:(?! => ). )* ) \} \s* $}xs + ? "sub $constraint" + : $constraint; + my $error = sprintf q{ %s->where_error(%s, '%s', '%s') }, $class, $var, $var, $constraint; + push @code, "$error unless do { no if \$] >= 5.017011, warnings => 'experimental::smartmatch'; grep { \$_ ~~ $constraint_impl } $var }; "; } return @code; diff --git a/lib/Method/Signatures/Parameter.pm b/lib/Method/Signatures/Parameter.pm index 3644cc8..dd821e8 100644 --- a/lib/Method/Signatures/Parameter.pm +++ b/lib/Method/Signatures/Parameter.pm @@ -2,7 +2,7 @@ package Method::Signatures::Parameter; use Mouse; use Carp; -use Method::Signatures::Parser; +use Method::Signatures::Utils; my $IDENTIFIER = qr{ [^\W\d] \w* }x; my $VARIABLE = qr{ [\$\@%] $IDENTIFIER }x; @@ -90,13 +90,13 @@ has variable_name => has where => is => 'rw', - isa => 'HashRef[Int]', - default => sub { {} }; + isa => 'ArrayRef', + default => sub { [] }; sub has_where { my $self = shift; - return keys %{$self->where} ? 1 : 0; + return @{$self->where} ? 1 : 0; } has traits => @@ -162,6 +162,17 @@ has is_required => isa => 'Bool', ; +# A PPI::Document representing the parameter +has ppi_doc => + is => 'ro', + isa => 'PPI::Document', + lazy => 1, + default => sub { + my $code = $_[0]->ppi_clean_code; + return new_ppi_doc(\$code); + }; + + sub is_optional { my $self = shift; @@ -219,7 +230,7 @@ sub _parse_with_ppi { $self->ppi_clean_code($self->variable. " " .$self->ppi_clean_code); # Tokenize... - my $components = Method::Signatures::Parser->new_ppi_doc(\($self->ppi_clean_code)); + my $components = $self->ppi_doc; my $statement = $components->find_first("PPI::Statement") or sig_parsing_error("Could not understand parameter specification: @{[$self->ppi_clean_code]}"); my $tokens = [ $statement->children ]; @@ -231,7 +242,7 @@ sub _parse_with_ppi { while ($self->_extract_leading(qr{^ where $}x, $tokens)) { sig_parsing_error("'where' constraint only available under Perl 5.10 or later. Error") if $] < 5.010; - $self->where->{ $self->_extract_until(qr{^ (?: where | is | = | //= ) $}x, $tokens) }++; + push @{$self->where}, $self->_extract_until(qr{^ (?: where | is | = | //= ) $}x, $tokens); } # Extract parameter traits... @@ -336,4 +347,34 @@ sub _init_split_variable { return; } + +# Check the integrity of one piece of the signature +sub check { + my($self, $signature) = @_; + + if( $self->is_slurpy ) { + sig_parsing_error("Signature can only have one slurpy parameter") + if $signature->num_slurpy >= 1; + sig_parsing_error("Slurpy parameter '@{[$self->variable]}' cannot be named; use a reference instead") + if $self->is_named; + } + + if( $self->is_named ) { + if( $signature->num_optional_positional ) { + my $pos_var = $signature->positional_parameters->[-1]->variable; + my $var = $self->variable; + sig_parsing_error("Named parameter '$var' mixed with optional positional '$pos_var'"); + } + } + else { + if( $signature->num_named ) { + my $named_var = $signature->named_parameters->[-1]->variable; + my $var = $self->variable; + sig_parsing_error("Positional parameter '$var' after named param '$named_var'"); + } + } + + return 1; +} + 1; diff --git a/lib/Method/Signatures/Parser.pm b/lib/Method/Signatures/Parser.pm deleted file mode 100644 index 92c591f..0000000 --- a/lib/Method/Signatures/Parser.pm +++ /dev/null @@ -1,107 +0,0 @@ -package Method::Signatures::Parser; - -use strict; -use warnings; -use Carp; - -use base qw(Exporter); -our @EXPORT = qw(split_proto split_parameter extract_invocant sig_parsing_error carp_location_for); - - -sub split_proto { - my $proto = shift; - return unless $proto =~ /\S/; - - local $@ = undef; - - my $ppi = __PACKAGE__->new_ppi_doc(\$proto); - $ppi->prune('PPI::Token::Comment'); - - my $statement = $ppi->find_first("PPI::Statement"); - sig_parsing_error("Could not understand parameter list specification: $proto") - unless $statement; - my $token = $statement->first_token; - - my @proto = (''); - do { - if( $token->class eq "PPI::Token::Operator" and $token->content eq ',' ) { - push @proto, ''; - } - else { - $proto[-1] .= $token->content; - } - - $token = $token->class eq 'PPI::Token::Label' ? $token->next_token : $token->next_sibling; - } while( $token ); - - - strip_ws($_) for @proto; - - # Remove blank entries due to trailing comma. - @proto = grep { /\S/ } @proto; - - return @proto; -} - - -# Extract an invocant, if one is present... -my $IDENTIFIER = qr{ [^\W\d] \w* }x; -sub extract_invocant { - my ($param_ref) = @_; - - if ($$param_ref =~ s{ ^ (\$ $IDENTIFIER) \s* : \s* }{}x) { - return $1; - } - return; -} - - -sub strip_ws { - $_[0] =~ s{^\s+}{}; - $_[0] =~ s{\s+$}{}; -} - -# Generate cleaner error messages... -sub carp_location_for { - my ($class, $target) = @_; - $target = qr{(?!)} if !$target; - - # using @CARP_NOT here even though we're not using Carp - # who knows? maybe someday Carp will be capable of doing what we want - # until then, we're rolling our own, but @CARP_NOT is still serving roughly the same purpose - our @CARP_NOT; - local @CARP_NOT; - push @CARP_NOT, 'Method::Signatures'; - push @CARP_NOT, $class unless $class =~ /^${\__PACKAGE__}(::|$)/; - push @CARP_NOT, qw< Class::MOP Moose Mouse Devel::Declare >; - - # Skip any package in the @CARP_NOT list or their sub packages. - my $carp_not_list_re = join '|', @CARP_NOT; - my $skip = qr/^ $carp_not_list_re (?: :: | $ ) /x; - - my $level = 0; - my ($pack, $file, $line, $method); - do { - ($pack, $file, $line, $method) = caller(++$level); - } while $method !~ $target and $method =~ /$skip/ or $pack =~ /$skip/; - - return ($file, $line, $method); -} - -sub new_ppi_doc { - my $class = shift; - my $source = shift; - - require PPI; - my $ppi = PPI::Document->new($source) or - sig_parsing_error("source '$$source' cannot be parsed by PPI: " . PPI::Document->errstr); - return $ppi; -} - -sub sig_parsing_error { - my ($file, $line) = carp_location_for(__PACKAGE__, 'Devel::Declare::linestr_callback'); - my $msg = join('', @_, " in declaration at $file line $line.\n"); - die($msg); -} - -1; diff --git a/lib/Method/Signatures/Signature.pm b/lib/Method/Signatures/Signature.pm new file mode 100644 index 0000000..7e266d2 --- /dev/null +++ b/lib/Method/Signatures/Signature.pm @@ -0,0 +1,272 @@ +package Method::Signatures::Signature; + +use Mouse; +use Method::Signatures::Types; +use Method::Signatures::Parameter; +use Method::Signatures::Utils qw(new_ppi_doc sig_parsing_error DEBUG); + +my $INF = ( 0 + "inf" ) == 0 ? 9e9999 : "inf"; + +# The unmodified, uncleaned up original signature for reference +has signature_string => + is => 'ro', + isa => 'Str', + required => 1; + +# Just the parameter part of the signature, no invocant +has parameter_string => + is => 'ro', + isa => 'Str', + lazy => 1, + builder => '_build_parameter_string'; + +# A list of strings for each parameter tokenized from parameter_string +has parameter_strings => + is => 'ro', + isa => 'ArrayRef[Str]', + lazy => 1, + builder => '_build_parameter_strings'; + +# The parsed Method::Signature::Parameter objects +has parameters => + is => 'ro', + isa => 'ArrayRef[Method::Signatures::Parameter]', + default => sub { [] }; + +has named_parameters => + is => 'ro', + isa => 'ArrayRef[Method::Signatures::Parameter]', + default => sub { [] }; + +has positional_parameters => + is => 'ro', + isa => 'ArrayRef[Method::Signatures::Parameter]', + default => sub { [] }; + +has optional_parameters => + is => 'ro', + isa => 'ArrayRef[Method::Signatures::Parameter]', + default => sub { [] }; + +has optional_positional_parameters => + is => 'ro', + isa => 'ArrayRef[Method::Signatures::Parameter]', + default => sub { [] }; + +has slurpy_parameters => + is => 'ro', + isa => 'ArrayRef[Method::Signatures::Parameter]', + default => sub { [] }; + +has yadayada_parameters => + is => 'ro', + isa => 'ArrayRef[Method::Signatures::Parameter]', + default => sub { [] }; + + +sub num_named { + return scalar @{$_[0]->named_parameters}; +} + +sub num_positional { + return scalar @{$_[0]->positional_parameters}; +} + +sub num_optional { + return scalar @{$_[0]->optional_parameters}; +} + +sub num_optional_positional { + return scalar @{$_[0]->optional_positional_parameters}; +} + +sub num_slurpy { + return scalar @{$_[0]->slurpy_parameters}; +} + +sub num_yadayada { + return scalar @{$_[0]->yadayada_parameters}; +} + +# Anything we need to pull out before the invocant. +# Primary example would be the $orig for around modifiers in Moose/Mouse +has pre_invocant => + is => 'rw', + isa => 'Maybe[Str]', + default => ''; + +has invocant => + is => 'rw', + isa => 'Maybe[Str]', + default => ''; + +sub has_invocant { + return $_[0]->invocant ? 1 : 0; +} + +# How big can @_ be? +has max_argv_size => + is => 'rw', + isa => 'Int|Inf'; + +# The maximum logical arguments (name => value counts as one argument) +has max_args => + is => 'rw', + isa => 'Int|Inf'; + +# A PPI::Document representing the list of parameters +has ppi_doc => + is => 'ro', + isa => 'PPI::Document', + lazy => 1, + default => sub { + my $code = $_[0]->parameter_string; + return new_ppi_doc(\$code); + }; + +# If set, no checks will be done on the signature or parameters +has no_checks => + is => 'rw', + isa => 'Bool', + default => 0; + + +sub BUILD { + my $self = shift; + + my $idx = 0; + for my $proto (@{$self->parameter_strings}) { + DEBUG( "proto: $proto\n" ); + + my $sig = Method::Signatures::Parameter->new( + original_code => $proto, + position => $idx, + ); + $idx++ if $sig->is_positional; + + push @{$self->parameters}, $sig; + + # Handle "don't care" specifier + if ($sig->is_yadayada) { + push @{$self->slurpy_parameters}, $sig; + push @{$self->yadayada_parameters}, $sig; + next; + } + + $sig->check($self) unless $self->no_checks; + + push @{$self->named_parameters}, $sig if $sig->is_named; + push @{$self->positional_parameters}, $sig if $sig->is_positional; + push @{$self->optional_parameters}, $sig if $sig->is_optional; + push @{$self->optional_positional_parameters}, $sig + if $sig->is_optional and $sig->is_positional; + push @{$self->slurpy_parameters}, $sig if $sig->is_slurpy; + + DEBUG( "sig: ", $sig ); + } + + $self->_calculate_max_args; + $self->check unless $self->no_checks; + + return; +} + + +sub _calculate_max_args { + my $self = shift; + + # If there's a slurpy argument, the max is infinity. + if( $self->num_slurpy ) { + $self->max_argv_size($INF); + $self->max_args($INF); + + return; + } + + $self->max_argv_size( ($self->num_named * 2) + $self->num_positional ); + $self->max_args( $self->num_named + $self->num_positional ); + + return; +} + + +# Check the integrity of the signature as a whole +sub check { + my $self = shift; + + # Check that slurpy arguments come at the end + if( + $self->num_slurpy && + !($self->num_yadayada || $self->positional_parameters->[-1]->is_slurpy) + ) + { + my $slurpy_param = $self->slurpy_parameters->[0]; + sig_parsing_error("Slurpy parameter '@{[$slurpy_param->variable]}' must come at the end"); + } + + return 1; +} + + +sub _strip_ws { + $_[1] =~ s/^\s+//; + $_[1] =~ s/\s+$//; +} + + +my $IDENTIFIER = qr{ [^\W\d] \w* }x; +sub _build_parameter_string { + my $self = shift; + + my $sig_string = $self->signature_string; + my $invocant; + + # Extract an invocant, if one is present. + if ($sig_string =~ s{ ^ (\$ $IDENTIFIER) \s* : \s* }{}x) { + $self->invocant($1); + } + + # The siganture, minus the invocant, is just the list of parameters + return $sig_string; +} + + +sub _build_parameter_strings { + my $self = shift; + + my $param_string = $self->parameter_string; + return [] unless $param_string =~ /\S/; + + local $@ = undef; + + my $ppi = $self->ppi_doc; + $ppi->prune('PPI::Token::Comment'); + + my $statement = $ppi->find_first("PPI::Statement"); + sig_parsing_error("Could not understand parameter list specification: $param_string") + unless $statement; + my $token = $statement->first_token; + + my @params = (''); + do { + if( $token->class eq "PPI::Token::Operator" and $token->content eq ',' ) { + push @params, ''; + } + else { + $params[-1] .= $token->content; + } + + $token = $token->class eq 'PPI::Token::Label' ? $token->next_token : $token->next_sibling; + } while( $token ); + + + $self->_strip_ws($_) for @params; + + # Remove blank entries due to trailing comma. + @params = grep { /\S/ } @params; + + return \@params; +} + + +1; diff --git a/lib/Method/Signatures/Types.pm b/lib/Method/Signatures/Types.pm new file mode 100644 index 0000000..76be401 --- /dev/null +++ b/lib/Method/Signatures/Types.pm @@ -0,0 +1,9 @@ +package Method::Signatures::Types; + +use Mouse::Util::TypeConstraints; + +subtype 'Inf', + as 'Str', + where { $_ eq 'inf' }; + +1; diff --git a/lib/Method/Signatures/Utils.pm b/lib/Method/Signatures/Utils.pm new file mode 100644 index 0000000..d201114 --- /dev/null +++ b/lib/Method/Signatures/Utils.pm @@ -0,0 +1,64 @@ +package Method::Signatures::Utils; + +use strict; +use warnings; +use Carp; + +use base qw(Exporter); +our @EXPORT = qw(new_ppi_doc sig_parsing_error carp_location_for DEBUG); + +sub DEBUG { + return unless $Method::Signatures::DEBUG; + + require Data::Dumper; + print STDERR "DEBUG: ", map { ref $_ ? Data::Dumper::Dumper($_) : $_ } @_; +} + + +sub new_ppi_doc { + my $code = shift; + + require PPI; + my $ppi = PPI::Document->new($code) or + sig_parsing_error( + "source '$$code' cannot be parsed by PPI: " . PPI::Document->errstr + ); + + return $ppi; +}; + + +# Generate cleaner error messages... +sub carp_location_for { + my ($class, $target) = @_; + $target = qr{(?!)} if !$target; + + # using @CARP_NOT here even though we're not using Carp + # who knows? maybe someday Carp will be capable of doing what we want + # until then, we're rolling our own, but @CARP_NOT is still serving roughly the same purpose + our @CARP_NOT; + local @CARP_NOT; + push @CARP_NOT, 'Method::Signatures'; + push @CARP_NOT, $class unless $class =~ /^${\__PACKAGE__}(::|$)/; + push @CARP_NOT, qw< Class::MOP Moose Mouse Devel::Declare >; + + # Skip any package in the @CARP_NOT list or their sub packages. + my $carp_not_list_re = join '|', @CARP_NOT; + my $skip = qr/^ $carp_not_list_re (?: :: | $ ) /x; + + my $level = 0; + my ($pack, $file, $line, $method); + do { + ($pack, $file, $line, $method) = caller(++$level); + } while $method !~ $target and $method =~ /$skip/ or $pack =~ /$skip/; + + return ($file, $line, $method); +} + +sub sig_parsing_error { + my ($file, $line) = carp_location_for(__PACKAGE__, 'Devel::Declare::linestr_callback'); + my $msg = join('', @_, " in declaration at $file line $line.\n"); + die($msg); +} + +1; diff --git a/t/split_proto.t b/t/split_proto.t index 5dbdcff..fdee2aa 100644 --- a/t/split_proto.t +++ b/t/split_proto.t @@ -5,7 +5,7 @@ use warnings; use Test::More 'no_plan'; -use Method::Signatures::Parser; +use Method::Signatures::Signature; my %tests = ( '$foo' => ['$foo'], @@ -37,5 +37,10 @@ my %tests = ( ); while(my($args, $expect) = each %tests) { - is_deeply [split_proto($args)], $expect, "split_proto($args)"; + my $sig = Method::Signatures::Signature->new( + signature_string => $args, + # we just want to test the tokenizing + no_checks => 1, + ); + is_deeply $sig->parameter_strings, $expect, "split_proto($args)"; } diff --git a/t/types.t b/t/types.t index 73dc1e0..205cef2 100644 --- a/t/types.t +++ b/t/types.t @@ -21,11 +21,12 @@ note "types"; { my $want = $tests{$proto}; my $ms = Method::Signatures->new; - $ms->parse_func(proto => $proto); + $ms->parse_proto($proto); my $which = shift @$want; for my $idx (0..$#{$want}) { - is $ms->{signature}{$which}[$idx]->type, $want->[$idx] || ''; + my $method = $which.'_parameters'; + is $ms->{signature}->$method->[$idx]->type, $want->[$idx] || ''; } } } @@ -45,7 +46,7 @@ note "inject_for_type_check"; { } my $ms = My::MS->new; - my $code = $ms->parse_func( proto => 'Foo $this, :$bar, Foo::Bar :$foobar' ); + my $code = $ms->parse_proto( 'Foo $this, :$bar, Foo::Bar :$foobar' ); like $code, qr{type_check\('\$this'\)}; like $code, qr{type_check\('\$foobar'\)}; }