hanenkamp has asked for the wisdom of the Perl Monks concerning the following question:

I know I'm stretching things a bit, but I really need the ability to conveniently wrap one object inside of another. In this way, the wrapper can then modify aspects of calls or returns meant for the wrappee. I'm doing this through the creation of a master wrapper class which then provides any derived wrapper with all the tools needed to perform the wrapping without all the nasty bits.

However, I've run into a problem whereby AUTOLOAD ends up getting called recursively when it seems that it should be calling a specific method in the derived class. The call is also invoking the "Use of inherited AUTOLOAD for non-method XXX is deprecated" warning, even though I'm using method call syntax.

Anyway, the code goes something like this:

package Wrapper; sub new { my ($class, $wrappee) = @_; bless { wrappee => $wrappee }, $class; } sub AUTOLOAD { my ($self, @args) = @_; my ($sub) = $AUTOLOAD =~ /([^:])$/; my $type = ...; # figured from other info $self->wrap($sub, $type, @args); } sub wrap { my ($self, $sub, $type, @args) = @_; if ($type = 'A') { $self->wrap_A($sub, @args); } elsif ($type = 'B') { $self->wrap_B($sub, @args); } elsif ($type = 'C') { $self->wrap_C($sub, @args); } else { die "Bad type $type"; } } # defaults just pass call onto wrappee. sub wrap_A { ... } sub wrap_B { ... } sub wrap_C { ... } package Wrapper::First; use base 'Wrapper'; # overrides wrap to wrap all method types sub wrap { ... } package Wrapper::Second; use base 'Wrapper'; # overrides wrap_B to wrap all methods of type B sub wrap_B { ... }

I'm not having a problem when I use code like Wrapper::First, but Wrapper::Second causes the warning and results in a call going to AUTOLOAD first, then to wrap, then instead of wrap_B in Wrapper::Second back to AUTOLOAD, which leads to deep recursion. (In my real code, I anticipated encountering this if I happened to have a misspelt method name, so I have a check that dies when the recursion depth gets too high.)

My real code is a lot more complicated than this, so I don't doubt that the real problem isn't shown here. However, does anyone know why the $self->wrap_B(...) calls are being interpreted as subroutine calls causing the inherited AUTOLOAD warning to popup? I think this must be the root of the problem, but I don't know why it's happening.

Replies are listed 'Best First'.
Re: Psycho AUTOLOAD question
by chromatic (Archbishop) on Aug 08, 2003 at 16:50 UTC

    Nothing in the code shown jumps out at me, so the problem is likely elsewhere. If you can cut it down to a 20 line test case, please post that.

    Otherwise, I recommend Class::Delegate or Class::Delegation. This is a solved problem; save yourself some time.

Re: Psycho AUTOLOAD question
by BrowserUk (Patriarch) on Aug 08, 2003 at 17:09 UTC

    Shouldn't

    my ($sub) = $AUTOLOAD =~ /([^:])$/;

    be

    my ($sub) = $AUTOLOAD =~ /([^:]+)$/;

    ? Or do you only need the last non-':' character of the subname?


    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "When I'm working on a problem, I never think about beauty. I think only how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong." -Richard Buckminster Fuller
    If I understand your problem, I can solve it! Of course, the same can be said for you.

Re: Psycho AUTOLOAD question
by hanenkamp (Pilgrim) on Aug 09, 2003 at 03:25 UTC

    There is a typo in the above code. It's just pseudo-code representative of the problem, so typos above don't matter.

    If the pseudocode is fleshed out to be runnable it doesn't exhibit any problems. The problem seems to lie in the fact that the call $self->wrap_B($sub, @args) in my real code is being interpreted as a subroutine.

    For the sake of demonstration, here's the real code:

    sub mediate { my ($self, $sub, $type, @args) = @_; if ($type eq 'A') { $self->mediate_action($sub, @args); } elsif ($type eq 'D') { $self->mediate_document($sub, @args); # 366 } elsif ($type eq 'O') { $self->mediate_operation($sub, @args); } elsif ($type eq 'P') { $self->mediate_property($sub, @args); } else { die "What in the ...?"; } }

    When I attempt to run the offending code, the line marked above causes recursion and I get, Use of inherited AUTOLOAD for non-method Contentment::Mediator::XSLT::mediate_document() is deprecated at {long-path}/Mediator.pm line 366. This is just plain screwy--unless I misinterpret the meaning of this error. As I understand it, this error only comes up when it sees a plain subroutine call--that is, no invocant.

    There is no way line 366 shouldn't be interpreted as a method. There aren't any syntax errors in any related module. Why in the world does Perl think 366 is a plain subroutine call rather than a method call? Even interpreted as a plain sub, it is ignoring the definitions of mediate_document that are in each of Contentment::Mediator and Contentment::Mediator::XSLT. What can I do? I'm about to comment the code in huge sections to see if I can narrow the issue, rewrite the modules entirely, or work out a different solution.

Re: Psycho AUTOLOAD question
by hanenkamp (Pilgrim) on Aug 09, 2003 at 03:58 UTC

    Nevermind. Stupid fraggin' Perl syntax error in an unrelated module was causing compilation to fail on the wrapper module. Therefore, as near as I can tell, it was trying to treat the call to mediate_document like a subroutine by moving the invocant in as the first argument in a package that wasn't defined. This should have prevented the test from opening up but I must have an eval wrapped around something in the wrong place.

    Therefore, note to anyone who gets the AUTOLOAD deprecated warning on code that looks okay: look out for modules that have syntax errors but didn't kill the program because of a caught and ignored exception. Doh!