1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

Support obj.clone(freeze: true) for freezing clone

This freezes the clone even if the receiver is not frozen.  It
is only for consistency with freeze: false not freezing the clone
even if the receiver is frozen.

Because Object#clone is now partially implemented in Ruby and
not fully implemented in C, freeze: nil must be supported to
provide the default behavior of only freezing the clone if the
receiver is frozen.

This requires modifying delegate and set, to set freeze: nil
instead of freeze: true as the keyword parameter for
initialize_clone.  Those are the two libraries in stdlib that
override initialize_clone.

Implements [Feature #16175]
This commit is contained in:
Jeremy Evans 2019-09-23 16:03:15 -07:00
parent 095e9f57af
commit 4f7b435c95
Notes: git 2020-03-23 01:30:39 +09:00
6 changed files with 78 additions and 34 deletions

View file

@ -1,13 +1,13 @@
module Kernel module Kernel
# #
# call-seq: # call-seq:
# obj.clone(freeze: true) -> an_object # obj.clone(freeze: nil) -> an_object
# #
# Produces a shallow copy of <i>obj</i>---the instance variables of # Produces a shallow copy of <i>obj</i>---the instance variables of
# <i>obj</i> are copied, but not the objects they reference. # <i>obj</i> are copied, but not the objects they reference.
# #clone copies the frozen (unless +:freeze+ keyword argument is # #clone copies the frozen value state of <i>obj</i>, unless the
# given with a false value) state of <i>obj</i>. See # +:freeze+ keyword argument is given with a false or true value.
# also the discussion under Object#dup. # See also the discussion under Object#dup.
# #
# class Klass # class Klass
# attr_accessor :str # attr_accessor :str
@ -23,7 +23,7 @@ module Kernel
# behavior will be documented under the #+initialize_copy+ method of # behavior will be documented under the #+initialize_copy+ method of
# the class. # the class.
# #
def clone(freeze: true) def clone(freeze: nil)
__builtin_rb_obj_clone2(freeze) __builtin_rb_obj_clone2(freeze)
end end
end end

View file

@ -218,7 +218,7 @@ class Delegator < BasicObject
end end
end end
def initialize_clone(obj, freeze: true) # :nodoc: def initialize_clone(obj, freeze: nil) # :nodoc:
self.__setobj__(obj.__getobj__.clone(freeze: freeze)) self.__setobj__(obj.__getobj__.clone(freeze: freeze))
end end
def initialize_dup(obj) # :nodoc: def initialize_dup(obj) # :nodoc:

View file

@ -137,7 +137,7 @@ class Set
end end
# Clone internal hash. # Clone internal hash.
def initialize_clone(orig, freeze: true) def initialize_clone(orig, freeze: nil)
super super
@hash = orig.instance_variable_get(:@hash).clone(freeze: freeze) @hash = orig.instance_variable_get(:@hash).clone(freeze: freeze)
end end

View file

@ -369,9 +369,9 @@ init_copy(VALUE dest, VALUE obj)
} }
} }
static int freeze_opt(int argc, VALUE *argv); static VALUE freeze_opt(int argc, VALUE *argv);
static VALUE immutable_obj_clone(VALUE obj, int kwfreeze); static VALUE immutable_obj_clone(VALUE obj, VALUE kwfreeze);
static VALUE mutable_obj_clone(VALUE obj, int kwfreeze); static VALUE mutable_obj_clone(VALUE obj, VALUE kwfreeze);
PUREFUNC(static inline int special_object_p(VALUE obj)); /*!< \private */ PUREFUNC(static inline int special_object_p(VALUE obj)); /*!< \private */
static inline int static inline int
special_object_p(VALUE obj) special_object_p(VALUE obj)
@ -390,21 +390,25 @@ special_object_p(VALUE obj)
} }
} }
static int static VALUE
obj_freeze_opt(VALUE freeze) obj_freeze_opt(VALUE freeze)
{ {
if (freeze == Qfalse) return FALSE; switch(freeze) {
case Qfalse:
if (freeze != Qtrue) case Qtrue:
case Qnil:
break;
default:
rb_raise(rb_eArgError, "unexpected value for freeze: %"PRIsVALUE, rb_obj_class(freeze)); rb_raise(rb_eArgError, "unexpected value for freeze: %"PRIsVALUE, rb_obj_class(freeze));
}
return TRUE; return freeze;
} }
static VALUE static VALUE
rb_obj_clone2(rb_execution_context_t *ec, VALUE obj, VALUE freeze) rb_obj_clone2(rb_execution_context_t *ec, VALUE obj, VALUE freeze)
{ {
int kwfreeze = obj_freeze_opt(freeze); VALUE kwfreeze = obj_freeze_opt(freeze);
if (!special_object_p(obj)) if (!special_object_p(obj))
return mutable_obj_clone(obj, kwfreeze); return mutable_obj_clone(obj, kwfreeze);
return immutable_obj_clone(obj, kwfreeze); return immutable_obj_clone(obj, kwfreeze);
@ -414,17 +418,16 @@ rb_obj_clone2(rb_execution_context_t *ec, VALUE obj, VALUE freeze)
VALUE VALUE
rb_immutable_obj_clone(int argc, VALUE *argv, VALUE obj) rb_immutable_obj_clone(int argc, VALUE *argv, VALUE obj)
{ {
int kwfreeze = freeze_opt(argc, argv); VALUE kwfreeze = freeze_opt(argc, argv);
return immutable_obj_clone(obj, kwfreeze); return immutable_obj_clone(obj, kwfreeze);
} }
static int static VALUE
freeze_opt(int argc, VALUE *argv) freeze_opt(int argc, VALUE *argv)
{ {
static ID keyword_ids[1]; static ID keyword_ids[1];
VALUE opt; VALUE opt;
VALUE kwfreeze; VALUE kwfreeze = Qnil;
int ret = 1;
if (!keyword_ids[0]) { if (!keyword_ids[0]) {
CONST_ID(keyword_ids[0], "freeze"); CONST_ID(keyword_ids[0], "freeze");
@ -432,24 +435,26 @@ freeze_opt(int argc, VALUE *argv)
rb_scan_args(argc, argv, "0:", &opt); rb_scan_args(argc, argv, "0:", &opt);
if (!NIL_P(opt)) { if (!NIL_P(opt)) {
rb_get_kwargs(opt, keyword_ids, 0, 1, &kwfreeze); rb_get_kwargs(opt, keyword_ids, 0, 1, &kwfreeze);
if (kwfreeze != Qundef) ret = obj_freeze_opt(kwfreeze); if (kwfreeze != Qundef)
kwfreeze = obj_freeze_opt(kwfreeze);
} }
return ret; return kwfreeze;
} }
static VALUE static VALUE
immutable_obj_clone(VALUE obj, int kwfreeze) immutable_obj_clone(VALUE obj, VALUE kwfreeze)
{ {
if (!kwfreeze) if (kwfreeze == Qfalse)
rb_raise(rb_eArgError, "can't unfreeze %"PRIsVALUE, rb_raise(rb_eArgError, "can't unfreeze %"PRIsVALUE,
rb_obj_class(obj)); rb_obj_class(obj));
return obj; return obj;
} }
static VALUE static VALUE
mutable_obj_clone(VALUE obj, int kwfreeze) mutable_obj_clone(VALUE obj, VALUE kwfreeze)
{ {
VALUE clone, singleton; VALUE clone, singleton;
VALUE argv[2];
clone = rb_obj_alloc(rb_obj_class(obj)); clone = rb_obj_alloc(rb_obj_class(obj));
@ -461,23 +466,44 @@ mutable_obj_clone(VALUE obj, int kwfreeze)
init_copy(clone, obj); init_copy(clone, obj);
if (kwfreeze) { switch (kwfreeze) {
case Qnil:
rb_funcall(clone, id_init_clone, 1, obj); rb_funcall(clone, id_init_clone, 1, obj);
RBASIC(clone)->flags |= RBASIC(obj)->flags & FL_FREEZE; RBASIC(clone)->flags |= RBASIC(obj)->flags & FL_FREEZE;
break;
case Qtrue:
{
static VALUE freeze_true_hash;
if (!freeze_true_hash) {
freeze_true_hash = rb_hash_new();
rb_gc_register_mark_object(freeze_true_hash);
rb_hash_aset(freeze_true_hash, ID2SYM(rb_intern("freeze")), Qtrue);
rb_obj_freeze(freeze_true_hash);
} }
else {
argv[0] = obj;
argv[1] = freeze_true_hash;
rb_funcallv_kw(clone, id_init_clone, 2, argv, RB_PASS_KEYWORDS);
RBASIC(clone)->flags |= FL_FREEZE;
break;
}
case Qfalse:
{
static VALUE freeze_false_hash; static VALUE freeze_false_hash;
VALUE argv[2];
if (!freeze_false_hash) { if (!freeze_false_hash) {
freeze_false_hash = rb_hash_new(); freeze_false_hash = rb_hash_new();
rb_gc_register_mark_object(freeze_false_hash);
rb_hash_aset(freeze_false_hash, ID2SYM(rb_intern("freeze")), Qfalse); rb_hash_aset(freeze_false_hash, ID2SYM(rb_intern("freeze")), Qfalse);
rb_obj_freeze(freeze_false_hash); rb_obj_freeze(freeze_false_hash);
rb_gc_register_mark_object(freeze_false_hash);
} }
argv[0] = obj; argv[0] = obj;
argv[1] = freeze_false_hash; argv[1] = freeze_false_hash;
rb_funcallv_kw(clone, id_init_clone, 2, argv, RB_PASS_KEYWORDS); rb_funcallv_kw(clone, id_init_clone, 2, argv, RB_PASS_KEYWORDS);
break;
}
default:
rb_bug("invalid kwfreeze passed to mutable_obj_clone");
} }
return clone; return clone;
@ -493,7 +519,7 @@ VALUE
rb_obj_clone(VALUE obj) rb_obj_clone(VALUE obj)
{ {
if (special_object_p(obj)) return obj; if (special_object_p(obj)) return obj;
return mutable_obj_clone(obj, Qtrue); return mutable_obj_clone(obj, Qnil);
} }
/** /**

View file

@ -37,11 +37,17 @@ describe "Kernel#clone" do
o3.frozen?.should == true o3.frozen?.should == true
end end
it 'takes an option to copy freeze state or not' do ruby_version_is '2.8' do
@obj.clone(freeze: true).frozen?.should == false it 'takes an freeze: true option to frozen copy' do
@obj.clone(freeze: false).frozen?.should == false @obj.clone(freeze: true).frozen?.should == true
@obj.freeze @obj.freeze
@obj.clone(freeze: true).frozen?.should == true @obj.clone(freeze: true).frozen?.should == true
end
end
it 'takes an freeze: false option to not return frozen copy' do
@obj.clone(freeze: false).frozen?.should == false
@obj.freeze
@obj.clone(freeze: false).frozen?.should == false @obj.clone(freeze: false).frozen?.should == false
end end

View file

@ -47,15 +47,27 @@ class TestObject < Test::Unit::TestCase
a = Object.new a = Object.new
def a.b; 2 end def a.b; 2 end
c = a.clone
assert_equal(false, c.frozen?)
assert_equal(false, a.frozen?)
assert_equal(2, c.b)
c = a.clone(freeze: true)
assert_equal(true, c.frozen?)
assert_equal(false, a.frozen?)
assert_equal(2, c.b)
a.freeze a.freeze
c = a.clone c = a.clone
assert_equal(true, c.frozen?) assert_equal(true, c.frozen?)
assert_equal(true, a.frozen?)
assert_equal(2, c.b) assert_equal(2, c.b)
assert_raise(ArgumentError) {a.clone(freeze: [])} assert_raise(ArgumentError) {a.clone(freeze: [])}
d = a.clone(freeze: false) d = a.clone(freeze: false)
def d.e; 3; end def d.e; 3; end
assert_equal(false, d.frozen?) assert_equal(false, d.frozen?)
assert_equal(true, a.frozen?)
assert_equal(2, d.b) assert_equal(2, d.b)
assert_equal(3, d.e) assert_equal(3, d.e)