overloadとClass::C3のreinitializeでバグる件

詰まったのでメモ。

overloadを使用しているモジュールを継承してClass::C3のreinitialize(initialize)を使うとバグる。Class::C3のバージョンは最新の0.14

 package Atest;
 use overload (
     q{fallback} => 1,
     q{""}       => 'str',
 );
 sub str {
     return 'Atest str';
 }
 
 package Btest;
 use Class::C3;
 use base qw/Atest/;
 Class::C3::reinitialize();
 
 my $obj = bless {} , 'Btest'; # エラーになる

これを実行するとbless時に、

 Can't resolve method "???" overloading """" in package "overload"

というエラーが発生する。

いわゆるq{""}と紐付けられているメソッドが未定義だというエラー内容ですね。
Atestでは確かにstrというメソッドの文字列を指定しているはずなのになぜか見つからない。

で、Class::C3覗いてみたんだけどどうやらreinitializeで定義されるパッケージはメソッドのみになっているらしく、strは文字列で指定してるんで無視されるようです。

ためしにoverload設定するときにコードリファレンスにしたら問題なく動きました。

 package Atest;
 use overload (
     q{fallback} => 1,
     q{""}       => \&str, # コードリファレンスにしてみる
 );
 sub str {
     return 'Atest str';
 }
 
 package Btest;
 use Class::C3;
 use base qw/Atest/;
 Class::C3::reinitialize();
 
 my $obj = bless {} , 'Btest'; # 問題なく動く
 
 print "$obj"; # Atest str

ただし、コードリファレンスにしちゃうと継承できなくなるのでそれはそれで問題ですねぇ、

 package Atest;
 use overload (
     q{fallback} => 1,
     q{""}       => \&str,
 );
 sub str {
     return 'Atest str';
 }
 
 package Btest;
 use Class::C3;
 use base qw/Atest/;
 Class::C3::reinitialize();
 sub str {
     return 'Btest str';
 }
 
 my $obj = bless {} , 'Btest';
 
 print "$obj"; # Atest str

ってな感じでBtestにstrというメソッドを定義してもAtestのstrメソッドが呼び出されてしまいます。
直接コードリファレンス指定してるんだから当然ちゃ当然の動きですねorz。

Class::C3側で対応してもらうしかないですかね。
overloadで使用しているパッケージ名が来たら文字列でも定義されるようにすればよいってことかな。

とりあえず適当にdiff

 --- oldC3.pm      Wed Sep 20 12:42:04 2006
 +++ newC3.pm      Mon Mar 19 13:52:46 2007
 @@ -120,6 +120,10 @@
      ${"${class}::()"} = $MRO{$class}->{has_overload_fallback}
          if $MRO{$class}->{has_overload_fallback};
      foreach my $method (keys %{$MRO{$class}->{methods}}) {
 +        if ( $method =~ /^\(/ ) {
 +            my $orig = $MRO{$class}->{methods}->{$method}->{orig};
 +            ${"${class}::$method"} = $$orig if defined $$orig;
 +        }
          *{"${class}::$method"} = $MRO{$class}->{methods}->{$method}->{code};
      }
  }

メソッドの先頭が「(」で始まっていたらoverloadで定義されたやつだと見なして処理してみたんだけど問題ないかなぁ・・・。

ま、コレで最初のサンプルが問題なく動くようになりました。万歳。

ところでClass::C3のreinitializeなんか使うことあるのかってことなんですが、DBIx::Classで使用されているのでこういった問題がでないこともないかなぁと。

実際、最近ちょっと考えてるあるモジュールでこの問題にブチ当たってどうしようかと悩んだりしてたりするので^^;

ってか最近Perl触ってなかったので色々忘れてるorz。

リハビリしなければ。

追記:2007/05/04

この問題はClass-C3-0.16でFIXされました。