summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Evans <code@jeremyevans.net>2020-01-06 16:41:03 -0800
committerJeremy Evans <code@jeremyevans.net>2020-02-27 11:03:13 -0800
commit3556a834a2847e52162d1d3302d4c64390df1694 (patch)
tree400bb182c86b19d14a777828fb07622c831d6625
parent1ca3a22117fb646579773960247aa46db46cb03c (diff)
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]
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/2936
-rw-r--r--class.c20
-rw-r--r--test/ruby/test_module.rb51
2 files changed, 71 insertions, 0 deletions
diff --git a/class.c b/class.c
index 98b2a1d92e..ae1a9c072f 100644
--- a/class.c
+++ b/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
diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb
index 5194a56a8c..cafb0cd753 100644
--- a/test/ruby/test_module.rb
+++ b/test/ruby/test_module.rb
@@ -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)