Bug #21538
closedinitialize_dup not called when duping class/module
Description
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
Updated by jeremyevans0 (Jeremy Evans) 14 days ago
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:
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:
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:
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
:
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]
Updated by jeremyevans0 (Jeremy Evans) 13 days ago
I found a simple fix for all issues: https://github.com/ruby/ruby/pull/14412
Updated by jeremyevans (Jeremy Evans) 13 days ago
- Status changed from Open to Closed
Applied in changeset git|5c7dfe85a1dc49334e2828791f0ade42eee662db.
Initialize class dup/clone before calling initialize_dup/initialize_clone
Previously, you could override the class initialize_dup/initialize_clone
method and the class hierarchy would not be set correctly inside the
method before calling super.
This removes Module#initialize_copy, and instead makes Object#dup/clone
call the underlying C function (rb_mod_init_copy) before calling the
appropriate initialize_dup/initialize_clone method.
This results in the following fixes:
-
The appropriate initialize_dup method is called (dup on a class
will respect superclass initialize_dup). -
Inside class initialize_dup/initialize_clone/initialize_copy,
class ancestor hierarchy is correct. -
Calling singleton_class inside initialize_dup no longer raises
a TypeError later in dup. -
Calling singleton_class.ancestors inside initialize_dup no
longer results in missing ancestors.
Fixes [Bug #21538]