From: "shugo (Shugo Maeda) via ruby-core" Date: 2026-10-07T03:52:21+00:00 Subject: [ruby-core:126945] [Ruby Feature#22309] Allow super in a module method to work if method was called by refinement method super Issue #22309 has been updated by shugo (Shugo Maeda). I have approved https://github.com/ruby/ruby/pull/18910, but there is one thing I am concerned about. The cache entries are keyed by the refinement iclass of each cref and never removed, so each `using` scope or `Proc#refined` copy that dispatches a refined module method leaves iclasses behind for as long as the class lives: ```ruby module M; def m = [:M]; end module R1; refine M do def m = [:R1, *super] end end module R2; refine M do def m = [:R2, *super] end end class C; include M; end o = C.new blk = ->(x) { x.m } 500.times { |i| blk.refined(i.even? ? R1 : R2).call(o) } GC.start # T_ICLASS count: about +1500 on the branch, about +0 on master ``` Since `super` from a refinement method now skips the other refinements, the copied chain seems to be needed only to find which refinement defines the method, and that can be done on the cref's own chain. So how about looking up the method first, and then creating a single iclass for the refinement that defines it, with the iclass of the refined module (the `defined_class` of `module_refinement_iclass`) as its super, cached on that iclass by the refinement? The entries would then depend only on the refinements actually called, not on crefs. ---------------------------------------- Feature #22309: Allow super in a module method to work if method was called by refinement method super https://bugs.ruby-lang.org/issues/22309#change-119337 * Author: jeremyevans0 (Jeremy Evans) * Status: Open ---------------------------------------- Ruby started allowing refinements of modules in 2.4. However, there was one limitation, which is that while the refinement method could call `super` to call the module method, the module method could not call `super`, because it did not have enough information to determine the appropriate ancestor. This limitation was known at the time and was accepted when module refinements were accepted in #12534. As discussed in #22071, `super` wasn't actually prohibited in the module method, it just used the incorrect method lookup, looking for a super method in `BasicObject`. After discussion in #22071, I fixed this to use an explicit exception for this case, so an error was was raised for it. While that addressed the incorrect super method lookup issue, it was an unsatisfying conclusion. I kept thinking about ways to actually fix the issue. My initial idea was to use a separate `T_ICLASS` for the module being refined in every class ancestry chain where it could be called via refinement `super`. However, that approach would result in a large amount of complexity, as well as potentially significant additional memory. I am instead proposing an alternative approach which relies on the fact that when we call `super` in the module method, we know that the current method is a module method, and that the previous method in the call stack is a refinement method. We can walk up the call stack to find the refinement method, look at the previous method calling that to determine the class to use for the super lookup. Note that the previous method may use a different receiver/method, in which case we start with the receiver's class. There are cases where we need to do this multiple times, if there are multiple refinements or multiple modules being refined that have already called super to reach this point. I'm not sure this alternative approach works in all cases, but I have gotten it to work every case I've tried: * Module and refinement appears multiple times in the ancestor chain * `super` calls both directly in method as well as in nested blocks in method * Both refinement method and module method are bmethods * Refinement refines multiple modules and both modules are involved in the same super call chain * Multiple refinements of the same module method * Use of ZSUPER aliases in the super call chain * The `super` call in the module method is not found, and `NoMethodError` is raised correctly We want the behavior of module refinement `super` to be consistent, so in addition to fixing `super` itself, we also need to fix `defined?(super)` as well as `Method#super_method` and `UnboundMethod#super_method`. `defined?(super)` is fixed by sharing the lookup that `super` now uses. However, `Method#super_method` and `UnboundMethod#super_method` require a different approach. Thankfully, `struct METHOD` already has a member named `iclass` that is used to implement `#super_method`. So we just need to make adjustments to the setting of the `iclass` member for `#super_method` to work. Simple example: ```ruby module M def m = [:M, *super] end class C prepend M def m = :C end module R refine M do def m = [:R, *super] end end using R C.new.m # => [:R, :M, :C] ``` I've submitted a pull request to implement this: https://github.com/ruby/ruby/pull/18797 -- 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/