Search code examples
perlgotomethod-resolution-orderperlguts

How does mro, goto, and set_subname interact?


This is a complex question with regard to mro.pm, and the interplay with set_subname, and goto

In troubleshooting a problem, I think the core of my misunderstanding relates to how mro.pm works -- especially with regard to set_subname.

What is the difference between these three constructs,

  1. Plain call to set_subname

    *Foo::bar = set_subname( 'Foo::bar', $codeRef );
    
  2. Anon sub which wraps a set_subname

    *Foo::bar = sub {
      my $codeRef2 = set_subname('Foo::bar', $codeRef);
      goto $codeRef2
    };
    
  3. Anon sub which has its name set with set_subname

    *Foo::bar = set_subname(
      'Foo::bar',
      sub { goto $codeRef }
    );
    

Specifically, the Mojo test suits fails with either of these modifications with anon subs applied to Mojo::Utils's monkey_patch Running the two variants above against t/mojo/websocket_proxy.t,

  • With the 2 (second) option I have

    *{"${class}::$k"} = sub {                                                                                                                          
      my $cr = set_subname("${class}::$k", $patch{$k});                                                                                                
      goto $cr;                                                                                                                                        
    }; 
    

    And I get

    Mojo::Reactor::Poll: Timer failed: Can't locate object method "send" via package "Mojo::Transaction::HTTP" at t/mojo/websocket_proxy.t line 66.
    
  • With the 3 (third) option I have,

    *{"${class}::$k"} = set_subname("${class}::$k", sub { goto $patch{$k} })
    

    And I get

    No next::method 'new' found for Mojolicious::Routes at /usr/lib/x86_64-linux-gnu/perl/5.28/mro.pm line 30.
    

Obviously, the first version works (it's from the code I linked), the question is why are the other two variants giving me different errors (especially the second variant) and what's happening there -- why don't they work?


Solution

  • Your second option is not working because the sub you are using as a wrapper does not match the prototype of the inner sub. monkey_patch is not only used for methods, and this changes how some functions are parsed. In particular, Mojo::Util::steady_time has an empty prototype and is often called without using parenthesis.

    *{"${class}::$k"} = Sub::Util::set_prototype(
      Sub::Util::prototype( $patch{$k} ),
      Sub::Util::set_subname(
        "${class}::$k",
        sub {
          my $cr = Sub::Util::set_subname("${class}::$k", $patch{$k});
          goto $cr;
        }
      )
    );
    

    The third construct is not working because you are using goto to remove the renamed wrapper sub from the call stack, leaving only the inner sub which has no name. This breaks next::method's ability to find the correct method name.