Class::MOP::Class->get_method_map()の挙動が不安定な件

get_method_map()は基本的にサブルーチンスタブ(宣言のみで実体なし)を拾わないようになっているんだけど,実際にはプログラムの状態によってスタブを拾ったり拾わなかったりと安定しない*1。特に,Devel::Coverの元で動かすとこの「拾ったり拾わなかったり」という問題が顕在化して,cover -testだとテストが通らない。

#!perl -w
use strict;
{
  package Foo;
  use metaclass; # install "->meta" property

  sub m1;           # 拾われない
  sub m2 :method;   # 拾われる
  sub m3; our $m3;  # 拾われる
  sub m4; m4() if 0;# 拾われる
}
use Data::Dumper;
print Dumper [Foo->meta->get_method_list];
__END__
# 結果
$VAR1 = [
          'm3',
          'm4',
          'm2',
          'meta'
        ];

これは,get_method_map()がシンボルテーブル(スタッシュ stash)にグロブが存在するかどうかで判断しているため。ところが,単なるスタブやプロトタイプつきスタブはスタッシュにグロブを作らない。ところが,たとえば同名のour変数を作るなどして何らかの理由でそのグロブが作られる状況を作ると,そのグロブのCODEスロットに空のコードリファレンスが入るわけだ。今のget_method_map()はグロブなしの純粋なスタブは無視して,空のコードリファレンスによるスタブは拾ってしまう*2
じゃあどういう仕様にするべきか。このケースは幸いperlが答えてくれる。

#!perl -w
use strict;
{
  package Base;
  sub foo{}
}
{
  package Derived;
  use parent -norequire => qw(Base);
  sub foo;
}
Derived->foo();
__END__
# 結果
Undefined subroutine &Derived::foo called.

Perlのメソッドディスパッチメカニズムは,スタブであってもそこにメソッドがあると見做すみたいだ。

*1:先日書いたXSによる実装でも一応この挙動は再現してある。

*2:なお,5.10.0で初めて導入されて,後に5.8.9にも導入された特殊な定数メカニズムがあって,これもグロブを作らなかったりするけど,それは例外として拾うようになっている。