Project

General

Profile

Bug #11189

alias prepended module

Added by ko1 (Koichi Sasada) over 5 years ago. Updated about 1 month ago.

Status:
Closed
Priority:
Normal
Target version:
-
[ruby-dev:49007]
Tags:

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

かなぁ。


Related issues

Related to Ruby master - Bug #7842: An alias of a "prepend"ed method skips the original method when calling superClosednobu (Nobuyoshi Nakada)02/13/2013Actions

Updated by ko1 (Koichi Sasada) over 5 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)へ

と分けなきゃいけないのも、なんかかっこ悪いですね。

#2

Updated by usa (Usaku NAKAMURA) over 5 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) about 1 year 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
#4

Updated by jeremyevans (Jeremy Evans) about 1 month 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]

Also available in: Atom PDF