From 210ed6a9f5e64c3ecd4fe4fd420225b82ed58f89 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Fri, 19 Feb 2016 12:20:38 -0800 Subject: [PATCH] Allow clone to yield cloned object before freezing This allows creating modified clones of frozen objects that have singleton classes: a = [1,2,3] def a.fl; first + last; end a.freeze a.fl # => 4 clone = a.clone{|c| c << 10} clone.last # => 10 clone.fl # => 11 clone.frozen? # => true Previously, this was not possible at all. If an object was frozen, the clone was frozen before the cloned object could be modified. It was possible to modify the clone using initialize_clone or initialize_copy, but you couldn't change how to modify the clone on a per-call basis. You couldn't use dup to return an unfrozen copy, modify it, and then freeze it, because dup doesn't copy singleton classes. This allows ruby to be used in a functional style with immutable data structures, while still keeping the advantages of singleton classes. --- object.c | 6 ++++++ test/ruby/test_object.rb | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/object.c b/object.c index 2497043..301eeb9 100644 --- a/object.c +++ b/object.c @@ -316,6 +316,9 @@ init_copy(VALUE dest, VALUE obj) * s1.inspect #=> "#" * s2.inspect #=> "#" * + * If a block is given, it is yielded the cloned object before the + * cloned object is frozen (if the receiver is frozen). + * * This method may have class-specific behavior. If so, that * behavior will be documented under the #+initialize_copy+ method of * the class. @@ -342,6 +345,9 @@ rb_obj_clone(VALUE obj) init_copy(clone, obj); rb_funcall(clone, id_init_clone, 1, obj); + if (rb_block_given_p()) { + rb_yield(clone); + } RBASIC(clone)->flags |= RBASIC(obj)->flags & FL_FREEZE; return clone; diff --git a/test/ruby/test_object.rb b/test/ruby/test_object.rb index 0ab1341..6212c9a 100644 --- a/test/ruby/test_object.rb +++ b/test/ruby/test_object.rb @@ -39,6 +39,18 @@ def initialize_dup(orig); throw :initialize_dup; end assert_throw(:initialize_dup) {obj.dup} end + def test_clone_with_block + cls = Class.new do + attr_accessor :b + end + obj = cls.new + obj.b = 1 + obj.freeze + c = obj.clone{|clone| clone.b = 2} + assert_equal 2, c.b + assert c.frozen? + end + def test_instance_of assert_raise(TypeError) { 1.instance_of?(1) } end -- 2.7.0