in reply to Re: Re3: Calling SUPER in MethodMaker-generated methods
in thread Calling SUPER in MethodMaker-generated methods

Ok, I think I found the cause of the problem, and a way to work around it, though it's a little ugly.

If you look in perlboot under SUPER, you find the following:

So, "SUPER::speak" means look in the current package's @ISA for "speak", invoking the first one found.

In other words, SUPER ignores the class of the object it's called upon, and always looks in the current package's @ISA to find the super class. So, when you create the anonymous sub to be the method, it's actually created in your CustomMethodMaker class, and so that's whose @ISA it uses, even though it's later installed into the FixedBug class. (Technically, it's not actually put into the FixedBug class. Rather, a reference to it is put into FixedBug's symbol table. The sub still resides in CustomMethodMaker.) Personally, I don't think is the correct thing to do (I think SUPER should look in the @ISA of the package that the object is blessed into) but it's documented, so it can't be a bug. ;)

So, to do what you want to do, you need to force the call to SUPER to be in the correct package. The way to do this is to note what package your installer function is called from, and manually set yourself back to that package just prior to calling the super-method. Of course, since you only know what package your installer function is called from at run-time, you have to set the package dynamically at run-time, via (string) eval.

I created a simple set of test classes. You should just need to use the eval and caller() lines in MyTest::MethodInstaller::install(). With your original example code, you'll have to get the caller two levels higher (ie, caller(2)). Once for the import, and once for something else internal to Class::MethodMaker. Or, you could use Class::MethodMaker's internal (undocumented) find_target_class() method, which searches up the call stack for the first namespace which is not a descendant of Class::MethodMaker. That would probably be better (more robust, somewhat less likely to change).

The code:

#!/usr/bin/perl use warnings; use strict; #-------------------------------------------------- # MyTest::MethodInstaller # # This is a simple "module" to install a generated # method into the caller's package. # # This is analogous to your CustomMethodMaker class, # though you'll call $class->install_methods() # instead of installing the method directly. #-------------------------------------------------- package MyTest::MethodInstaller; sub install { my ($caller) = caller(0); my $method = shift; my $method_ref = sub { my $self = shift; print "$caller\::$method called.\n"; # And here's the magic trick eval "package $caller; \$self->SUPER::$method(\@_); 1" or die $@; }; # Install the method into the caller's namespace no strict 'refs'; *{"$caller\::foo"} = $method_ref; } #-------------------------------------------------- # MyTest::Super # # This is a simple test super-class. #-------------------------------------------------- package MyTest::Super; sub new { my $class = shift; return bless {}, $class; } # This is the super-class' foo() method that # we're interested in calling. This will have # been installed by Class::MethodMaker. sub foo { my $self = shift; print "MyTest::Super::foo called with (",join(',',@_),").\n"; } #-------------------------------------------------- # MyTest # # This is a simple test base-class that uses our # MethodInstaller class. #-------------------------------------------------- package MyTest; use base 'MyTest::Super'; # Request that the generated method # be installed into our namespace. MyTest::MethodInstaller::install('foo'); #-------------------------------------------------- # main # # This is the simple test script. #-------------------------------------------------- package main; my $t = new MyTest; $t->foo('bar', 'quux');

Well, that should get you working, though I think the eval trick is ugly and shouldn't be necessary in the first place. Oh well. C'est la vie.

bbfu
Black flowers blossom
Fearless on my breath

Replies are listed 'Best First'.
Re: Re5: Calling SUPER in MethodMaker-generated methods
by danb (Friar) on Jul 09, 2003 at 07:48 UTC

    Done! For the record, I attached the final MethodMaker-derived code. Would you accept a small tip via PayPal (if so, what e-mail should I use)?

    I'm going to see about submitting this for the MethodMaker maintainer.

    #################################################################### ## CustomMethodMaker #################################################################### package CustomMethodMaker; use base ( 'Class::MethodMaker' ); use Carp; =head2 grouped_fields_inherit Works like grouped_fields, except that it also calls the parent class. =cut sub grouped_fields_inherit { my ($class, %args) = @_; my %methods; foreach (keys %args) { my @slots = @{$args{$_}}; $class->get_set(@slots); my $method_name = $_; my $caller = $class->find_target_class(); $methods{$_} = sub { my ( $self ) = @_; my @parent_slots = (); my $to_execute = "SUPER::$method_name"; # # Without $caller and eval, the following line causes this err +or: # # Can't locate auto/CustomMethodMaker/SUPER/[METHOD].al in @ +INC # @parent_slots = eval "package $caller; return \$self->SUPER::$ +method_name(\@_); 1" or die $@; return ( @parent_slots, @slots ); }; } $class->install_methods(%methods); } 1;
    -Dan

      Would you accept a small tip via PayPal

      Nah, don't sweat it. I enjoyed helping. :) I appreciate the offer, tho.

      Update: Re: your code; I just wanted to point out that you should probably either put the assignment to @parent_slots inside the eval, since it's conceivable that the super-method could correctly return an empty list which would cause your code to die, or make the test for dying an explicit check on $@ (in which case the 1; at the end of the eval is superfluous). IOW, it's best to do this:

      @parent_slots = eval "package $class; \$self->SUPER::$method_name()"; die $@ if $@;

      Also, if you're going to pass @_ in to the super-method, you need to shift $self off first, instead of setting it via list-context assignment. (Otherwise, the super-method gets two copies of $self.)

      bbfu
      Black flowers blossom
      Fearless on my breath

        Thanks. I rewrote it slightly:
        ##################################################################### ## CustomMethodMaker ## (must be in it's own file, due to the fact that it has to be 'used +'. ##################################################################### package Business::Shipping::CustomMethodMaker; use base ( 'Class::MethodMaker' ); =head2 grouped_fields_inherit Works like grouped_fields, except that it also calls the parent class. =cut sub grouped_fields_inherit { my ($class, %args) = @_; my %methods; foreach (keys %args) { my @slots = @{$args{$_}}; $class->get_set(@slots); my $method_name = $_; my $caller = $class->find_target_class(); $methods{$_} = sub { my $self = shift; # #print "installing $method_name() in $caller...\n"; # # Without $caller and eval, the following line causes this err +or: # # Can't locate auto/CustomMethodMaker/SUPER/[METHOD].al in @IN +C # my @parent_slots = eval " package $caller; if ( \$self->can( SUPER::$method_name ) ) { return \$self->SUPER::$method_name( \@_ ); } else { return ( ); } 1; "; die $@ if $@; return ( @parent_slots, @slots ); }; } $class->install_methods(%methods); } 1;
        -Dan
Re: Re5: Calling SUPER in MethodMaker-generated methods
by danb (Friar) on Jul 09, 2003 at 06:35 UTC

    Woo hoo! Thanks, bbfu. I'll have to try this out in the morning. (FYI, don't do eval "package $self";, it causes a Segmentation Fault.)

    -Dan