indirect object notationでハマらないためのno indirectプラグマ

普段、Perlを書くときは require_ok などでモジュールがコンパイル可能か・構文エラーがないかをテストするようにしているけれど、 require_ok では発見できない構文エラーを発見したのでそれの詳細と対策について。

実行時エラーになる構文エラー (?) とindirect object notation

実行時エラーに構文エラーになることなんてあるのかよと思うけど、厳密には構文エラーではなく、意図しない構文解析の結果、コンパイルは成功するが実行するとエラーになるというコードができあがってしまった。

具体的には以下のようなコード:

package App::Logger;

use Exporter 'import';

our @EXPORT = qw(INFO);

sub INFO ($;@) { ... }

1;
package App::Loader::User;

sub find {
  local $@;
  eval { ... };
  if ($@) {
    INFO $@;
  }
}

1;

App::Logger はいろいろ便利なことをしてくれるアプリケーションロガーで定義されたサブルーチン INFO はprototypeが宣言されており INFO 'hi'; INFO 'user: %s', $user->name; のように呼び出せる。

App::Loader::UserApp::Loader::INFO を呼び出しているパッケージだけど、 use App::Logger するのを忘れていた。しかしコンパイルは通る。つまり構文解析には成功しているということである。なぜだろうか?

具体的には INFO $@; がなぜ構文解析に成功するのか。答えはindirect object notationとして妥当な構文であるからだった。

indirect object notationはperldoc perlmodに詳しいが、 MOD->METHOD という呼び出しは METHOD MOD と等しく、後者をindirect object notationという。

つまり INFO $@$@->INFO構文解析されて妥当というわけである。

no indirect プラグマ

謎は解けたが、ひとつのことを成し遂げるために複数のやりかたがあるという状態は、特にチーム開発においてはうれしいことばかりではない。堅牢にソフトウェアを作るためには、曖昧さが少なくエンバグのおそれが少ない書き方ができるほうが望ましい。

indirect object notationを意図せず使っていたら警告なりしてほしいところだけど、そんなときはindirectプラグマが使える。

no indirect; を書いたスコープでindirect object notationが使われていると警告される。 no indirect 'fatal'; で警告する代わりに例外を投げる。

プラグマの影響範囲はデフォルトでレキシカルだけど no indirect 'global'; でグローバルに有効にできる。

これでindirect object notationでハマることを減らせそうで助かってめでたいですね。