tobyink/p5-type-tiny

Type::Params: unexpected error when die'ing in goto_next in a multiple signature

djerius opened this issue · 2 comments

Type::Tiny 2.004000

I'm trying to handle a legacy API issue by using multiple signatures. The old API is

foo( $hashref, %options )

i.e., first is positional, rest are named.

The new API is

foo( head => $hashref, %options );

i.e., all are named.

In order to transparently convert from the first to the second, I'm doing this:

sub goto_next {
    $_[1]{head} = $_[0];
    return $_[1];
}

signature_for foo => (
    multiple => [ {
            # legacy
            goto_next => \&goto_next,
            head      => [Str],
            named     => [ uri => Optional [Any], head => Optional [Any] ],
        },
        {
            # new
            named => [ head => Str, uri => Optional [Any] ]
        },
    ],
);
sub foo { p @_ }

I need to provide the head parameter to the named options for the legacy API otherwise the constructed parameter class doesn't have a head attribute.

This however leaves open the possibility that this call:

foo( $head_value, head => $another_head_value );

could arise if someone tweaked %options to include head and forgot to fix the actual call.

So I figured I could catch this via:

sub goto_next {
    die "legacy API: do not supply a named 'head' parameter"
      if $_[1]->has_head;
    $_[1]{head} = $_[0];
    return $_[1];
}

But this call

foo( 'foo', head => undef );

results in a rather unexpected error:

$ perl boom.pl
Use of uninitialized value in pattern match (m//) at ../5.36/lib/perl5/site_perl/5.36.1/Error/TypeTiny.pm line 61.
Use of uninitialized value in pattern match (m//) at ../5.36/lib/perl5/site_perl/5.36.1/Error/TypeTiny.pm line 61.
Parameter validation failed at file? line NaN.

While debugging, I reverted to a similar approach with a single signature, i.e.

signature_for foo => (
    goto_next => \&goto_next
    head  => [Str],
    named => [ uri => Optional [Any], head => Optional [Any] ],
);

With the result that the call

foo( 'foo', head => undef );

results in the expected outcome:

$ perl tst.pl
legacy API: do not supply a named 'head' parameter at tst.pl line 9.

BTW, this is rather low priority, as there's a simple (rather less convoluted) workaround:

sub foo (@args){
    state $signature = signature( named => [ head => Str, uri => Optional [Any] ] );

    # support legacy API: foo( $head, %options )
    if ( @args %2 ) {
        my $head = shift @args;
        my %arg = @args;
        die "legacy API: do not supply a named 'head' parameter"
          if exists $arg{head};
        $arg{head} = $head;
        @args = %arg;
    }
    my $opt = $signature->( @args );

    p $opt;
}