diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb index 3411c3d701..2d7bdb47fc 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -2890,6 +2890,61 @@ class TestModule < Test::Unit::TestCase } end + def test_prepend_constant_lookup + m = Module.new do + const_set(:C, :m) + end + c = Class.new do + const_set(:C, :c) + prepend m + end + sc = Class.new(c) + # Situation from [Bug #17887] + assert_equal(sc.ancestors.take(3), [sc, m, c]) + assert_equal(:m, sc.const_get(:C)) + assert_equal(:m, sc::C) + + assert_equal(:c, c::C) + + m.send(:remove_const, :C) + assert_equal(:c, sc.const_get(:C)) + assert_equal(:c, sc::C) + + # Same ancestors, built with include instead of prepend + m = Module.new do + const_set(:C, :m) + end + c = Class.new do + const_set(:C, :c) + end + sc = Class.new(c) do + include m + end + + assert_equal(sc.ancestors.take(3), [sc, m, c]) + assert_equal(:m, sc.const_get(:C)) + assert_equal(:m, sc::C) + + m.send(:remove_const, :C) + assert_equal(:c, sc.const_get(:C)) + assert_equal(:c, sc::C) + + # Situation from [Bug #17887], but with modules + m = Module.new do + const_set(:C, :m) + end + m2 = Module.new do + const_set(:C, :m2) + prepend m + end + c = Class.new do + include m2 + end + assert_equal(c.ancestors.take(3), [c, m, m2]) + assert_equal(:m, c.const_get(:C)) + assert_equal(:m, c::C) + end + def test_inspect_segfault bug_10282 = '[ruby-core:65214] [Bug #10282]' assert_separately [], "#{<<~"begin;"}\n#{<<~'end;'}" diff --git a/variable.c b/variable.c index eef19ec237..0e442241ea 100644 --- a/variable.c +++ b/variable.c @@ -2569,16 +2569,31 @@ rb_const_get_0(VALUE klass, ID id, int exclude, int recurse, int visibility) static VALUE rb_const_search_from(VALUE klass, ID id, int exclude, int recurse, int visibility) { - VALUE value, tmp; + VALUE value, current; + bool first_iteration = true; - tmp = klass; - while (RTEST(tmp)) { + for (current = klass; + RTEST(current); + current = RCLASS_SUPER(current), first_iteration = false) { + VALUE tmp; VALUE am = 0; rb_const_entry_t *ce; + if (!first_iteration && RCLASS_ORIGIN(current) != current) { + // This item in the super chain has an origin iclass + // that comes later in the chain. Skip this item so + // prepended modules take precedence. + continue; + } + + // Do lookup in original class or module in case we are at an origin + // iclass in the chain. + tmp = current; + if (BUILTIN_TYPE(tmp) == T_ICLASS) tmp = RBASIC(tmp)->klass; + + // Do the lookup. Loop in case of autoload. while ((ce = rb_const_lookup(tmp, id))) { if (visibility && RB_CONST_PRIVATE_P(ce)) { - if (BUILTIN_TYPE(tmp) == T_ICLASS) tmp = RBASIC(tmp)->klass; GET_EC()->private_const_reference = tmp; return Qundef; } @@ -2599,7 +2614,6 @@ rb_const_search_from(VALUE klass, ID id, int exclude, int recurse, int visibilit return value; } if (!recurse) break; - tmp = RCLASS_SUPER(tmp); } not_found: