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
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:
classAdefinitialize_dup(_)puts"dup instance"superendendclassClassdefinitialize_dup(_)puts"dup class"superendendA.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:
classAdefinitialize_clone(_)puts"clone instance"superenddefself.initialize_clone(_)puts"clone class"superendendA.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:
[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:
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.