summaryrefslogtreecommitdiff
path: root/spec/ruby/language/assignments_spec.rb
blob: 005c1f0dc3f9281010fd7c4d8cd4c67210178fa4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
require_relative '../spec_helper'

# Should be synchronized with spec/ruby/language/optional_assignments_spec.rb
describe 'Assignments' do
  describe 'using +=' do
    describe 'using an accessor' do
      before do
        klass = Class.new { attr_accessor :b }
        @a    = klass.new
      end

      it 'does evaluate receiver only once when assigns' do
        ScratchPad.record []
        @a.b = 1

        (ScratchPad << :evaluated; @a).b += 2

        ScratchPad.recorded.should == [:evaluated]
        @a.b.should == 3
      end

      it 'ignores method visibility when receiver is self' do
        klass_with_private_methods = Class.new do
          def initialize(n) @a = n end
          def public_method(n); self.a += n end
          private
          def a; @a end
          def a=(n) @a = n; 42 end
        end

        a = klass_with_private_methods.new(0)
        a.public_method(2).should == 2
      end
    end

    describe 'using a #[]' do
      before do
        klass = Class.new do
          def [](k)
            @hash ||= {}
            @hash[k]
          end

          def []=(k, v)
            @hash ||= {}
            @hash[k] = v
            7
          end
        end
        @b = klass.new
      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 == 3
      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, n); self[k] += n end
          private
          def [](k) @a[k] end
          def []=(k, v) @a[k] = v; 42 end
        end

        a = klass_with_private_methods.new(k: 0)
        a.public_method(:k, 2).should == 2
      end

      context 'splatted argument' do
        it 'correctly handles it' do
          @b[:m] = 10
          (@b[*[:m]] += 10).should == 20
          @b[:m].should == 20

          @b[:n] = 10
          (@b[*(1; [:n])] += 10).should == 20
          @b[:n].should == 20

          @b[:k] = 10
          (@b[*begin 1; [:k] end] += 10).should == 20
          @b[:k].should == 20
        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] += 10).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]]] += 10).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]] += 10).should == 20
          a[:a, :b, :c].should == 20
        end
      end
    end

    describe 'using compounded constants' do
      it 'causes side-effects of the module part to be applied only once (when assigns)' do
        module ConstantSpecs
          OpAssignTrue = 1
        end

        suppress_warning do # already initialized constant
          x = 0
          (x += 1; ConstantSpecs)::OpAssignTrue += 2
          x.should == 1
          ConstantSpecs::OpAssignTrue.should == 3
        end

        ConstantSpecs.send :remove_const, :OpAssignTrue
      end
    end
  end
end