Ruby の ! について

はいどうも

! メソッド

Ruby における ! ってご存知ですか?
Ruby演算子っぽいようなものもメソッドとして実装されているという話があったりします。

1 + 1 # Integer の + メソッドが呼ばれている

class Integer
  # 上書き
  def +(other)
    42
  end
end

1 + 1 # => 42

! も BasicObject のメソッドとして定義されています。

/**
 *  call-seq:
 *     !obj    -> true or false
 *
 *  Boolean negate.
 *--
 * \private
 *++
 */

MJIT_FUNC_EXPORTED VALUE
rb_obj_not(VALUE obj)
{
    return RTEST(obj) ? Qfalse : Qtrue;
}
// ...略
    rb_define_method(rb_cBasicObject, "!", rb_obj_not, 0);
class BasicObject
  def !(*)
    raise 'bang dayo-nn'
  end
end

!1
# Traceback (most recent call last):
#         1: from basicobject.rb:7:in `<main>'
# basicobject.rb:3:in `!': bang dayo-nn (RuntimeError)

ところで

クラス内でのメソッド呼び出しについては以下のようになりますよね。

class Foo
  def foo
    bar(1) # Foo#bar への呼び出しになる
  end

  def bar(arg)
    puts arg
  end
end

Foo.new.foo # => 1 が出力される

ということはこれで Foo#! が呼ばれる気がするじゃないですか

class Foo
  def foo
    !(true)
  end

  def !(arg)
    puts '! called'
    false
  end
end

Foo.new.foo # なんもでない
puts 'done'

でもそうはならないんですよね。

なんで?

parseされたときの結果を見ればわかりますが、 ! メソッドのレシーバーは nd_lit: 1 であることがわかります。

$ ruby --dump=parse -e '!(1)'
###########################################################
## Do NOT use this node dump for any purpose other than  ##
## debug and research.  Compatibility is not guaranteed. ##
###########################################################

# @ NODE_SCOPE (line: 1, location: (1,0)-(1,4))
# +- nd_tbl: (empty)
# +- nd_args:
# |   (null node)
# +- nd_body:
#     @ NODE_OPCALL (line: 1, location: (1,0)-(1,4))*
#     +- nd_mid: :!
#     +- nd_recv:
#     |   @ NODE_LIT (line: 1, location: (1,2)-(1,3))*
#     |   +- nd_lit: 1
#     +- nd_args:
#         (null node)

!(1) は メソッド ! 呼び出しの引数が 1 ではなく、(1) (= 1)への ! メソッド呼び出し扱いになるんですね。

parserの気持ちがわからない。
以上、昔この issue を見てよくわかんないなーと思って調べたことの dump です。

github.com

なおこの issue を理解するには更に if + regex literal のときの挙動を理解しておく必要があります。

また if の条件式が正規表現リテラルである時には特別に $_ =~ リテラル であるかのように評価されます。

https://docs.ruby-lang.org/ja/2.5.0/doc/spec=2fcontrol.html#if

補足

Ruby の動作は ruby 2.6.0preview1 (2018-02-24 trunk 62554) [x86_64-darwin17]
CRubyのソースコードは ac44ae58c697971e14e72493da2cfdd9a4e3b458 時点のものを基準としています。