Skip to content

Commit 4f7b435

Browse files
committed
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]
1 parent 095e9f5 commit 4f7b435

File tree

6 files changed

+78
-34
lines changed

6 files changed

+78
-34
lines changed

kernel.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
module Kernel
22
#
33
# call-seq:
4-
# obj.clone(freeze: true) -> an_object
4+
# obj.clone(freeze: nil) -> an_object
55
#
66
# Produces a shallow copy of <i>obj</i>---the instance variables of
77
# <i>obj</i> are copied, but not the objects they reference.
8-
# #clone copies the frozen (unless +:freeze+ keyword argument is
9-
# given with a false value) state of <i>obj</i>. See
10-
# also the discussion under Object#dup.
8+
# #clone copies the frozen value state of <i>obj</i>, unless the
9+
# +:freeze+ keyword argument is given with a false or true value.
10+
# See also the discussion under Object#dup.
1111
#
1212
# class Klass
1313
# attr_accessor :str
@@ -23,7 +23,7 @@ module Kernel
2323
# behavior will be documented under the #+initialize_copy+ method of
2424
# the class.
2525
#
26-
def clone(freeze: true)
26+
def clone(freeze: nil)
2727
__builtin_rb_obj_clone2(freeze)
2828
end
2929
end

lib/delegate.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ def marshal_load(data)
218218
end
219219
end
220220

221-
def initialize_clone(obj, freeze: true) # :nodoc:
221+
def initialize_clone(obj, freeze: nil) # :nodoc:
222222
self.__setobj__(obj.__getobj__.clone(freeze: freeze))
223223
end
224224
def initialize_dup(obj) # :nodoc:

lib/set.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def initialize_dup(orig)
137137
end
138138

139139
# Clone internal hash.
140-
def initialize_clone(orig, freeze: true)
140+
def initialize_clone(orig, freeze: nil)
141141
super
142142
@hash = orig.instance_variable_get(:@hash).clone(freeze: freeze)
143143
end

object.c

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -369,9 +369,9 @@ init_copy(VALUE dest, VALUE obj)
369369
}
370370
}
371371

372-
static int freeze_opt(int argc, VALUE *argv);
373-
static VALUE immutable_obj_clone(VALUE obj, int kwfreeze);
374-
static VALUE mutable_obj_clone(VALUE obj, int kwfreeze);
372+
static VALUE freeze_opt(int argc, VALUE *argv);
373+
static VALUE immutable_obj_clone(VALUE obj, VALUE kwfreeze);
374+
static VALUE mutable_obj_clone(VALUE obj, VALUE kwfreeze);
375375
PUREFUNC(static inline int special_object_p(VALUE obj)); /*!< \private */
376376
static inline int
377377
special_object_p(VALUE obj)
@@ -390,21 +390,25 @@ special_object_p(VALUE obj)
390390
}
391391
}
392392

393-
static int
393+
static VALUE
394394
obj_freeze_opt(VALUE freeze)
395395
{
396-
if (freeze == Qfalse) return FALSE;
397-
398-
if (freeze != Qtrue)
396+
switch(freeze) {
397+
case Qfalse:
398+
case Qtrue:
399+
case Qnil:
400+
break;
401+
default:
399402
rb_raise(rb_eArgError, "unexpected value for freeze: %"PRIsVALUE, rb_obj_class(freeze));
403+
}
400404

401-
return TRUE;
405+
return freeze;
402406
}
403407

404408
static VALUE
405409
rb_obj_clone2(rb_execution_context_t *ec, VALUE obj, VALUE freeze)
406410
{
407-
int kwfreeze = obj_freeze_opt(freeze);
411+
VALUE kwfreeze = obj_freeze_opt(freeze);
408412
if (!special_object_p(obj))
409413
return mutable_obj_clone(obj, kwfreeze);
410414
return immutable_obj_clone(obj, kwfreeze);
@@ -414,42 +418,43 @@ rb_obj_clone2(rb_execution_context_t *ec, VALUE obj, VALUE freeze)
414418
VALUE
415419
rb_immutable_obj_clone(int argc, VALUE *argv, VALUE obj)
416420
{
417-
int kwfreeze = freeze_opt(argc, argv);
421+
VALUE kwfreeze = freeze_opt(argc, argv);
418422
return immutable_obj_clone(obj, kwfreeze);
419423
}
420424

421-
static int
425+
static VALUE
422426
freeze_opt(int argc, VALUE *argv)
423427
{
424428
static ID keyword_ids[1];
425429
VALUE opt;
426-
VALUE kwfreeze;
427-
int ret = 1;
430+
VALUE kwfreeze = Qnil;
428431

429432
if (!keyword_ids[0]) {
430433
CONST_ID(keyword_ids[0], "freeze");
431434
}
432435
rb_scan_args(argc, argv, "0:", &opt);
433436
if (!NIL_P(opt)) {
434437
rb_get_kwargs(opt, keyword_ids, 0, 1, &kwfreeze);
435-
if (kwfreeze != Qundef) ret = obj_freeze_opt(kwfreeze);
438+
if (kwfreeze != Qundef)
439+
kwfreeze = obj_freeze_opt(kwfreeze);
436440
}
437-
return ret;
441+
return kwfreeze;
438442
}
439443

440444
static VALUE
441-
immutable_obj_clone(VALUE obj, int kwfreeze)
445+
immutable_obj_clone(VALUE obj, VALUE kwfreeze)
442446
{
443-
if (!kwfreeze)
447+
if (kwfreeze == Qfalse)
444448
rb_raise(rb_eArgError, "can't unfreeze %"PRIsVALUE,
445449
rb_obj_class(obj));
446450
return obj;
447451
}
448452

449453
static VALUE
450-
mutable_obj_clone(VALUE obj, int kwfreeze)
454+
mutable_obj_clone(VALUE obj, VALUE kwfreeze)
451455
{
452456
VALUE clone, singleton;
457+
VALUE argv[2];
453458

454459
clone = rb_obj_alloc(rb_obj_class(obj));
455460

@@ -461,23 +466,44 @@ mutable_obj_clone(VALUE obj, int kwfreeze)
461466

462467
init_copy(clone, obj);
463468

464-
if (kwfreeze) {
469+
switch (kwfreeze) {
470+
case Qnil:
465471
rb_funcall(clone, id_init_clone, 1, obj);
466472
RBASIC(clone)->flags |= RBASIC(obj)->flags & FL_FREEZE;
467-
}
468-
else {
473+
break;
474+
case Qtrue:
475+
{
476+
static VALUE freeze_true_hash;
477+
if (!freeze_true_hash) {
478+
freeze_true_hash = rb_hash_new();
479+
rb_gc_register_mark_object(freeze_true_hash);
480+
rb_hash_aset(freeze_true_hash, ID2SYM(rb_intern("freeze")), Qtrue);
481+
rb_obj_freeze(freeze_true_hash);
482+
}
483+
484+
argv[0] = obj;
485+
argv[1] = freeze_true_hash;
486+
rb_funcallv_kw(clone, id_init_clone, 2, argv, RB_PASS_KEYWORDS);
487+
RBASIC(clone)->flags |= FL_FREEZE;
488+
break;
489+
}
490+
case Qfalse:
491+
{
469492
static VALUE freeze_false_hash;
470-
VALUE argv[2];
471493
if (!freeze_false_hash) {
472494
freeze_false_hash = rb_hash_new();
495+
rb_gc_register_mark_object(freeze_false_hash);
473496
rb_hash_aset(freeze_false_hash, ID2SYM(rb_intern("freeze")), Qfalse);
474497
rb_obj_freeze(freeze_false_hash);
475-
rb_gc_register_mark_object(freeze_false_hash);
476498
}
477499

478500
argv[0] = obj;
479501
argv[1] = freeze_false_hash;
480502
rb_funcallv_kw(clone, id_init_clone, 2, argv, RB_PASS_KEYWORDS);
503+
break;
504+
}
505+
default:
506+
rb_bug("invalid kwfreeze passed to mutable_obj_clone");
481507
}
482508

483509
return clone;
@@ -493,7 +519,7 @@ VALUE
493519
rb_obj_clone(VALUE obj)
494520
{
495521
if (special_object_p(obj)) return obj;
496-
return mutable_obj_clone(obj, Qtrue);
522+
return mutable_obj_clone(obj, Qnil);
497523
}
498524

499525
/**

spec/ruby/core/kernel/clone_spec.rb

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,17 @@ def klass.allocate
3737
o3.frozen?.should == true
3838
end
3939

40-
it 'takes an option to copy freeze state or not' do
41-
@obj.clone(freeze: true).frozen?.should == false
40+
ruby_version_is '2.8' do
41+
it 'takes an freeze: true option to frozen copy' do
42+
@obj.clone(freeze: true).frozen?.should == true
43+
@obj.freeze
44+
@obj.clone(freeze: true).frozen?.should == true
45+
end
46+
end
47+
48+
it 'takes an freeze: false option to not return frozen copy' do
4249
@obj.clone(freeze: false).frozen?.should == false
4350
@obj.freeze
44-
@obj.clone(freeze: true).frozen?.should == true
4551
@obj.clone(freeze: false).frozen?.should == false
4652
end
4753

test/ruby/test_object.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,27 @@ def test_clone
4747
a = Object.new
4848
def a.b; 2 end
4949

50+
c = a.clone
51+
assert_equal(false, c.frozen?)
52+
assert_equal(false, a.frozen?)
53+
assert_equal(2, c.b)
54+
55+
c = a.clone(freeze: true)
56+
assert_equal(true, c.frozen?)
57+
assert_equal(false, a.frozen?)
58+
assert_equal(2, c.b)
59+
5060
a.freeze
5161
c = a.clone
5262
assert_equal(true, c.frozen?)
63+
assert_equal(true, a.frozen?)
5364
assert_equal(2, c.b)
5465

5566
assert_raise(ArgumentError) {a.clone(freeze: [])}
5667
d = a.clone(freeze: false)
5768
def d.e; 3; end
5869
assert_equal(false, d.frozen?)
70+
assert_equal(true, a.frozen?)
5971
assert_equal(2, d.b)
6072
assert_equal(3, d.e)
6173

0 commit comments

Comments
 (0)