[ruby-core:122945] [Ruby Bug#21538] initialize_dup not called when duping class/module

Issue #21538 has been reported by chucke (Tiago Cardoso). ---------------------------------------- Bug #21538: initialize_dup not called when duping class/module https://bugs.ruby-lang.org/issues/21538 * Author: chucke (Tiago Cardoso) * Status: Open * ruby -v: 3.4.5 * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- Not sure whether this is expected behaviour or not, but just leaving it here to start the debate on whether callbacks like `initialize_dup` are supposed to be called when a module or class is duped (the same happens with `initialize_copy` and `initialize_clone` btw): class A def initialize_dup(_) puts "dup instance" super end def self.initialize_dup(_) puts "dup class" super end end A.new.dup #=> "dup instance" A.dup #=> nothing -- https://bugs.ruby-lang.org/

Issue #21538 has been updated by jeremyevans0 (Jeremy Evans). The example given isn't a bug. `initialize_dup` is called on the new instance (in this example, the instance of `Class`), and `dup` does not copy singleton classes. If you define `Class#inititialize_dup`, it works as expected: ```ruby class A def initialize_dup(_) puts "dup instance" super end end class Class def initialize_dup(_) puts "dup class" super end end A.new.dup # prints "dup instance" A.dup # prints "dup class" ``` `dup` is different than `clone`, because `clone` copies the singleton class, and therefore will pick up singleton methods: ```ruby class A def initialize_clone(_) puts "clone instance" super end def self.initialize_clone(_) puts "clone class" super end end A.new.clone # prints "clone instance" A.clone # prints "clone class" ``` That being said, there are two definite bugs and another probable bug in `Class#dup`, as evidenced by this example: ```ruby class Class def initialize_dup(_) p ancestors p singleton_class.ancestors super end end class B def self.initialize_dup(_) puts "dup class" super end end class A < B end puts p A.ancestors p A.singleton_class.ancestors puts C = A.dup puts p C.ancestors p C.singleton_class.ancestors ``` Output on Ruby 3.4 (with comments on bugs): ``` [A, B, Object, Kernel, BasicObject] [#<Class:A>, #<Class:B>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject] # Probable Bug: During initialize_dup, ancestors are missing, # and therefore ancestor initialize_dup singleton method not called [#<Class:0x00000d7e572e8ca8>] [#<Class:#<Class:0x00000d7e572e8ca8>>, Class, Module, Object, Kernel, BasicObject] # Definite Bug 1: after dup, singleton class ancestors are missing [C, B, Object, Kernel, BasicObject] [#<Class:C>, Class, Module, Object, Kernel, BasicObject] ``` Output on master branch: ``` [A, B, Object, Kernel, BasicObject] [#<Class:A>, #<Class:B>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject] # Definite Bug 2: calling singleton_class inside Class#initialize_dup # results in TypeError [#<Class:0x00000b7eeb88aca0>] [#<Class:#<Class:0x00000b7eeb88aca0>>, Class, Module, Object, Kernel, BasicObject] -:5:in 'Module#initialize_copy': already initialized class (TypeError) from -:5:in 'Kernel#initialize_dup' from -:5:in 'Class#initialize_dup' from -:25:in 'Kernel#dup' from -:25:in '<main>' ``` The reason I call the first bug probable and not definite is that it is at least defensible that ancestor setup occurs inside `dup`, but after the call to `Class#initialize_dup`. I still think it should be considered a bug and fixed. I'm guessing fixing definite bug 2 requires fixing the probable bug. For `clone`, there is a similar issue of missing ancestors (but not singleton class ancestors) inside `initialize_clone`: ```ruby class B def self.initialize_clone(_) p ancestors p singleton_class.ancestors puts "clone class" super end end class A < B end puts p A.ancestors p A.singleton_class.ancestors puts C = A.clone puts p C.ancestors p C.singleton_class.ancestors ``` Output: ``` [A, B, Object, Kernel, BasicObject] [#<Class:A>, #<Class:B>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject] # Probable bug: During initialize_clone, ancestors are missing [#<Class:0x00000a047fffaab8>] [#<Class:#<Class:0x00000a047fffaab8>>, #<Class:B>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject] clone class [C, B, Object, Kernel, BasicObject] [#<Class:C>, #<Class:B>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject] ``` ---------------------------------------- Bug #21538: initialize_dup not called when duping class/module https://bugs.ruby-lang.org/issues/21538#change-114458 * Author: chucke (Tiago Cardoso) * Status: Open * ruby -v: 3.4.5 * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- Not sure whether this is expected behaviour or not, but just leaving it here to start the debate on whether callbacks like `initialize_dup` are supposed to be called when a module or class is duped (the same happens with `initialize_copy` and `initialize_clone` btw): class A def initialize_dup(_) puts "dup instance" super end def self.initialize_dup(_) puts "dup class" super end end A.new.dup #=> "dup instance" A.dup #=> nothing -- https://bugs.ruby-lang.org/

Issue #21538 has been updated by jeremyevans0 (Jeremy Evans). I found a simple fix for all issues: https://github.com/ruby/ruby/pull/14412 ---------------------------------------- Bug #21538: initialize_dup not called when duping class/module https://bugs.ruby-lang.org/issues/21538#change-114460 * Author: chucke (Tiago Cardoso) * Status: Open * ruby -v: 3.4.5 * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- Not sure whether this is expected behaviour or not, but just leaving it here to start the debate on whether callbacks like `initialize_dup` are supposed to be called when a module or class is duped (the same happens with `initialize_copy` and `initialize_clone` btw): class A def initialize_dup(_) puts "dup instance" super end def self.initialize_dup(_) puts "dup class" super end end A.new.dup #=> "dup instance" A.dup #=> nothing -- https://bugs.ruby-lang.org/
participants (2)
-
chucke (Tiago Cardoso)
-
jeremyevans0 (Jeremy Evans)