From: "naopontan (naoki takaesu) via ruby-core" Date: 2026-10-27T09:45:11+00:00 Subject: [ruby-core:127246] [Ruby Bug#22475] Prepending a second module to an already-included module duplicates it in the includer's ancestors (regression from the Bug #22257 fix) Issue #22475 has been reported by naopontan (naoki takaesu). ---------------------------------------- Bug #22475: Prepending a second module to an already-included module duplicates it in the includer's ancestors (regression from the Bug #22257 fix) https://bugs.ruby-lang.org/issues/22475 * Author: naopontan (naoki takaesu) * Status: Open * ruby -v: ruby 3.4.12 (2026-10-23 revision 366f5fdc00) +PRISM [arm64-darwin25] * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- ## Summary Since the fix for #22257 (master c59c4d717a, backported to ruby_3_4 as c8e05b5885 and to ruby_4_0 as 06611602a9), prepending a module to a module `M` that already has includers works correctly the first time, but every *subsequent* `M.prepend` inserts the new module twice into each includer's ancestor chain. `super` therefore passes through the second and later prepended modules twice. ## Reproduction ```ruby m = Module.new c = Class.new { include m } # include first a = Module.new; b = Module.new; d = Module.new m.prepend a m.prepend b m.prepend d anc = c.ancestors puts "A x#{anc.count(a)} B x#{anc.count(b)} D x#{anc.count(d)}" ``` Expected (Ruby 3.3.10, 3.4.10): ``` A x1 B x1 D x1 # ancestors: [C, D, B, A, M, Object, Kernel, BasicObject] ``` Actual (Ruby 3.4.11, 3.4.12, 4.0.9, 4.1.0-preview1, master cda3527671): ``` A x1 B x2 D x2 # ancestors: [C, D, B, A, M, D, B, Object, Kernel, BasicObject] ``` `super` really goes through the duplicated modules: ```ruby module Base; def tabs = [:info]; end h = Module.new; h.include Base view = Class.new { include h } module P1; def tabs = super << :p1; end module P2; def tabs = super << :p2; end h.prepend P1 h.prepend P2 p view.new.tabs # 3.4.10: [:info, :p1, :p2] # 3.4.12: [:info, :p2, :p1, :p2] (P2#tabs is called twice) ``` Not affected: prepending directly to a class, `M.include` after `M.prepend`, and prepending before the module is included. ## Cause The fix for #22257 registers the origin iclass backfilled into each includer in the module's subclasses list (`rb_module_add_to_subclasses_list(klass, origin)`) so that `rb_clear_method_cache` can reach it. On the *next* `rb_prepend_module`, the loop over `RCLASS_SUBCLASSES(klass)` now also visits that origin iclass and calls `include_modules_at(origin, origin, module, FALSE)` on it, which inserts a second iclass of the prepended module right after the origin. The comment in the fix ("Registering during it would visit the new iclass and prepend module into it a second time") describes this for the first call; the same thing happens on every later call. ## Proposed fix Skip origin iclasses in that loop: ```c if (RICLASS_IS_ORIGIN_P(subclass)) continue; ``` The module is already inserted between the includer's iclass and its origin when the includer's iclass is visited. With this change (patch with a regression test attached; I will also open a pull request on GitHub and link it here) the reproduction above prints `A x1 B x1 D x1` on master, the #22257 reproduction still prints `:hooked`, and `make btest` / `make test-all` pass (38553 tests, 0 failures). ## Environment - ruby 3.4.12 (2026-10-23 revision 366f5fdc00) +PRISM [arm64-darwin25] - ruby 4.1.0dev (2026-10-27T16:37:34Z master cda3527671) +PRISM [arm64-darwin25] - macOS 26.6.2 (arm64) ---Files-------------------------------- fix_prepend_duplicate.patch (2.18 KB) -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/