😽

[Bug #21026] __FILE__ に対して特異メソッドを定義したときの話

2025/01/24に公開

[Bug #21026] def __FILE__.a; end should be a syntax error

  • def __FILE__.a; end はシンタックスエラーにする必要があるというバグ報告
  • どういうこと?という話なんですが、まず __FILE__ は特別な疑似変数で現在実行しているスクリプトのファイル名を返します
# test.rb

# 実行中のスクリプトのファイル名を返す
pp __FILE__
# => "test.rb"

この __FILE__ に対して特異メソッドを定義するとメソッドは定義できるんですがメソッド呼び出すとエラーになります

# メソッド定義自体はできる
def __FILE__.a
end

# ただし、そのメソッドを呼び出すとエラーになる
__FILE__.a
# => error: undefined method 'a' for an instance of String (NoMethodError)
  • …つまりどういうこと?っていう話ではあるんですがそもそも __FILE__ は『コンパイル時に現在実行しているスクリプトのファイル名に置き換わる』という挙動になっている?みたいです
  • 実際に RubyVM::InstructionSequence でのコンパイル結果をみてみるとファイル名が直接展開されていることがわかると思います
iseq = RubyVM::InstructionSequence.compile(<<~'RUBY', "test.rb")
  puts __FILE__
RUBY

puts iseq.disasm
__END__
output:
== disasm: #<ISeq:<compiled>@test.rb:1 (1,0)-(1,13)>
0000 putself                                                          (   1)[Li]
0001 putchilledstring                       "test.rb"                               <= ここで直接ファイル名が展開されている
0003 opt_send_without_block                 <calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE>
0005 leave
  • なので __FILE__ は必ずしも同じオブジェクトになるとは限りません
pp __FILE__.object_id # => 16
pp __FILE__.object_id # => 24
  • これを踏まえると元のコードはこういう解釈になるんですかね…?
# test.rb
def "test.rb".a
end

"test.rb".a
  • この時にメソッドを定義したオブジェクトとメソッドを呼び出すオブジェクトは異なるのでメソッドが呼び出せずにエラーになります
  • 逆に次のように __FILE__ の結果を代入した値に対してメソッド定義 + 呼び出しを行うと動作します
# test.rb
file = __FILE__

def file.a
  "hello"
end

pp file.a
# => "hello"
  • またコンパイル時に文字列になるので frozen_string_literal: true の場合はメソッド定義の時点でエラーになります
# frozen_string_literal: true

# 文字列リテラルとして定義されるので frozen_string_literal の設定に依存する
pp __FILE__.frozen? # => true

# frozen されているオブジェクトに対してメソッドは定義できないのでエラーになる
# error: can't define singleton (TypeError)
def __FILE__.a
end
  • ややこしい…
GitHubで編集を提案

Discussion