Moose metaclass compatibility is too complex!
Mooseのmetaclass compatibilityのルールがとんでもなく複雑だということがわかったのでメモしておく。
コード例:
#!perl use strict; use warnings; use Test::More tests => 4; use Moose::Util qw(does_role); { package FooTrait; use Moose::Role; package BarTrait; use Moose::Role; package BaseClass; use Moose -traits => qw(FooTrait); package SubClass; use Moose -traits => qw(BarTrait); extends qw(BaseClass); } ok does_role(BaseClass->meta, 'FooTrait'), ' BaseClass->meta->does("FooTrait")'; ok!does_role(BaseClass->meta, 'BarTrait'), '!BaseClass->meta->does("BarTrait")'; ok does_role(SubClass->meta, 'FooTrait'), 'SubClass->meta->does("FooTrait")'; ok does_role(SubClass->meta, 'BarTrait'), 'SubClass->meta->does("BarTrait")'; __END__
まず基本ルールとして,クラスに階層関係があるBaseClass,SubClassがあるとき,SubClass->meta is-a BaseClass->metaでなければならない。これがmetaclass compatibilityというルールで,Class::MOPで説明されている。
さて,上記のコードではBaseClassに-traitsを指定しているが,これはBaseClass->metaにroleを当てる*1ことになるので,BaseClass->meta->does('FooTrait')となる。基本ルールを適用すると,その結果当然SubClass->meta->does('FooTrait')も期待できる。
ここで,SubClassに-traitsを指定する,つまりSubClass->metaにroleを当てるとどうなるか。SubClass->metaはBaseClass->metaを継承しつつdoes('BarTraits')というような代物になるはずである。実際,Mooseでは上記のテストコードは問題なく通る。すばらしい。
問題はその実装だ。useディレクティブがコンパイル時に行われ,extends()関数が実行時に呼ばれる以上,SubClass->metaにroleを当てた後にBaseClass=>SubClass間のクラス階層が構築されるのだ。このときextends()は基本ルールが成立しているかどうかをチェックするのだが,SubClassにはroleが当てられているのでmetaclass compatibilityに適合しない!そこでそれをfixするためにBaseClass->metaに当てられているroleを解析して…というところまで理解したが,そこで力尽きた。
だが,このmetaclass compatibility resolutionをMouseに移植しないとuse Mouse -traits => ...
を実装できないのだ。これができたら0.41のリリースということになるだろう。
(追記)
実装できた。これで0.41と行きたいところだが,-traitsを使うと5.6.2でSEGVが起きる。とりあえず5.6.2でのテストをスキップして0.40_08にした。
*1:この「当てる」は「パッチを当てる」の「当てる」と同じ意味である