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:
parent
095e9f57af
commit
4f7b435c95
Notes:
git
2020-03-23 01:30:39 +09:00
6 changed files with 78 additions and 34 deletions
10
kernel.rb
10
kernel.rb
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
72
object.c
72
object.c
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue