Bug #11189
closedalias prepended module
Description
module P
def m1
p :P_m1
super
end
def m2
p :P_m2
super
end
end
class C0
def m1
p :C0_m1
end
end
class C1 < C0
def m1
p :C1_m1
super
end
prepend P
alias m2 m1
end
C1.new.m2
このプログラムは、
:P_m2
:P_m1
:C0_m1
という結果になります。super で辿っているはずなのに、同じモジュールのメソッドが 2 回呼ばれます。super で辿っていったら、必ず継承元へ行く、2度同じクラスは来ない、という常識があると思っていたので、Ruby 的にはかなり驚きました。
この挙動は、設定時に:
- alias で C1 のメソッドテーブルに、C1#m2 を P#m1 へ飛ばすように設定する
次に、呼び出された時に:
- m2 が呼ばれる
- P#m2 を実行する
- P#m2 の super を呼ぶ
- C1#m2 を見ると、P#m1 へ飛ぶように alias が設定されている
- P#m1 を実行する、super する
- (なんでか C1#m1 をスキップする なんで?)
- C0#m1 が呼ばれる
となっておりました。P#m2 -> P#m1 という流れがとても気持ち悪いですが、これは意図された挙動でしょうか。
C1#foo が呼ばれないのも気持ち悪いですが。
ちなみに、2.1 では C1#m1 が呼ばれていました。
* ruby 2.1.5p312 (2015-03-10 revision 49912) [i386-mswin32_110]
:P_m2
:P_m1
:C1_m1
:C0_m1
たしか、alias + prepend の議論が以前あったと思うのですが。
あ、[Bug #7842] だ。
ちなみに、私の予想は
:P_m1
:C1_m1
:C0_m1
かなぁ。
Updated by ko1 (Koichi Sasada) over 9 years ago
今、こんなコードだと、
- C1 (m_tbl 空)
- T_ICLASS(m_tbl 空) -> P
- T_ICLASS(C1 の m_tbl) -> C1
- C0
- Object...
という継承関係(下が親)が作られるけど、alias のために C1 に m_tbl 作らないといかんね、って話な気がします。が、なんか以前この話をしていたことがあったような。結局 C1 にはテーブル作れないんでしたっけ。
メソッド定義については、
- alias の時は、C1::m_tbl へ
- メソッド定義の時は T_ICLASS(C1 の m_tbl)へ
と分けなきゃいけないのも、なんかかっこ悪いですね。
Updated by usa (Usaku NAKAMURA) over 9 years ago
- Related to Bug #7842: An alias of a "prepend"ed method skips the original method when calling super added
Updated by jeremyevans0 (Jeremy Evans) over 5 years ago
ko1 (Koichi Sasada) wrote:
ちなみに、私の予想は
:P_m1 :C1_m1 :C0_m1
かなぁ。
This output would be against my expection. If C1
prepends P
, then methods in P
must be considered before methods in C1
. Consider C1.ancestors
:
[P, C1, C0, Object, Kernel, BasicObject]
P#m2
must be called before C1#m2
, and alias m2 m1
in C1
only modifies the method table in C1
, not in P
.
All Ruby versions I tested have the same behavior as Ruby 2.1:
:P_m2
:P_m1
:C1_m1
:C0_m1
There does appear to be a related bug in super_method
, though:
C1.new.method(:m2)
# => #<Method: C1(P)#m2>
C1.new.method(:m2).super_method
# => #<Method: C1(P)#m2(m1)>
C1.new.method(:m2).super_method.super_method
# => nil
# Should be #<Method: C1#m1>
Note that because of the way alias
works with prepend
, you can get an infinite loop in method lookup with aliases and only calling super
:
module P
def m1
super
end
def m2
super
end
end
class C
prepend P
alias m2 m1
alias m1 m2
end
C.new.m2
# SystemStackError
Updated by jeremyevans (Jeremy Evans) over 4 years ago
- Status changed from Open to Closed
Applied in changeset git|c60aaed1856b2b6f90de0992c34771830019e021.
Fix Method#super_method for aliased methods
Previously, Method#super_method looked at the called_id to
determine the method id to use, but that isn't correct for
aliased methods, because the super target depends on the
original method id, not the called_id.
Additionally, aliases can reference methods defined in other
classes and modules, and super lookup needs to start in the
super of the defined class in such cases.
This adds tests for Method#super_method for both types of
aliases, one that uses VM_METHOD_TYPE_ALIAS and another that
does not. Both check that the results for calling super
methods return the expected values.
To find the defined class for alias methods, add an rb_ prefix
to find_defined_class_by_owner in vm_insnhelper.c and make it
non-static, so that it can be called from method_super_method
in proc.c.
This bug was original discovered while researching [Bug #11189].
Fixes [Bug #17130]