diff --git a/CHANGELOG.md b/CHANGELOG.md index f92f9ae6d48c..8ebfc3f1e4ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ Compatibility: * Add `Dir#chdir` (#3681, @andrykonchin). * Declare `File::SHARE_DELETE` constant (#3745, @andrykonchin). * Support `symbolize_names` argument to `MatchData#named_captures` (#3681, @rwstauner). +* Support `Proc#initialize_{dup,copy}` for subclasses (#3681, @rwstauner). Performance: diff --git a/spec/ruby/core/proc/clone_spec.rb b/spec/ruby/core/proc/clone_spec.rb index 7eca9c561e28..26f031334f7d 100644 --- a/spec/ruby/core/proc/clone_spec.rb +++ b/spec/ruby/core/proc/clone_spec.rb @@ -1,4 +1,5 @@ require_relative '../../spec_helper' +require_relative 'fixtures/common' require_relative 'shared/dup' describe "Proc#clone" do @@ -12,4 +13,18 @@ proc.clone.frozen?.should == true end end + + ruby_version_is "3.3" do + it "calls #initialize_copy on subclass" do + obj = ProcSpecs::MyProc2.new(:a, 2) { } + dup = obj.clone + + dup.should_not equal(obj) + dup.class.should == ProcSpecs::MyProc2 + + dup.first.should == :a + dup.second.should == 2 + dup.initializer.should == :copy + end + end end diff --git a/spec/ruby/core/proc/dup_spec.rb b/spec/ruby/core/proc/dup_spec.rb index dd19b3c1e974..716357d1f0e3 100644 --- a/spec/ruby/core/proc/dup_spec.rb +++ b/spec/ruby/core/proc/dup_spec.rb @@ -1,4 +1,5 @@ require_relative '../../spec_helper' +require_relative 'fixtures/common' require_relative 'shared/dup' describe "Proc#dup" do @@ -10,4 +11,18 @@ proc.frozen?.should == true proc.dup.frozen?.should == false end + + ruby_version_is "3.3" do + it "calls #initialize_dup on subclass" do + obj = ProcSpecs::MyProc2.new(:a, 2) { } + dup = obj.dup + + dup.should_not equal(obj) + dup.class.should == ProcSpecs::MyProc2 + + dup.first.should == :a + dup.second.should == 2 + dup.initializer.should == :dup + end + end end diff --git a/spec/ruby/core/proc/fixtures/common.rb b/spec/ruby/core/proc/fixtures/common.rb index 6e27a2dee74f..204acde597ba 100644 --- a/spec/ruby/core/proc/fixtures/common.rb +++ b/spec/ruby/core/proc/fixtures/common.rb @@ -32,7 +32,21 @@ def initialize(a, b) @second = b end - attr_reader :first, :second + attr_reader :first, :second, :initializer + + def initialize_copy(other) + super + @initializer = :copy + @first = other.first + @second = other.second + end + + def initialize_dup(other) + super + @initializer = :dup + @first = other.first + @second = other.second + end end class Arity diff --git a/src/main/java/org/truffleruby/core/proc/ProcNodes.java b/src/main/java/org/truffleruby/core/proc/ProcNodes.java index d0c8723f4cb6..e3b7414f485e 100644 --- a/src/main/java/org/truffleruby/core/proc/ProcNodes.java +++ b/src/main/java/org/truffleruby/core/proc/ProcNodes.java @@ -86,21 +86,7 @@ RubyProc procSpecial(VirtualFrame frame, RubyClass procClass, Object[] args, Rub @Cached DispatchNode initialize) { // Instantiate a new instance of procClass as classes do not correspond - final RubyProc proc = new RubyProc( - procClass, - getLanguage().procShape, - block.type, - block.arity, - block.argumentDescriptors, - block.callTargets, - block.callTarget, - block.declarationFrame, - block.declarationVariables, - block.declaringMethod, - block.frameOnStackMarker, - block.declarationContext); - - AllocationTracing.trace(proc, this); + final RubyProc proc = ProcOperations.duplicate(procClass, getLanguage().procShape, block, this); initialize.callWithDescriptor(proc, "initialize", block, RubyArguments.getDescriptor(frame), args); return proc; } @@ -110,27 +96,26 @@ protected RubyClass metaClass(RubyProc object) { } } - @CoreMethod(names = { "dup", "clone" }) + @CoreMethod(names = "clone") + public abstract static class CloneNode extends CoreMethodArrayArgumentsNode { + + @Specialization + RubyProc clone(RubyProc proc, + @Cached DispatchNode initializeCopyNode) { + final RubyProc copy = ProcOperations.duplicate(proc.getLogicalClass(), getLanguage().procShape, proc, this); + initializeCopyNode.call(copy, "initialize_copy", proc); + return copy; + } + } + + @CoreMethod(names = "dup") public abstract static class DupNode extends CoreMethodArrayArgumentsNode { @Specialization - RubyProc dup(RubyProc proc) { - final RubyClass logicalClass = proc.getLogicalClass(); - final RubyProc copy = new RubyProc( - logicalClass, - getLanguage().procShape, - proc.type, - proc.arity, - proc.argumentDescriptors, - proc.callTargets, - proc.callTarget, - proc.declarationFrame, - proc.declarationVariables, - proc.declaringMethod, - proc.frameOnStackMarker, - proc.declarationContext); - - AllocationTracing.trace(copy, this); + RubyProc dup(RubyProc proc, + @Cached DispatchNode initializeDupNode) { + final RubyProc copy = ProcOperations.duplicate(proc.getLogicalClass(), getLanguage().procShape, proc, this); + initializeDupNode.call(copy, "initialize_dup", proc); return copy; } } diff --git a/src/main/java/org/truffleruby/core/proc/ProcOperations.java b/src/main/java/org/truffleruby/core/proc/ProcOperations.java index 09a697848c0e..40381588561b 100644 --- a/src/main/java/org/truffleruby/core/proc/ProcOperations.java +++ b/src/main/java/org/truffleruby/core/proc/ProcOperations.java @@ -10,6 +10,7 @@ package org.truffleruby.core.proc; import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.object.Shape; import org.truffleruby.RubyContext; import org.truffleruby.RubyLanguage; @@ -20,6 +21,7 @@ import org.truffleruby.language.methods.DeclarationContext; import org.truffleruby.language.methods.InternalMethod; import org.truffleruby.language.methods.SharedMethodInfo; +import org.truffleruby.language.objects.AllocationTracing; import org.truffleruby.language.threadlocal.SpecialVariableStorage; import com.oracle.truffle.api.RootCallTarget; @@ -123,6 +125,24 @@ public static RubyProc createProcFromBlock(RubyContext context, RubyLanguage lan return convertBlock(context, language, block, ProcType.PROC); } + public static RubyProc duplicate(RubyClass procClass, Shape shape, RubyProc proc, Node node) { + final RubyProc copy = new RubyProc( + procClass, + shape, + proc.type, + proc.arity, + proc.argumentDescriptors, + proc.callTargets, + proc.callTarget, + proc.declarationFrame, + proc.declarationVariables, + proc.declaringMethod, + proc.frameOnStackMarker, + proc.declarationContext); + AllocationTracing.trace(copy, node); + return copy; + } + public static Object getSelf(RubyProc proc) { return RubyArguments.getSelf(proc.declarationFrame); }