diff --git a/lib/B/Deparse.pm b/lib/B/Deparse.pm index 6b52d6757be3..a903ee782e65 100644 --- a/lib/B/Deparse.pm +++ b/lib/B/Deparse.pm @@ -7,8 +7,9 @@ # This is based on the module of the same name by Malcolm Beattie, # but essentially none of his code remains. -package B::Deparse 1.87; +package B::Deparse 1.88; use strict; +use builtin qw( true false ); use Carp; use B qw(class main_root main_start main_cv svref_2object opnumber perlstring OPf_WANT OPf_WANT_VOID OPf_WANT_SCALAR OPf_WANT_LIST @@ -1068,6 +1069,16 @@ sub ambient_pragmas { $self->{'ambient_hinthash'} = $hinthash; } +sub ambient_pragmas_from_caller { + my $self = shift; + my ($hint_bits, $warning_bits, $hinthash) = (caller(0))[8, 9, 10]; + $self->ambient_pragmas( + hint_bits => $hint_bits, + warning_bits => $warning_bits, + '%^H' => $hinthash, + ); +} + # This method is the inner loop, so try to keep it simple sub deparse { my $self = shift; @@ -1182,11 +1193,14 @@ sub pad_subs { # deparse_multiparam(): deparse, if possible, a sequence of ops into a -# subroutine signature. If possible, returns a string representing the -# signature syntax, minus the surrounding parentheses. +# subroutine signature. If possible, returns either: +# (if $use_feature_sig is true): a string representing the signature syntax, +# minus the surrounding parentheses. +# (if $use_feature_sig is false): a string of perl code that approximates +# the behaviour of the signature. sub deparse_multiparam { - my ($self, $topop, $cv) = @_; + my ($self, $topop, $cv, $use_feature_sig) = @_; $topop = $topop->first; return unless $$topop and $topop->name eq 'lineseq'; @@ -1213,16 +1227,20 @@ sub deparse_multiparam { my @param_padix = splice @rest, 0, $nparams, (); my ($slurpy_padix) = @rest; - my @sig; + my @param_padname = map { $_ ? $self->padname($_) : '$' } @param_padix; + my ($slurpy_padname) = map { $_ ? $self->padname($_) : $slurpy } $slurpy_padix; + my %parami_for_padix; # Initial scalars foreach my $parami ( 0 .. $max_args-1 ) { my $padix = $param_padix[$parami]; - $sig[$parami] = $self->padname($padix) || '$'; $parami_for_padix{$padix} = $parami; } + my @param_defmode; + my @param_defexpr; + $o = $o->sibling; for (; $o and !null $o; $o = $o->sibling) { # Look for OP_NULL[OP_PARAMTEST[OP_PARAMSTORE]] @@ -1234,23 +1252,17 @@ sub deparse_multiparam { my $parami = $parami_for_padix{$ofirst->targ}; - my $assign = "="; - $assign = "//=" if $ofirst->private == OPpPARAM_IF_UNDEF; - $assign = "||=" if $ofirst->private == OPpPARAM_IF_FALSE; - - length $sig[$parami] > 1 ? - ( $sig[$parami] .= ' ' ) : - ( $sig[$parami] = '$' ); # intentionally no trailing space + my $defmode = "="; + $defmode = "//=" if $ofirst->private == OPpPARAM_IF_UNDEF; + $defmode = "||=" if $ofirst->private == OPpPARAM_IF_FALSE; + $param_defmode[$parami] = $defmode; my $defop = $ofirst->first->first; - if ($defop->name eq "stub") { - $sig[$parami] .= "$assign"; - } - else { - my $def = $self->deparse($defop, 7); - $def = "($def)" if $defop->flags & OPf_PARENS; + if ($defop->name ne "stub") { + my $expr = $self->deparse($defop, 7); + $expr = "($expr)" if $defop->flags & OPf_PARENS; - $sig[$parami] .= "$assign $def"; + $param_defexpr[$parami] = $expr; } } } @@ -1258,15 +1270,101 @@ sub deparse_multiparam { if ($cv->CvFLAGS & CVf_IsMETHOD) { # Remove the implied `$self` argument warn "Expected first signature argument to be named \$self" - unless @sig and $sig[0] eq '$self'; - shift @sig; + unless @param_padname and $param_padname[0] eq '$self'; + + shift @param_padix; + shift @param_padname; + shift @param_defmode; + shift @param_defexpr; + } + + if ($use_feature_sig) { + my @sig; + + foreach my $parami ( 0 .. $#param_padix ) { + my $param_sig = $param_padname[$parami]; + if ($param_defmode[$parami]) { + length $param_sig > 1 ? + ( $param_sig .= ' ' ) : + ( $param_sig = '$' ); # intentionally no trailing space + + $param_sig .= $param_defmode[$parami]; + + my $defexpr = $param_defexpr[$parami]; + $param_sig .= " $defexpr" if defined $defexpr; + } + + push @sig, $param_sig; + } + + push @sig, $slurpy_padname if $slurpy; + + return join(", ", @sig); + } + + # Approximate the behaviour using plain perl code + my $code = ""; + + $code .= <<"EOF" if !$slurpy_padix; +die sprintf("Too many arguments for subroutine at %s line %d.\\n", (caller)[1, 2]) unless \@_ <= $nparams; +EOF + + $code .= <<"EOF" if $min_args > 0; +die sprintf("Too few arguments for subroutine at %s line %d.\\n", (caller)[1, 2]) unless \@_ >= $min_args; +EOF + + $code .= < $nparams && ((\@_ - $nparams) & 1); +EOF + + foreach my $parami ( 0 .. $#param_padix ) { + my $argix = $parami; + + my $stmt = "my $param_padname[$parami] = "; + + if (my $defmode = $param_defmode[$parami]) { + my $defexpr = $param_defexpr[$parami]; + # Optional parameter + + if (length $param_padname[$parami] > 1) { + # Named optional param + if ($defmode eq "=") { + $stmt .= "\@_ > $argix ? \$_[$argix] : $defexpr"; + } + else { + $defmode =~ s/=\z//; + $stmt .= "\$_[$argix] $defmode $defexpr"; + } + } + else { + # Anonymous optional param. This does not create or assign a + # variable but we still evaluate the defaulting expression for + # side-effects + my $cond = ( $defmode eq "//=" ) ? "defined \$_[$argix]" : + ( $defmode eq "||=" ) ? "\$_[$argix]" : + "\@_ > $argix"; + $stmt = "$defexpr unless $cond"; + } + } + else { + # Mandatory parameter + + # Anonymous mandatory params can be entirely ignored. Their pad + # index will be zero. + $param_padix[$parami] or next; + + $stmt .= "\$_[$argix]"; + } + + $code .= "$stmt;\n"; } if ($slurpy) { - push @sig, $slurpy_padix ? $self->padname($slurpy_padix) : $slurpy; + $code .= "my $slurpy_padname = \@_[$nparams..\$#_];\n"; } - return join(", ", @sig); + $code =~ s/;\n\z//; + return $code; } # deparse_argops(): deparse, if possible, a sequence of argcheck + argelem @@ -1424,10 +1522,10 @@ Carp::confess("NULL in deparse_sub") if !defined($cv) || $cv->isa("B::NULL"); Carp::confess("SPECIAL in deparse_sub") if $cv->isa("B::SPECIAL"); local $self->{'curcop'} = $self->{'curcop'}; - my $has_sig = $self->feature_enabled('signatures'); + my $use_feature_sig = $self->feature_enabled('signatures'); if ($cv->FLAGS & SVf_POK) { my $myproto = $cv->PV; - if ($has_sig) { + if ($use_feature_sig) { push @attrs, "prototype($myproto)"; } else { @@ -1447,7 +1545,7 @@ Carp::confess("SPECIAL in deparse_sub") if $cv->isa("B::SPECIAL"); local($self->{'curcvlex'}); local(@$self{qw'curstash warnings hints hinthash'}) = @$self{qw'curstash warnings hints hinthash'}; - my $body; + my $body = ""; my $root = $cv->ROOT; local $B::overlay = {}; if (not null $root) { @@ -1461,16 +1559,21 @@ Carp::confess("SPECIAL in deparse_sub") if $cv->isa("B::SPECIAL"); # Try to deparse first subtree as a signature if possible. # Top of signature subtree has an ex-argcheck as a placeholder - if ( $has_sig - and $$firstop - and $firstop->name eq 'null' - and $firstop->targ == OP_ARGCHECK - ) { - my ($mysig) = $self->deparse_multiparam($firstop, $cv) // - $self->deparse_argops($firstop, $cv); - if (defined $mysig) { - $sig = $mysig; - $firstop = $is_list ? $firstop->sibling : undef; + if ($$firstop and $firstop->name eq 'null' and $firstop->targ == OP_ARGCHECK) { + if ($use_feature_sig) { + my ($mysig) = $self->deparse_multiparam($firstop, $cv, true) // + $self->deparse_argops($firstop, $cv); + if (defined $mysig) { + $sig = $mysig; + $firstop = $is_list ? $firstop->sibling : undef; + } + } + else { + my $prelude = $self->deparse_multiparam($firstop, $cv, false); + if (defined $prelude) { + $body .= $prelude; + $firstop = $is_list ? $firstop->sibling : undef; + } } } @@ -1479,8 +1582,8 @@ Carp::confess("SPECIAL in deparse_sub") if $cv->isa("B::SPECIAL"); for (my $o = $firstop; $$o; $o=$o->sibling) { push @ops, $o; } - $body = $self->lineseq(undef, 0, @ops).";"; - if (!$has_sig and $ops[-1]->name =~ /^(next|db)state$/) { + $body .= $self->lineseq(undef, 0, @ops).";"; + if (!$use_feature_sig and $ops[-1]->name =~ /^(next|db)state$/) { # this handles void context in # use feature signatures; sub ($=1) {} $body .= "\n()"; @@ -1492,10 +1595,10 @@ Carp::confess("SPECIAL in deparse_sub") if $cv->isa("B::SPECIAL"); } } elsif ($firstop) { - $body = $self->deparse($root->first, 0); + $body .= $self->deparse($root->first, 0); } else { - $body = ';'; # stub sub + $body .= ';'; # stub sub } my $l = ''; @@ -6967,6 +7070,11 @@ sub pp_argdefelem { } +sub pp_multiparam { + die "Unable to handle PP_MULTIPARAM outside of a regular subroutine signature position"; +} + + sub pp_pushdefer { my $self = shift; my($op, $cx) = @_; @@ -7009,9 +7117,9 @@ B B<-MO=Deparse>[B<,-d>][B<,-f>I][B<,-p>][B<,-q>][B<,-l>] =head1 DESCRIPTION -B::Deparse is a backend module for the Perl compiler that generates +C is a backend module for the Perl compiler that generates perl source code, based on the internal compiled structure that perl -itself creates after parsing a program. The output of B::Deparse won't +itself creates after parsing a program. The output of C won't be exactly the same as the original source, since perl doesn't keep track of comments or whitespace, and there isn't a one-to-one correspondence between perl's syntactical constructions and their @@ -7020,34 +7128,34 @@ option, the output also includes parentheses even when they are not required by precedence, which can make it easy to see if perl is parsing your expressions the way you intended. -While B::Deparse goes to some lengths to try to figure out what your +While C goes to some lengths to try to figure out what your original program was doing, some parts of the language can still trip it up; it still fails even on some parts of Perl's own test suite. If you encounter a failure other than the most common ones described in -the BUGS section below, you can help contribute to B::Deparse's +the BUGS section below, you can help contribute to C's ongoing development by submitting a bug report with a small example. =head1 OPTIONS As with all compiler backend options, these must follow directly after -the '-MO=Deparse', separated by a comma but not any white space. +the C<-MO=Deparse>, separated by a comma but not any white space. =over 4 =item B<-d> -Output data values (when they appear as constants) using Data::Dumper. -Without this option, B::Deparse will use some simple routines of its -own for the same purpose. Currently, Data::Dumper is better for some +Output data values (when they appear as constants) using L. +Without this option, C will use some simple routines of its +own for the same purpose. Currently, L is better for some kinds of data (such as complex structures with sharing and self-reference) while the built-in routines are better for others (such as odd floating-point values). =item B<-f>I -Normally, B::Deparse deparses the main code of a program, and all the subs -defined in the same file. To include subs defined in +Normally, C deparses the main code of a program, and all the +subs defined in the same file. To include subs defined in other files, pass the B<-f> option with the filename. You can pass the B<-f> option several times, to include more than one secondary file. (Most of the time you don't want to @@ -7056,12 +7164,12 @@ defined in the scope of a B<#line> directive with two parameters. =item B<-l> -Add '#line' declarations to the output based on the line and file +Add C<#line> declarations to the output based on the line and file locations of the original code. =item B<-p> -Print extra parentheses. Without this option, B::Deparse includes +Print extra parentheses. Without this option, C includes parentheses in its output only when they are needed, based on the structure of your program. With B<-p>, it uses parentheses (almost) whenever they would be legal. This can be useful if you are used to @@ -7101,8 +7209,8 @@ making clear how the parameters are actually passed to C. =item B<-q> Expand double-quoted strings into the corresponding combinations of -concatenation, uc, ucfirst, lc, lcfirst, quotemeta, and join. For -instance, print +concatenation, C, C, C, C, C, and +C. For instance, print print "Hello, $world, @ladies, \u$gentlemen\E, \u\L$me!"; @@ -7113,13 +7221,13 @@ as Note that the expanded form represents the way perl handles such constructions internally -- this option actually turns off the reverse -translation that B::Deparse usually does. On the other hand, note that +translation that C usually does. On the other hand, note that C<$x = "$y"> is not the same as C<$x = $y>: the former makes the value -of $y into a string before doing the assignment. +of C<$y> into a string before doing the assignment. =item B<-s>I -Tweak the style of B::Deparse's output. The letters should follow +Tweak the style of C's output. The letters should follow directly after the 's', with no space or punctuation. The following options are available: @@ -7169,7 +7277,7 @@ conventional values include 0, 1, 42, '', 'foo', and 'Useless use of constant omitted' (which may need to be B<-sv"'Useless use of constant omitted'."> or something similar depending on your shell). The default is '???'. -If you're using B::Deparse on a module or other file that's require'd, +If you're using C on a module or other file that's C'd, you shouldn't use a value that evaluates to false, since the customary true constant at the end of a module will be in void context when the file is compiled as a main program. @@ -7181,7 +7289,7 @@ file is compiled as a main program. Expand conventional syntax constructions into equivalent ones that expose their internal operation. I should be a digit, with higher values meaning more expansion. As with B<-q>, this actually involves turning off -special cases in B::Deparse's normal operations. +special cases in C's normal operations. If I is at least 3, C loops will be translated into equivalent while loops with continue blocks; for instance @@ -7238,7 +7346,7 @@ turns into $nice ? do { print 'hi' } : do { print 'bye' }; Long sequences of elsifs will turn into nested ternary operators, which -B::Deparse doesn't know how to indent nicely. +C doesn't know how to indent nicely. =back @@ -7253,7 +7361,7 @@ B::Deparse doesn't know how to indent nicely. =head2 Description -B::Deparse can also be used on a sub-by-sub basis from other perl +C can also be used on a sub-by-sub basis from other perl programs. =head2 new @@ -7304,15 +7412,16 @@ use re; =back -Ordinarily, if you use B::Deparse on a subroutine which has +Ordinarily, if you use C on a subroutine which has been compiled in the presence of one or more of these pragmas, the output will include statements to turn on the appropriate -directives. So if you then compile the code returned by coderef2text, -it will behave the same way as the subroutine which you deparsed. +directives. So if you then compile the code returned by +L, it will behave the same way as the subroutine +which you deparsed. However, you may know that you intend to use the results in a particular context, where some pragmas are already in scope. In -this case, you use the B method to describe the +this case, you use the L method to describe the assumptions you wish to make. Not all of the options currently have any useful effect. See @@ -7332,7 +7441,7 @@ expect. =item $[ -Takes a number, the value of the array base $[. +Takes a number, the value of the array base C<$[>. Obsolete: cannot be non-zero. =item bytes @@ -7379,7 +7488,7 @@ See L for more information about lexical warnings. =item warning_bits These two parameters are used to specify the ambient pragmas in -the format used by the special variables $^H and ${^WARNING_BITS}. +the format used by the special variables C<$^H> and C<${^WARNING_BITS}>. They exist principally so that you can write code like: @@ -7392,7 +7501,8 @@ They exist principally so that you can write code like: ); } which specifies that the ambient pragmas are exactly those which -are in scope at the point of calling. +are in scope at the point of calling. However, see also +L. =item %^H @@ -7401,6 +7511,16 @@ stored in the special hash %^H. =back +=head2 ambient_pragmas_from_caller + + $deparse->ambient_pragmas_from_caller() + +A convenient shortcut for setting the hints and warnings ambient pragmas to +those of the immediately calling code. This uses the +L function to determine the hints and warnings bits in +effect at the callsite to this method, and sets those as the ambient settings +for the deparser. + =head2 coderef2text $body = $deparse->coderef2text(\&func) @@ -7414,6 +7534,31 @@ want to eval the result, you should prepend "sub subname ", or "sub " for an anonymous function constructor. Unless the sub was defined in the main:: package, the code will include a package declaration. +Normally, C will emit code that includes the L pragma +if required to enable features that are used in the fragment that follows. +However, as L emits only the body of a subroutine and expects +the caller to prepend the C and optional name onto the beginning of it, +it will not have the opportunity to emit a C if the +subroutine uses a signature, and the signatures feature is not enabled in the +ambient pragmas. + +In the particular situation of a subroutine that uses the C +feature to parse its arguments being passed to L when the +feature is B enabled in L, C will attempt +to emit pure-perl code that emulates the behaviour of the signature as closely +as possible. This is performed on a B basis. It is not +guaranteed to perfectly capture the semantics of the signature's behaviour, +only to offer a human-readable suggestion as to what it might do. +Furthermore, it is not guaranteed to be able to reproduce every possible +behaviour of signatures in future versions of Perl. It may be that a future +version introduces a behaviour that does not have a tidy way to express it in +this pure-perl emulation code without using the C feature. + +If this is of importance to you, make sure to use the L or +L method to enable the C feature, +ensuring that C will use it to deparse subroutines that use +signatures. + =head1 BUGS =over 4 @@ -7426,7 +7571,7 @@ C, C, C and C. Excepting those listed above, we're currently unable to guarantee that -B::Deparse will produce a pragma at the correct point in the program. +C will produce a pragma at the correct point in the program. (Specifically, pragmas at the beginning of a block often appear right before the start of the block instead.) Since the effects of pragmas are often lexically scoped, this can mean @@ -7436,7 +7581,7 @@ than in the input file. =item * In fact, the above is a specific instance of a more general problem: -we can't guarantee to produce BEGIN blocks or C declarations in +we can't guarantee to produce C blocks or C declarations in exactly the right place. So if you use a module which affects compilation (such as by over-riding keywords, overloading constants or whatever) then the output code might not work as intended. @@ -7444,8 +7589,8 @@ then the output code might not work as intended. =item * Some constants don't print correctly either with or without B<-d>. -For instance, neither B::Deparse nor Data::Dumper know how to print -dual-valued scalars correctly, as in: +For instance, neither C nor L know how to +print dual-valued scalars correctly, as in: use constant E2BIG => ($!=7); $y = E2BIG; print $y, 0+$y; @@ -7475,7 +7620,7 @@ which is not, consequently, deparsed correctly. =item * Lexical (my) variables declared in scopes external to a subroutine -appear in coderef2text output text as package variables. This is a tricky +appear in L output text as package variables. This is a tricky problem, as perl has no native facility for referring to a lexical variable defined within a different scope, although L is a good start. diff --git a/lib/B/Deparse.t b/lib/B/Deparse.t index 2742bf40354d..2bb77852f1c8 100644 --- a/lib/B/Deparse.t +++ b/lib/B/Deparse.t @@ -13,7 +13,7 @@ BEGIN { use warnings; use strict; -my $tests = 53; # not counting those in the __DATA__ section +my $tests = 72; # not counting those in the __DATA__ section use B::Deparse; my $deparse = B::Deparse->new(); @@ -67,15 +67,7 @@ while () { my $code = "$meta{context};\n" . <<'EOC' . "sub {$input\n}"; # Tell B::Deparse about our ambient pragmas -my ($hint_bits, $warning_bits, $hinthash); -BEGIN { - ($hint_bits, $warning_bits, $hinthash) = ($^H, ${^WARNING_BITS}, \%^H); -} -$deparse->ambient_pragmas ( - hint_bits => $hint_bits, - warning_bits => $warning_bits, - '%^H' => $hinthash, -); +$deparse->ambient_pragmas_from_caller; EOC my $coderef = eval $code; @@ -103,17 +95,7 @@ EOC } # Reset the ambient pragmas -{ - my ($b, $w, $h); - BEGIN { - ($b, $w, $h) = ($^H, ${^WARNING_BITS}, \%^H); - } - $deparse->ambient_pragmas ( - hint_bits => $b, - warning_bits => $w, - '%^H' => $h, - ); -} +$deparse->ambient_pragmas_from_caller; use constant 'c', 'stuff'; is((eval "sub ".$deparse->coderef2text(\&c))->(), 'stuff', @@ -584,6 +566,108 @@ EOF qr/ +method m \(\) \{\n +\$x\+\+;\n +\}/, "feature class method deparses as method"; +# GH#23699 +{ + my $signatured_sub = do { + use feature qw( signatures ); + sub ($x, $y) { return $x + $y; } + }; + + { + no feature qw( signatures ); + $deparse->ambient_pragmas_from_caller; + my $deparsed = $deparse->coderef2text( $signatured_sub ); + unlike $deparsed, qr/^\(\$x, \$y\) \{/, + 'Deparsed signatured sub under no feature qw( signatures )'; + } + + { + use feature qw( signatures ); + $deparse->ambient_pragmas_from_caller; + my $deparsed = $deparse->coderef2text( $signatured_sub ); + like $deparsed, qr/^\(\$x, \$y\) \{/, + 'Deparsed signatured sub under use feature qw( signatures )'; + } + + { + use v5.36; + $deparse->ambient_pragmas_from_caller; + my $deparsed = $deparse->coderef2text( $signatured_sub ); + like $deparsed, qr/^\(\$x, \$y\) \{/, + 'Deparsed signatured sub under use v5.36'; + } +} + +{ + # Ability to deparse various kinds of signature into non-feature signatures + # context + no feature qw( signatures ); + $deparse->ambient_pragmas_from_caller; + + use feature qw( signatures ); + my $deparsed; + + # These tests are all somewhat fragile as they depend on the exact + # pure-perl transliteration of OP_MULTIPARAM, as performed by B/Deparse.pm + + $deparsed = $deparse->coderef2text( sub () { } ); + like $deparsed, + qr/die .*Too many arguments for subroutine at.* unless \@_ <= 0/m, + 'Deparsed signature empty max bounds'; + + $deparsed = $deparse->coderef2text( sub ($x, $y) { } ); + like $deparsed, + qr/die .*Too many arguments for subroutine at.* unless \@_ <= 2/m, + 'Deparsed signature two-args max bounds'; + like $deparsed, + qr/die .*Too few arguments for subroutine at.* unless \@_ >= 2/m, + 'Deparsed signature two-args min bounds'; + like $deparsed, + qr/my \$x = \$_\[0];/m, + 'Deparsed signature two-args arg 0'; + like $deparsed, + qr/my \$y = \$_\[1];/m, + 'Deparsed signature two-args arg 1'; + + $deparsed = $deparse->coderef2text( sub ($one = 1, $two //= 2, $three ||= 3) { } ); + like $deparsed, + qr/my \$one = \@_ > 0 \? \$_\[0] : 1;/m, + 'Deparsed signature with defaults arg 0'; + like $deparsed, + qr/my \$two = \$_\[1] \/\/ 2;/m, + 'Deparsed signature with defaults arg 1'; + like $deparsed, + qr/my \$three = \$_\[2] \|\| 3;/m, + 'Deparsed signature with defaults arg 2'; + + $deparsed = $deparse->coderef2text( sub ($, $ = IFMISSING(), $ //= IFUNDEF(), $ ||= IFFALSE()) { } ); + unlike $deparsed, + qr/\$_\[0];/m, + 'Deparsed signature anon args 0'; + like $deparsed, + qr/IFMISSING\(\) unless \@_ > 1;/m, + 'Deparsed signature anon args 1'; + like $deparsed, + qr/IFUNDEF\(\) unless defined \$_\[2];/m, + 'Deparsed signature anon args 2'; + like $deparsed, + qr/IFFALSE\(\) unless \$_\[3];/m, + 'Deparsed signature anon args 3'; + + $deparsed = $deparse->coderef2text( sub ($z, @rest) { } ); + unlike $deparsed, + qr/die .*Too many arguments for subroutine at.*/m, + 'Deparsed signature with slurpy has no max bounds'; + like $deparsed, + qr/die .*Too few arguments for subroutine at.* unless \@_ >= 1/m, + 'Deparsed signature with slurpy min bounds'; + like $deparsed, + qr/my \$z = \$_\[0];/m, + 'Deparsed signature with slurpy arg 0'; + like $deparsed, + qr/my \@rest = \@_\[1..\$#_];/m, + 'Deparsed signature with slurpy slurpy'; +} done_testing($tests);