Make Module#include affect the iclasses of the module
When calling Module#include, if the receiver is a module, walk the subclasses list and include the argument module in each iclass. This does not affect Module#prepend, as fixing that is significantly more involved. Fixes [Bug #9573]
This commit is contained in:
parent
1ca3a22117
commit
3556a834a2
Notes:
git
2020-02-28 04:03:36 +09:00
20
class.c
20
class.c
@ -883,6 +883,26 @@ rb_include_module(VALUE klass, VALUE module)
|
||||
changed = include_modules_at(klass, RCLASS_ORIGIN(klass), module, TRUE);
|
||||
if (changed < 0)
|
||||
rb_raise(rb_eArgError, "cyclic include detected");
|
||||
|
||||
if (RB_TYPE_P(klass, T_MODULE)) {
|
||||
rb_subclass_entry_t *iclass = RCLASS_EXT(klass)->subclasses;
|
||||
int do_include = 1;
|
||||
while (iclass) {
|
||||
VALUE check_class = iclass->klass;
|
||||
while (check_class) {
|
||||
if (RB_TYPE_P(check_class, T_ICLASS) &&
|
||||
(RBASIC(check_class)->klass == module)) {
|
||||
do_include = 0;
|
||||
}
|
||||
check_class = RCLASS_SUPER(check_class);
|
||||
}
|
||||
|
||||
if (do_include) {
|
||||
include_modules_at(iclass->klass, RCLASS_ORIGIN(iclass->klass), module, TRUE);
|
||||
}
|
||||
iclass = iclass->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static enum rb_id_table_iterator_result
|
||||
|
@ -473,6 +473,57 @@ class TestModule < Test::Unit::TestCase
|
||||
assert_raise(ArgumentError) { Module.new { include } }
|
||||
end
|
||||
|
||||
def test_include_into_module_already_included
|
||||
c = Class.new{def foo; [:c] end}
|
||||
modules = lambda do ||
|
||||
sub = Class.new(c){def foo; [:sc] + super end}
|
||||
[
|
||||
Module.new{def foo; [:m1] + super end},
|
||||
Module.new{def foo; [:m2] + super end},
|
||||
Module.new{def foo; [:m3] + super end},
|
||||
sub,
|
||||
sub.new
|
||||
]
|
||||
end
|
||||
|
||||
m1, m2, m3, sc, o = modules.call
|
||||
assert_equal([:sc, :c], o.foo)
|
||||
sc.include m1
|
||||
assert_equal([:sc, :m1, :c], o.foo)
|
||||
m1.include m2
|
||||
assert_equal([:sc, :m1, :m2, :c], o.foo)
|
||||
m2.include m3
|
||||
assert_equal([:sc, :m1, :m2, :m3, :c], o.foo)
|
||||
|
||||
m1, m2, m3, sc, o = modules.call
|
||||
sc.prepend m1
|
||||
assert_equal([:m1, :sc, :c], o.foo)
|
||||
m1.include m2
|
||||
assert_equal([:m1, :m2, :sc, :c], o.foo)
|
||||
m2.include m3
|
||||
assert_equal([:m1, :m2, :m3, :sc, :c], o.foo)
|
||||
|
||||
m1, m2, m3, sc, o = modules.call
|
||||
sc.include m2
|
||||
assert_equal([:sc, :m2, :c], o.foo)
|
||||
sc.prepend m1
|
||||
assert_equal([:m1, :sc, :m2, :c], o.foo)
|
||||
m1.include m2
|
||||
assert_equal([:m1, :sc, :m2, :c], o.foo)
|
||||
m1.include m3
|
||||
assert_equal([:m1, :m3, :sc, :m2, :c], o.foo)
|
||||
|
||||
m1, m2, m3, sc, o = modules.call
|
||||
sc.include m3
|
||||
sc.include m2
|
||||
assert_equal([:sc, :m2, :m3, :c], o.foo)
|
||||
sc.prepend m1
|
||||
assert_equal([:m1, :sc, :m2, :m3, :c], o.foo)
|
||||
m1.include m2
|
||||
m1.include m3
|
||||
assert_equal([:m1, :sc, :m2, :m3, :c], o.foo)
|
||||
end
|
||||
|
||||
def test_included_modules
|
||||
assert_equal([], Mixin.included_modules)
|
||||
assert_equal([Mixin], User.included_modules)
|
||||
|
Loading…
x
Reference in New Issue
Block a user