問題
crystal のコードを試しに書いていて、早速以下のような問題にぶつかりました。
class Test
def a
b.new
end
end
この a というメソッドの中では定義されていない b という変数を参照しているので java や c の感覚では当然コンパイルエラーになるはずのものです。ですが、驚いたことに実はこれはコンパイルに通ってしまいます。crystal は型チェックをしてくれるのではなかったのでしょうか?これが通ってしまっては行けないのでは?
では、実際にインスタンスを作成してみたら実行時エラーが出てしまうのでしょうか?
class Test
def a
b.new
end
end
Test.new.a #=> undefined local variable or method 'b'
実はこうするとコンパイルエラーがちゃんと出ます。これは一体どういうことでしょうか?
今度はためしに Test.new.a
をメソッドの中に定義して実行されないようにしてみます。
class Test
def a
b.new
end
end
def c
Test.new.a #=> OK
end
今度はまたコンパイルに通るようになりました。c をグローバルコンテキストから呼ぶとまたエラーになります。
では if false
だとどうでしょうか。
class Test
def a
b.new
end
end
if false
Test.new.a #=> NG
end
これはコンパイルエラー。
では a
を呼ばなかったらどうなるでしょうか?
class Test
def a
b.new
end
end
Test.new #=> OK
コンパイル、通ります!
じゃあ、今度はクラスコンテキストからだと?
class Test
def a
b.new
end
Test.new.a #=> NG
end
エラー!
つまりどういうこと?
どうやら、どこからも使われていないメソッドはコンパイルの対象に含まれないようです。
グローバルコンテキストないしはクラスコンテキストからコードを再帰的に辿って行って、実際に使われているコードを洗い出してコンパイルしているのではないでしょうか。上記に書いた以外にもクラス継承を挟んでみたり、super で呼び出してみたりしましたが、実行時にメソッドが使われない場合は一貫してコンパイルスルーされました。バイナリのサイズを小さくするための工夫でしょうが、びっくりしますね。
驚いたので勢いで記事をかきました。