5

Perl provides an API via CORE::prototype, that allows you to get a prototype. This is further documented by Sub::Util which is the documented method for working with subs,

Sub::Util::prototype,

Returns the prototype of the given $code reference, if it has one, as a string. This is the same as the CORE::prototype operator; it is included here simply for symmetry and completeness with the other functions.

However, I don't see anything anywhere on how to get the signatures in runtime? Does perl make this available?

Evan Carroll
  • 78,363
  • 46
  • 261
  • 468
  • A prototype affects how the call to the sub is parsed, and thus must be known outside of the sub. A signature, like the remainder of the body of the sub, does not need to be known outside of the sub. So there's no introspection mechanism short of walking through the ops (like Deparse does) – ikegami Sep 11 '20 at 04:32

4 Answers4

5

This is very ... indirect, but deparse the sub and parse the signature code.

sub foo ($bar) { return 0 }

use B::Deparse;
$foo = B::Deparse->new->coderef2text(\&foo);

# contents of foo:
# BEGIN {${^WARNING_BITS} = "\x10\x01\x00\x00\x00\x50\x04\x00\x00\x00\x00\x00\x00\x55\x50\x55\x50\x51\x01"}
# use feature 'signatures';
# die sprintf("Too many arguments for subroutine at %s line %d.\n", (caller)[1, 2]) unless @_ <= 1;
# die sprintf("Too few arguments for subroutine at %s line %d.\n", (caller)[1, 2]) unless @_ >= 1;
# my $bar = $_[0];
# return 0;

@foo = split /\n/, $foo;
if ($foo[2] =~ /use feature 'signatures'/ &&
        $foo[3] =~ /Too many arguments/ &&
        $foo[4] =~ /Too few arguments/) {
    @sig = ();
    $n = 5;
    do {
        ($sig) = $foo[$n] =~ /my (\W\w+) = /;
        push @sig,$sig if $sig;
        $n++;
    } while ($sig);
    print "Signature is (", join(",",@sig), ")\n";
}
mob
  • 117,087
  • 18
  • 149
  • 283
2

This is currently not possible, for the same reason why traditional argument parsing (my ($foo, $bar) = @_;) isn't: it's internal to the subroutine.

It has been suggested before to add such a thing, but currently it doesn't appear to be likely.

Leon Timmermans
  • 30,029
  • 2
  • 61
  • 110
1

As of Perl 5.36 (at least in my environment), mob's answer no longer works as there are multiple "leading lines":

$VAR1 = '{';
$VAR2 = '    do {';
$VAR3 = '        package My::Package;';
$VAR4 = '        BEGIN {${^WARNING_BITS} = "\\x55\\x55\\x55\\x55\\x55\\x55\\x55\\x55\\x55\\x55\\x55\\x55\\x55\\x55\\x54\\x55\\x55\\x55\\x55\\x55\\x55"}';
$VAR5 = '        use strict;';
$VAR6 = '        use feature \'current_sub\', \'evalbytes\', \'fc\', \'say\', \'signatures\', \'state\', \'switch\', \'unicode_strings\', \'unicode_eval\';';
$VAR7 = '        die sprintf("Too few arguments for subroutine at %s line %d.\\n", (caller)[1, 2]) unless @_ >= 1;';
$VAR8 = '        my $class = $_[0];';
$VAR9 = '        my @args = @_[1 .. $#_]';

Here is a quick alternative which I've created which seem to work for me:

sub _get_signature ($called_sub) {
    my $signature = q{};
    if (
        !eval {
            require B::Deparse;
            my ( $found_feature, $found_sig, @sig, @lines );
            @lines = split( /\n/, B::Deparse->new->coderef2text( \&$called_sub ) );
            foreach my $line (@lines) {
                if ( $line =~ /\w*\};/ ) {    # we've reach the end of the "do" block or similar
                    last;
                }
                if ( $line =~ /\w*use feature/ ) {
                    if ( $line =~ /signatures/ ) {
                        $found_feature = 1;
                        next;
                    }
                    last;    # no signatures
                }
                if ( $found_feature && $line =~ /^\s*my (\W\w+) =/ ) {
                    push( @sig, $1 );
                    $found_sig = 1;
                    next;
                }
                if ($found_sig) {
                    last; # if we have started to find signatures and then stopped, we've reached the end of them.
                }
            }
            if ($found_sig) {
                $signature = join( q{, }, @sig );
            }
            1;
        }
    ) {
        croak("Unable to produce signatures due to $@");
    }
    return "($signature)";
}
Richy B.
  • 1,619
  • 12
  • 20
-1

From irc.freenode.net/#perl,

15:03 < Grinnz> there's no perl level api for that

That's pretty much a perl demiboss. He pointed me to this work from Nov 2019 which starts down the path of "Signature introspection API."

Evan Carroll
  • 78,363
  • 46
  • 261
  • 468