diff options
Diffstat (limited to 'spec/ruby/language/optional_assignments_spec.rb')
-rw-r--r-- | spec/ruby/language/optional_assignments_spec.rb | 294 |
1 files changed, 269 insertions, 25 deletions
diff --git a/spec/ruby/language/optional_assignments_spec.rb b/spec/ruby/language/optional_assignments_spec.rb index 02461655d6..2443cc6b79 100644 --- a/spec/ruby/language/optional_assignments_spec.rb +++ b/spec/ruby/language/optional_assignments_spec.rb @@ -57,7 +57,7 @@ describe 'Optional variable assignments' do end end - describe 'using a accessor' do + describe 'using an accessor' do before do klass = Class.new { attr_accessor :b } @a = klass.new @@ -103,6 +103,16 @@ describe 'Optional variable assignments' do @a.b.should == 10 end + it 'does evaluate receiver only once when assigns' do + ScratchPad.record [] + @a.b = nil + + (ScratchPad << :evaluated; @a).b ||= 10 + + ScratchPad.recorded.should == [:evaluated] + @a.b.should == 10 + end + it 'returns the new value if set to false' do def @a.b=(x) :v @@ -122,29 +132,148 @@ describe 'Optional variable assignments' do (@a.b ||= 20).should == 10 end - it 'works when writer is private' do + it 'ignores method visibility when receiver is self' do + klass_with_private_methods = Class.new do + def initialize(v) @a = v end + def public_method(v); self.a ||= v end + private + def a; @a end + def a=(v) @a = v; 42 end + end + + a = klass_with_private_methods.new(false) + a.public_method(10).should == 10 + end + end + + describe 'using a #[]' do + before do + @a = {} klass = Class.new do - def t - self.b = false - (self.b ||= 10).should == 10 - (self.b ||= 20).should == 10 + def [](k) + @hash ||= {} + @hash[k] + end + + def []=(k, v) + @hash ||= {} + @hash[k] = v + 7 end + end + @b = klass.new + end + + it 'returns the assigned value, not the result of the []= method with ||=' do + (@b[:k] ||= 12).should == 12 + end + + it "evaluates the index precisely once" do + ary = [:x, :y] + @a[:x] = 15 + @a[ary.pop] ||= 25 + ary.should == [:x] + @a.should == { x: 15, y: 25 } + end - def b - @b + it "evaluates the index arguments in the correct order" do + ary = Class.new(Array) do + def [](x, y) + super(x + 3 * y) end - def b=(x) - @b = x - :v + def []=(x, y, value) + super(x + 3 * y, value) end + end.new + ary[0, 0] = 1 + ary[1, 0] = 1 + ary[2, 0] = nil + ary[3, 0] = 1 + ary[4, 0] = 1 + ary[5, 0] = 1 + ary[6, 0] = nil + + foo = [0, 2] + + ary[foo.pop, foo.pop] ||= 2 # expected `ary[2, 0] ||= 2` + + ary[2, 0].should == 2 + ary[6, 0].should == nil # returns the same element as `ary[0, 2]` + end + + it 'evaluates receiver only once when assigns' do + ScratchPad.record [] + @a[:k] = nil - private :b= + (ScratchPad << :evaluated; @a)[:k] ||= 2 + + ScratchPad.recorded.should == [:evaluated] + @a[:k].should == 2 + end + + it 'ignores method visibility when receiver is self' do + klass_with_private_methods = Class.new do + def initialize(h) @a = h end + def public_method(k, v); self[k] ||= v end + private + def [](k) @a[k] end + def []=(k, v) @a[k] = v; 42 end end - klass.new.t + a = klass_with_private_methods.new(k: false) + a.public_method(:k, 10).should == 10 end + context 'splatted argument' do + it 'correctly handles it' do + (@b[*[:m]] ||= 10).should == 10 + @b[:m].should == 10 + + (@b[*(1; [:n])] ||= 10).should == 10 + @b[:n].should == 10 + + (@b[*begin 1; [:k] end] ||= 10).should == 10 + @b[:k].should == 10 + end + + it 'calls #to_a only once' do + k = Object.new + def k.to_a + ScratchPad << :to_a + [:k] + end + + ScratchPad.record [] + (@b[*k] ||= 20).should == 20 + @b[:k].should == 20 + ScratchPad.recorded.should == [:to_a] + end + + it 'correctly handles a nested splatted argument' do + (@b[*[*[:k]]] ||= 20).should == 20 + @b[:k].should == 20 + end + + it 'correctly handles multiple nested splatted arguments' do + klass_with_multiple_parameters = Class.new do + def [](k1, k2, k3) + @hash ||= {} + @hash[:"#{k1}#{k2}#{k3}"] + end + + def []=(k1, k2, k3, v) + @hash ||= {} + @hash[:"#{k1}#{k2}#{k3}"] = v + 7 + end + end + a = klass_with_multiple_parameters.new + + (a[*[:a], *[:b], *[:c]] ||= 20).should == 20 + a[:a, :b, :c].should == 20 + end + end end end @@ -191,7 +320,7 @@ describe 'Optional variable assignments' do end end - describe 'using a single variable' do + describe 'using an accessor' do before do klass = Class.new { attr_accessor :b } @a = klass.new @@ -236,6 +365,29 @@ describe 'Optional variable assignments' do @a.b.should == 20 end + + it 'does evaluate receiver only once when assigns' do + ScratchPad.record [] + @a.b = 10 + + (ScratchPad << :evaluated; @a).b &&= 20 + + ScratchPad.recorded.should == [:evaluated] + @a.b.should == 20 + end + + it 'ignores method visibility when receiver is self' do + klass_with_private_methods = Class.new do + def initialize(v) @a = v end + def public_method(v); self.a &&= v end + private + def a; @a end + def a=(v) @a = v; 42 end + end + + a = klass_with_private_methods.new(true) + a.public_method(10).should == 10 + end end describe 'using a #[]' do @@ -297,17 +449,15 @@ describe 'Optional variable assignments' do end it 'returns the assigned value, not the result of the []= method with ||=' do - (@b[:k] ||= 12).should == 12 - end - - it 'correctly handles a splatted argument for the index' do - (@b[*[:k]] ||= 12).should == 12 + @b[:k] = 10 + (@b[:k] &&= 12).should == 12 end it "evaluates the index precisely once" do ary = [:x, :y] @a[:x] = 15 - @a[ary.pop] ||= 25 + @a[:y] = 20 + @a[ary.pop] &&= 25 ary.should == [:x] @a.should == { x: 15, y: 25 } end @@ -324,24 +474,103 @@ describe 'Optional variable assignments' do end.new ary[0, 0] = 1 ary[1, 0] = 1 - ary[2, 0] = nil + ary[2, 0] = 1 ary[3, 0] = 1 ary[4, 0] = 1 ary[5, 0] = 1 - ary[6, 0] = nil + ary[6, 0] = 1 foo = [0, 2] - ary[foo.pop, foo.pop] ||= 2 + ary[foo.pop, foo.pop] &&= 2 # expected `ary[2, 0] &&= 2` ary[2, 0].should == 2 - ary[6, 0].should == nil + ary[6, 0].should == 1 # returns the same element as `ary[0, 2]` + end + + it 'evaluates receiver only once when assigns' do + ScratchPad.record [] + @a[:k] = 1 + + (ScratchPad << :evaluated; @a)[:k] &&= 2 + + ScratchPad.recorded.should == [:evaluated] + @a[:k].should == 2 end it 'returns the assigned value, not the result of the []= method with +=' do @b[:k] = 17 (@b[:k] += 12).should == 29 end + + it 'ignores method visibility when receiver is self' do + klass_with_private_methods = Class.new do + def initialize(h) @a = h end + def public_method(k, v); self[k] &&= v end + private + def [](k) @a[k] end + def []=(k, v) @a[k] = v; 42 end + end + + a = klass_with_private_methods.new(k: true) + a.public_method(:k, 10).should == 10 + end + + context 'splatted argument' do + it 'correctly handles it' do + @b[:m] = 0 + (@b[*[:m]] &&= 10).should == 10 + @b[:m].should == 10 + + @b[:n] = 0 + (@b[*(1; [:n])] &&= 10).should == 10 + @b[:n].should == 10 + + @b[:k] = 0 + (@b[*begin 1; [:k] end] &&= 10).should == 10 + @b[:k].should == 10 + end + + it 'calls #to_a only once' do + k = Object.new + def k.to_a + ScratchPad << :to_a + [:k] + end + + ScratchPad.record [] + @b[:k] = 10 + (@b[*k] &&= 20).should == 20 + @b[:k].should == 20 + ScratchPad.recorded.should == [:to_a] + end + + it 'correctly handles a nested splatted argument' do + @b[:k] = 10 + (@b[*[*[:k]]] &&= 20).should == 20 + @b[:k].should == 20 + end + + it 'correctly handles multiple nested splatted arguments' do + klass_with_multiple_parameters = Class.new do + def [](k1, k2, k3) + @hash ||= {} + @hash[:"#{k1}#{k2}#{k3}"] + end + + def []=(k1, k2, k3, v) + @hash ||= {} + @hash[:"#{k1}#{k2}#{k3}"] = v + 7 + end + end + a = klass_with_multiple_parameters.new + + a[:a, :b, :c] = 10 + (a[*[:a], *[:b], *[:c]] &&= 20).should == 20 + a[:a, :b, :c].should == 20 + end + end end end @@ -434,7 +663,7 @@ describe 'Optional constant assignment' do ConstantSpecs::ClassA::OR_ASSIGNED_CONSTANT2.should == :assigned end - it 'causes side-effects of the module part to be applied (for nil constant)' do + it 'causes side-effects of the module part to be applied only once (for nil constant)' do suppress_warning do # already initialized constant ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT2 = nil x = 0 @@ -492,5 +721,20 @@ describe 'Optional constant assignment' do ConstantSpecs::OpAssignFalse.should == false ConstantSpecs.send :remove_const, :OpAssignFalse end + + it 'causes side-effects of the module part to be applied only once (when assigns)' do + module ConstantSpecs + OpAssignTrue = true + end + + suppress_warning do # already initialized constant + x = 0 + (x += 1; ConstantSpecs)::OpAssignTrue &&= :assigned + x.should == 1 + ConstantSpecs::OpAssignTrue.should == :assigned + end + + ConstantSpecs.send :remove_const, :OpAssignTrue + end end end |