summaryrefslogtreecommitdiff
path: root/spec/ruby/core/module/ruby2_keywords_spec.rb
blob: 6de3fdec801087a7d46a6b72391d0be8e5743959 (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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
require_relative '../../spec_helper'
require_relative 'fixtures/classes'

ruby_version_is "2.7" do
  describe "Module#ruby2_keywords" do
    it "marks the final hash argument as keyword hash" do
      obj = Object.new

      obj.singleton_class.class_exec do
        def foo(*a) a.last end
        ruby2_keywords :foo
      end

      last = obj.foo(1, 2, a: "a")
      Hash.ruby2_keywords_hash?(last).should == true
    end

    it "makes a copy of the hash and only marks the copy as keyword hash" do
      obj = Object.new
      obj.singleton_class.class_exec do
        def regular(*args)
          args.last
        end

        ruby2_keywords def foo(*args)
          args.last
        end
      end

      h = {a: 1}
      ruby_version_is "3.0" do
        obj.regular(**h).should.equal?(h)
      end

      last = obj.foo(**h)
      Hash.ruby2_keywords_hash?(last).should == true
      Hash.ruby2_keywords_hash?(h).should == false

      last2 = obj.foo(**last) # last is already marked
      Hash.ruby2_keywords_hash?(last2).should == true
      Hash.ruby2_keywords_hash?(last).should == true
      last2.should_not.equal?(last)
      Hash.ruby2_keywords_hash?(h).should == false
    end

    it "makes a copy and unmark at the call site when calling with marked *args" do
      obj = Object.new
      obj.singleton_class.class_exec do
        ruby2_keywords def foo(*args)
          args
        end

        def single(arg)
          arg
        end

        def splat(*args)
          args.last
        end

        def kwargs(**kw)
          kw
        end
      end

      h = { a: 1 }
      args = obj.foo(**h)
      marked = args.last
      Hash.ruby2_keywords_hash?(marked).should == true

      after_usage = obj.single(*args)
      after_usage.should == h
      after_usage.should_not.equal?(h)
      after_usage.should_not.equal?(marked)
      Hash.ruby2_keywords_hash?(after_usage).should == false
      Hash.ruby2_keywords_hash?(marked).should == true

      after_usage = obj.splat(*args)
      after_usage.should == h
      after_usage.should_not.equal?(h)
      after_usage.should_not.equal?(marked)
      ruby_bug "#18625", ""..."3.3" do # might be fixed in 3.2
        Hash.ruby2_keywords_hash?(after_usage).should == false
      end
      Hash.ruby2_keywords_hash?(marked).should == true

      after_usage = obj.kwargs(*args)
      after_usage.should == h
      after_usage.should_not.equal?(h)
      after_usage.should_not.equal?(marked)
      Hash.ruby2_keywords_hash?(after_usage).should == false
      Hash.ruby2_keywords_hash?(marked).should == true
    end

    it "applies to the underlying method and applies across aliasing" do
      obj = Object.new

      obj.singleton_class.class_exec do
        def foo(*a) a.last end
        alias_method :bar, :foo
        ruby2_keywords :foo

        def baz(*a) a.last end
        ruby2_keywords :baz
        alias_method :bob, :baz
      end

      last = obj.foo(1, 2, a: "a")
      Hash.ruby2_keywords_hash?(last).should == true

      last = obj.bar(1, 2, a: "a")
      Hash.ruby2_keywords_hash?(last).should == true

      last = obj.baz(1, 2, a: "a")
      Hash.ruby2_keywords_hash?(last).should == true

      last = obj.bob(1, 2, a: "a")
      Hash.ruby2_keywords_hash?(last).should == true
    end

    ruby_version_is "2.7" ... "3.0" do
      it "fixes delegation warnings when calling a method accepting keywords" do
        obj = Object.new

        obj.singleton_class.class_exec do
          def foo(*a) bar(*a) end
          def bar(*a, **b) end
        end

        -> { obj.foo(1, 2, {a: "a"}) }.should complain(/Using the last argument as keyword parameters is deprecated/)

        obj.singleton_class.class_exec do
          ruby2_keywords :foo
        end

        -> { obj.foo(1, 2, {a: "a"}) }.should_not complain
      end
    end

    it "returns nil" do
      obj = Object.new

      obj.singleton_class.class_exec do
        def foo(*a) end

        ruby2_keywords(:foo).should == nil
      end
    end

    it "raises NameError when passed not existing method name" do
      obj = Object.new

      -> {
        obj.singleton_class.class_exec do
          ruby2_keywords :not_existing
        end
      }.should raise_error(NameError, /undefined method `not_existing'/)
    end

    it "accepts String as well" do
      obj = Object.new

      obj.singleton_class.class_exec do
        def foo(*a) a.last end
        ruby2_keywords "foo"
      end

      last = obj.foo(1, 2, a: "a")
      Hash.ruby2_keywords_hash?(last).should == true
    end

    it "raises TypeError when passed not Symbol or String" do
      obj = Object.new

      -> {
        obj.singleton_class.class_exec do
          ruby2_keywords Object.new
        end
      }.should raise_error(TypeError, /is not a symbol nor a string/)
    end

    it "prints warning when a method does not accept argument splat" do
      obj = Object.new
      def obj.foo(a, b, c) end

      -> {
        obj.singleton_class.class_exec do
          ruby2_keywords :foo
        end
      }.should complain(/Skipping set of ruby2_keywords flag for/)
    end

    it "prints warning when a method accepts keywords" do
      obj = Object.new
      def obj.foo(a:, b:) end

      -> {
        obj.singleton_class.class_exec do
          ruby2_keywords :foo
        end
      }.should complain(/Skipping set of ruby2_keywords flag for/)
    end

    it "prints warning when a method accepts keyword splat" do
      obj = Object.new
      def obj.foo(**a) end

      -> {
        obj.singleton_class.class_exec do
          ruby2_keywords :foo
        end
      }.should complain(/Skipping set of ruby2_keywords flag for/)
    end
  end
end