Subject: | subtle bug created by wrapping UNIVERSAL::can |
Because of this module, I keep bumping into corners of Perl I hadn't explored yet :-)
Today's discovery came after trying to figure out why the following failed:
$ perl -wle 'use Config::Std; use Template::Timer;'
Died at /Library/Perl/5.8.6/Template/Timer.pm line 63.
The uses wouldn't fail if performed in the opposite order.
Template::Timer looked too simple to give rise to any real weirdness, so I looked through Class::Std to see if anything unusual was going on, and came across the replacement of UNIVERSAL::can. And sure enough, the offending line in Template::Timer was a call to can() with a form I'd never seen before:
my $super = __PACKAGE__->can("SUPER::$sub") or die;
I didn't even know that was possible! The error introduced by Class::Std is, I think, that SUPER::whatever has a meaning based on the context of the call, not the class of the invocant. Class::Std, in wrapping UNIVERSAL::can, moves the context of the call to itself, which will never completely work, at least without some ugly hacking for this corner case. Since Template::Timer is using simple inheritance it seems it could be easily rewritten to be both less cryptic and compatible with Class::Std. But this feature could be pretty useful with multiple inheritance, and breaking unrelated code is obviously a bad thing...
I have attached a test case that demonstrates the problem succinctly.
use strict;
package A;
sub foo { 1 }
package B;
our @ISA = 'A';
use Test::More tests => 2;
BEGIN {
is( B->can("foo"), B->can("SUPER::foo"), "can(SUPER::method) finds sub");
}
use Class::Std;
is( B->can("foo"), B->can("SUPER::foo"),
"can(SUPER::method) after UNIVERSAL::can replaced" );