type_toolkit 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +45 -1
- data/benchmark/.rubocop.yml +7 -0
- data/benchmark/abstract_methods_benchmark.rb +221 -0
- data/benchmark/interface_startup_performance.rb +128 -0
- data/benchmark/module_benchmark.rb +62 -0
- data/config/default.yml +5 -0
- data/docs/design/inherited_implementation_problem.md +119 -0
- data/docs/design/self_dot_methods.md +79 -0
- data/lib/rubocop/cop/type_toolkit/plugin.rb +2 -1
- data/lib/rubocop/cop/type_toolkit/prefer_not_nil.rb +96 -0
- data/lib/rubocop-type_toolkit.rb +1 -0
- data/lib/type_toolkit/abstract_method_receiver.rb +39 -0
- data/lib/type_toolkit/dsl.rb +64 -0
- data/lib/type_toolkit/ext/method.rb +11 -0
- data/lib/type_toolkit/ext/module.rb +7 -0
- data/lib/type_toolkit/ext/nil_assertions.rb +3 -1
- data/lib/type_toolkit/has_abstract_methods.rb +108 -0
- data/lib/type_toolkit/interface.rb +38 -0
- data/lib/type_toolkit/method_def_recorder.rb +53 -0
- data/lib/type_toolkit/method_patch.rb +16 -0
- data/lib/type_toolkit/version.rb +1 -1
- data/lib/type_toolkit.rb +3 -0
- data/sorbet/config +3 -1
- data/sorbet/rbi/gems/benchmark-ips@2.14.0.rbi +981 -0
- data/sorbet/rbi/gems/{erb@6.0.1.rbi → erb@6.0.1.1.rbi} +2 -2
- data/sorbet/rbi/gems/{json@2.18.1.rbi → json@2.19.9.rbi} +115 -161
- data/sorbet/rbi/gems/minitest@5.27.0.rbi +707 -0
- data/sorbet/rbi/gems/rexml@3.4.4.rbi +0 -167
- data/sorbet/rbi/gems/rubocop-ast@1.49.0.rbi +6 -0
- data/sorbet/rbi/gems/rubocop@1.84.2.rbi +7 -0
- data/sorbet/rbi/gems/tapioca@0.17.10.rbi +1 -0
- data/sorbet/rbi/gems/{yard@0.9.38.rbi → yard@0.9.44.rbi} +1206 -241
- data/sorbet/rbi/shims/core.rbi +19 -0
- data/sorbet/rbi/shims/minitest.rbi +5 -2
- data/spec/interface_spec.rb +405 -0
- data/spec/rubocop/cop/type_toolkit/prefer_not_nil_spec.rb +229 -0
- data/spec/spec_helper.rb +14 -0
- metadata +23 -4
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# A shim module representing (a subset of) the common interface between Method and UnboundMethod.
|
|
5
|
+
module MethodOrUnboundMethod
|
|
6
|
+
sig { returns(T::Module[T.untyped]) }
|
|
7
|
+
def owner; end
|
|
8
|
+
|
|
9
|
+
sig { returns(Symbol) }
|
|
10
|
+
def name; end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class Method
|
|
14
|
+
include MethodOrUnboundMethod
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class UnboundMethod
|
|
18
|
+
include MethodOrUnboundMethod
|
|
19
|
+
end
|
|
@@ -35,9 +35,12 @@ module Minitest
|
|
|
35
35
|
def it(desc = T.unsafe(nil), &block); end
|
|
36
36
|
|
|
37
37
|
sig do
|
|
38
|
-
params(
|
|
38
|
+
params(
|
|
39
|
+
type: T.anything,
|
|
40
|
+
block: T.proc.bind(T.attached_class).void,
|
|
41
|
+
).void
|
|
39
42
|
end
|
|
40
|
-
def before(&block); end
|
|
43
|
+
def before(type = T.unsafe(nil), &block); end
|
|
41
44
|
end
|
|
42
45
|
end
|
|
43
46
|
end
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
# typed: ignore
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "spec_helper"
|
|
5
|
+
|
|
6
|
+
module TypeToolkit
|
|
7
|
+
class InterfaceSpec < Minitest::Spec
|
|
8
|
+
module SimpleInterface
|
|
9
|
+
interface!
|
|
10
|
+
|
|
11
|
+
abstract def m1 = assert_never_called!
|
|
12
|
+
abstract def m2 = assert_never_called!
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# A normal class that's completely unrelated `SimpleInterface`.
|
|
16
|
+
class NonImpl
|
|
17
|
+
def m1 = "NonImpl#m1"
|
|
18
|
+
# Does not implement `m2`
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# A class that implements some but not all of the SimpleInterface's abstract methods.
|
|
22
|
+
class PartialImpl
|
|
23
|
+
include SimpleInterface
|
|
24
|
+
|
|
25
|
+
def m1 = "PartialImpl#m1"
|
|
26
|
+
# Does not provide an implementation for `m2`
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# A class that implements all of the SimpleInterface's abstract methods.
|
|
30
|
+
class FullImpl
|
|
31
|
+
include SimpleInterface
|
|
32
|
+
|
|
33
|
+
def initialize
|
|
34
|
+
@a = 1
|
|
35
|
+
@b = 2
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def m1 = "FullImpl#m1"
|
|
39
|
+
def m2 = "FullImpl#m2"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# A class that provides a partial implementation of `SimpleInterface` for `PartiallyInheritsItsImpl`.
|
|
43
|
+
class PartialParent
|
|
44
|
+
def m1 = "PartialParent#m1"
|
|
45
|
+
# Does not implement `m2`
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# A class that implements all of the SimpleInterface's abstract methods, some via inheritance, and some via direct implementation.
|
|
49
|
+
class PartiallyInheritsItsImpl < PartialParent
|
|
50
|
+
include SimpleInterface
|
|
51
|
+
|
|
52
|
+
def m2 = "PartiallyInheritsItsImpl#m2"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
describe "An interface" do
|
|
56
|
+
describe ".abstract macro" do
|
|
57
|
+
it "returns the method name" do
|
|
58
|
+
test_context = self
|
|
59
|
+
|
|
60
|
+
Module.new do
|
|
61
|
+
interface!
|
|
62
|
+
|
|
63
|
+
test_context.assert_equal(:the_method_name, abstract(def the_method_name; end))
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "raises when it sees a different method name than what MethodDefRecorder recorded" do
|
|
68
|
+
error = assert_raises(RuntimeError) do
|
|
69
|
+
Module.new do
|
|
70
|
+
interface!
|
|
71
|
+
|
|
72
|
+
# Simulate a conflicting `method_added` call by sneaking in a method definition
|
|
73
|
+
# between the `def` and the `abstract` call.
|
|
74
|
+
def m1; end
|
|
75
|
+
|
|
76
|
+
def something_else; end
|
|
77
|
+
|
|
78
|
+
abstract(:m1)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Do not rely on this message content! Its content is subject to change!
|
|
83
|
+
assert_equal <<~EXPECTED.chomp, error.message
|
|
84
|
+
`abstract` expected to see `#m1`, but the last recorded method was called `something_else`.
|
|
85
|
+
This can happen when `abstract` is combined with other metaprogramming.
|
|
86
|
+
If you think this is a bug, please open an issue: https://github.com/Shopify/type_toolkit/issues
|
|
87
|
+
EXPECTED
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
describe ".declared_abstract_instance_methods" do
|
|
92
|
+
it "contains all declared abstract methods" do
|
|
93
|
+
assert_equal [:m1, :m2], SimpleInterface.declared_abstract_instance_methods
|
|
94
|
+
assert_equal [:m1, :m2], SimpleInterface.declared_abstract_instance_methods(true)
|
|
95
|
+
assert_equal [:m1, :m2], SimpleInterface.declared_abstract_instance_methods(false)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
describe ".abstract_instance_methods" do
|
|
100
|
+
it "only contains unimplemented abstract methods" do
|
|
101
|
+
assert_equal [:m1, :m2], SimpleInterface.abstract_instance_methods
|
|
102
|
+
assert_equal [:m1, :m2], SimpleInterface.abstract_instance_methods(true)
|
|
103
|
+
assert_equal [:m1, :m2], SimpleInterface.abstract_instance_methods(false)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
describe ".abstract_method?" do
|
|
108
|
+
it "returns true for abstract methods" do
|
|
109
|
+
assert SimpleInterface.abstract_method?(:m1)
|
|
110
|
+
assert SimpleInterface.abstract_method?(:m2)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it "returns false for non-abstract methods" do
|
|
114
|
+
refute SimpleInterface.abstract_method?(:inspect)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
describe ".abstract_method_declared?" do
|
|
119
|
+
it "returns true for abstract methods" do
|
|
120
|
+
assert SimpleInterface.abstract_method_declared?(:m1)
|
|
121
|
+
assert SimpleInterface.abstract_method_declared?(:m2)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
describe "A class that does not implement the interface" do
|
|
127
|
+
describe "a method with the same name as another interface's members" do
|
|
128
|
+
# These tests sanity-check that our runtime trickery didn't accidentally change the
|
|
129
|
+
# normal behaviour of method calling and `method_missing` for unrelated classes.
|
|
130
|
+
|
|
131
|
+
before do
|
|
132
|
+
@class = NonImpl
|
|
133
|
+
@x = NonImpl.new
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it "#respond_to? returns true" do
|
|
137
|
+
assert_respond_to @x, :m1
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it "can be called like normal" do
|
|
141
|
+
assert_equal "NonImpl#m1", @x.m1
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
describe "a Method object for it" do
|
|
145
|
+
it "can be called like normal" do
|
|
146
|
+
assert_equal "NonImpl#m1", @x.method(:m1).call
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it "is not abstract" do
|
|
150
|
+
refute_predicate @x.method(:m1), :abstract?
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
describe "an UnboundMethod object for it" do
|
|
155
|
+
before { @um = @x.method(:m1).unbind }
|
|
156
|
+
it "can be called like normal" do
|
|
157
|
+
assert_equal "NonImpl#m1", @um.bind_call(@x)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it "is not abstract" do
|
|
161
|
+
refute_predicate @um, :abstract?
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it "calls a defined method like normal" do
|
|
166
|
+
assert_respond_to @x, :m1
|
|
167
|
+
assert_equal "NonImpl#m1", @x.m1
|
|
168
|
+
assert_equal "NonImpl#m1", @x.method(:m1).call
|
|
169
|
+
refute_predicate @x.method(:m1), :abstract?
|
|
170
|
+
refute_predicate @x.method(:m1).unbind, :abstract?
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it "raises NoMethodError for an undefined method like normal" do
|
|
174
|
+
refute_respond_to @x, :m2
|
|
175
|
+
assert_raises(NoMethodError) { @x.m2 }
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it "does not respond to .abstract_method?" do
|
|
180
|
+
refute_respond_to @class, :abstract_method?
|
|
181
|
+
assert_raises(NoMethodError) { @class.abstract_method?(:m1) }
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it "does not respond to .abstract_method_declared?" do
|
|
185
|
+
refute_respond_to @class, :abstract_method_declared?
|
|
186
|
+
assert_raises(NoMethodError) { @class.abstract_method_declared?(:m1) }
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it "does not respond to .declared_abstract_instance_methods" do
|
|
190
|
+
refute_respond_to @class, :declared_abstract_instance_methods
|
|
191
|
+
assert_raises(NoMethodError) { @class.declared_abstract_instance_methods }
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
it "does not respond to .abstract_instance_methods" do
|
|
195
|
+
refute_respond_to @class, :abstract_instance_methods
|
|
196
|
+
assert_raises(NoMethodError) { @class.abstract_instance_methods }
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
describe "A class that partially implements the interface" do
|
|
201
|
+
before do
|
|
202
|
+
@class = PartialImpl
|
|
203
|
+
@x = PartialImpl.new
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
describe "calling an implemented abstract method" do
|
|
207
|
+
it "calls the concrete implementation" do
|
|
208
|
+
assert_respond_to @x, :m1
|
|
209
|
+
assert_equal "PartialImpl#m1", @x.m1
|
|
210
|
+
assert_equal "PartialImpl#m1", @x.method(:m1).call
|
|
211
|
+
refute_predicate @x.method(:m1), :abstract?
|
|
212
|
+
refute_predicate @x.method(:m1).unbind, :abstract?
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
describe "calling an unimplemented abstract method" do
|
|
217
|
+
it "raises AbstractMethodNotImplementedError" do
|
|
218
|
+
assert_respond_to @x, :m2
|
|
219
|
+
|
|
220
|
+
# Notice it's not `NoMethodError`, so we can give a better error message.
|
|
221
|
+
e = assert_abstract { @x.m2 }
|
|
222
|
+
|
|
223
|
+
# Do not rely on this message content! Its content is subject to change!
|
|
224
|
+
# We only test it to ensure it's formatted correctly.
|
|
225
|
+
assert_equal "Abstract method `#m2` was never implemented.", e.message
|
|
226
|
+
|
|
227
|
+
m2 = @x.method(:m2)
|
|
228
|
+
assert_kind_of Method, m2
|
|
229
|
+
assert_predicate m2, :abstract?
|
|
230
|
+
assert_predicate m2.unbind, :abstract?
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
describe "calling a non-abstract method" do
|
|
235
|
+
it "calls the concrete implementation" do
|
|
236
|
+
assert_respond_to @x, :inspect
|
|
237
|
+
assert_kind_of String, @x.inspect
|
|
238
|
+
assert_kind_of String, @x.method(:inspect).call
|
|
239
|
+
refute_predicate @x.method(:inspect), :abstract?
|
|
240
|
+
refute_predicate @x.method(:inspect).unbind, :abstract?
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
describe ".abstract_method?" do
|
|
245
|
+
it "returns false for abstract methods that have been implemented" do
|
|
246
|
+
refute @class.abstract_method?(:m1)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
it "returns true for abstract methods that have not been implemented" do
|
|
250
|
+
assert @class.abstract_method?(:m2)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
it "returns false for non-abstract methods" do
|
|
254
|
+
refute @class.abstract_method?(:inspect)
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
describe ".abstract_method_declared?" do
|
|
259
|
+
it "is true for all abstract methods" do
|
|
260
|
+
assert @class.abstract_method_declared?(:m1) # Even the one that's been implemented
|
|
261
|
+
assert @class.abstract_method_declared?(:m2)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
it "is false for non-abstract methods" do
|
|
265
|
+
assert @class.public_method_defined?(:inspect) # precondition
|
|
266
|
+
refute @class.abstract_method_declared?(:inspect)
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
describe ".declared_abstract_instance_methods" do
|
|
271
|
+
it "returns all declared abstract methods, even those that have been implemented" do
|
|
272
|
+
assert_equal [:m1, :m2], @class.declared_abstract_instance_methods
|
|
273
|
+
assert_equal [:m1, :m2], @class.declared_abstract_instance_methods(true)
|
|
274
|
+
assert_equal [], @class.declared_abstract_instance_methods(false)
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
describe ".abstract_instance_methods" do
|
|
279
|
+
it "returns only unimplemented abstract methods" do
|
|
280
|
+
assert_equal [:m2], @class.abstract_instance_methods
|
|
281
|
+
assert_equal [:m2], @class.abstract_instance_methods(true)
|
|
282
|
+
assert_equal [], @class.abstract_instance_methods(false)
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
describe "A class that fully implements the interface" do
|
|
288
|
+
before do
|
|
289
|
+
@class = FullImpl
|
|
290
|
+
@x = FullImpl.new
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
describe "calling an implemented abstract method" do
|
|
294
|
+
it "calls the concrete implementation" do
|
|
295
|
+
assert_respond_to @x, :m1
|
|
296
|
+
assert_equal "FullImpl#m1", @x.m1
|
|
297
|
+
assert_equal "FullImpl#m1", @x.method(:m1).call
|
|
298
|
+
refute_predicate @x.method(:m1), :abstract?
|
|
299
|
+
refute_predicate @x.method(:m1).unbind, :abstract?
|
|
300
|
+
|
|
301
|
+
assert_respond_to @x, :m2
|
|
302
|
+
assert_equal "FullImpl#m2", @x.m2
|
|
303
|
+
assert_equal "FullImpl#m2", @x.method(:m2).call
|
|
304
|
+
refute_predicate @x.method(:m2), :abstract?
|
|
305
|
+
refute_predicate @x.method(:m2).unbind, :abstract?
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
describe ".abstract_method?" do
|
|
310
|
+
it "returns false for abstract methods that have been implemented" do
|
|
311
|
+
refute @class.abstract_method?(:m1)
|
|
312
|
+
refute @class.abstract_method?(:m2)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
it "returns false for non-abstract methods" do
|
|
316
|
+
refute @class.abstract_method?(:inspect)
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
describe ".abstract_method_declared?" do
|
|
321
|
+
it "returns true for all abstract methods" do
|
|
322
|
+
assert @class.abstract_method_declared?(:m1)
|
|
323
|
+
assert @class.abstract_method_declared?(:m2)
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
describe ".declared_abstract_instance_methods" do
|
|
328
|
+
it "returns all declared abstract methods, even those that have been implemented" do
|
|
329
|
+
assert_equal [:m1, :m2], @class.declared_abstract_instance_methods
|
|
330
|
+
assert_equal [:m1, :m2], @class.declared_abstract_instance_methods(true)
|
|
331
|
+
assert_equal [], @class.declared_abstract_instance_methods(false)
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
describe ".abstract_instance_methods" do
|
|
336
|
+
it "returns only unimplemented abstract methods" do
|
|
337
|
+
assert_equal [], @class.abstract_instance_methods
|
|
338
|
+
assert_equal [], @class.abstract_instance_methods(true)
|
|
339
|
+
assert_equal [], @class.abstract_instance_methods(false)
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
describe "A class that fully implements the interface, partially via inheritance" do
|
|
345
|
+
before do
|
|
346
|
+
@class = PartiallyInheritsItsImpl
|
|
347
|
+
@x = PartiallyInheritsItsImpl.new
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
describe "calling an abstract method with an inherited implementation" do
|
|
351
|
+
it "calls the inherited implementation" do
|
|
352
|
+
assert_respond_to @x, :m1
|
|
353
|
+
assert_equal "PartialParent#m1", @x.m1
|
|
354
|
+
assert_equal "PartialParent#m1", @x.method(:m1).call
|
|
355
|
+
refute_predicate @x.method(:m1), :abstract?
|
|
356
|
+
refute_predicate @x.method(:m1).unbind, :abstract?
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
describe "calling an abstract method with defined in the child implementation" do
|
|
361
|
+
it "calls the child implementation" do
|
|
362
|
+
assert_respond_to @x, :m2
|
|
363
|
+
assert_equal "PartiallyInheritsItsImpl#m2", @x.m2
|
|
364
|
+
assert_equal "PartiallyInheritsItsImpl#m2", @x.method(:m2).call
|
|
365
|
+
refute_predicate @x.method(:m2), :abstract?
|
|
366
|
+
refute_predicate @x.method(:m2).unbind, :abstract?
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
describe ".abstract_method?" do
|
|
371
|
+
it "returns false for abstract methods that have been implemented" do
|
|
372
|
+
refute @class.abstract_method?(:m1)
|
|
373
|
+
refute @class.abstract_method?(:m2)
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
it "returns false for non-abstract methods" do
|
|
377
|
+
refute @class.abstract_method?(:inspect)
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
describe ".abstract_method_declared?" do
|
|
382
|
+
it "returns true for all abstract methods" do
|
|
383
|
+
assert @class.abstract_method_declared?(:m1)
|
|
384
|
+
assert @class.abstract_method_declared?(:m2)
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
describe ".declared_abstract_instance_methods" do
|
|
389
|
+
it "returns all declared abstract methods, even those that have been implemented" do
|
|
390
|
+
assert_equal [:m1, :m2], @class.declared_abstract_instance_methods
|
|
391
|
+
assert_equal [:m1, :m2], @class.declared_abstract_instance_methods(true)
|
|
392
|
+
assert_equal [], @class.declared_abstract_instance_methods(false)
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
describe ".abstract_instance_methods" do
|
|
397
|
+
it "returns only unimplemented abstract methods" do
|
|
398
|
+
assert_equal [], @class.abstract_instance_methods
|
|
399
|
+
assert_equal [], @class.abstract_instance_methods(true)
|
|
400
|
+
assert_equal [], @class.abstract_instance_methods(false)
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
end
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "spec_helper"
|
|
5
|
+
require "rubocop"
|
|
6
|
+
require "rubocop/minitest/assert_offense"
|
|
7
|
+
require "rubocop-type_toolkit"
|
|
8
|
+
|
|
9
|
+
module RuboCop
|
|
10
|
+
module Cop
|
|
11
|
+
module TypeToolkit
|
|
12
|
+
class PreferNotNilSpec < ::Minitest::Spec
|
|
13
|
+
include RuboCop::Minitest::AssertOffense
|
|
14
|
+
|
|
15
|
+
MSG = "TypeToolkit/PreferNotNil: Use `.not_nil!` instead of `T.must()`."
|
|
16
|
+
|
|
17
|
+
before do
|
|
18
|
+
@cop = PreferNotNil.new
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "autocorrects T.must" do
|
|
22
|
+
assert_offense(<<~RUBY)
|
|
23
|
+
value = T.must(foo)
|
|
24
|
+
^^^^^^^^^^^ #{MSG}
|
|
25
|
+
RUBY
|
|
26
|
+
|
|
27
|
+
assert_correction(<<~RUBY)
|
|
28
|
+
value = foo.not_nil!
|
|
29
|
+
RUBY
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "autocorrects ::T.must" do
|
|
33
|
+
assert_offense(<<~RUBY)
|
|
34
|
+
value = ::T.must(foo)
|
|
35
|
+
^^^^^^^^^^^^^ #{MSG}
|
|
36
|
+
RUBY
|
|
37
|
+
|
|
38
|
+
assert_correction(<<~RUBY)
|
|
39
|
+
value = foo.not_nil!
|
|
40
|
+
RUBY
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "autocorrects inside string interpolation" do
|
|
44
|
+
assert_offense(<<~RUBY)
|
|
45
|
+
string = "\#{T.must(foo)}"
|
|
46
|
+
^^^^^^^^^^^ #{MSG}
|
|
47
|
+
RUBY
|
|
48
|
+
|
|
49
|
+
assert_correction(<<~RUBY)
|
|
50
|
+
string = "\#{foo.not_nil!}"
|
|
51
|
+
RUBY
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "preserves the precedence of conditional and range expressions" do
|
|
55
|
+
assert_offense(<<~RUBY)
|
|
56
|
+
conditional = T.must(condition ? foo : bar)
|
|
57
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{MSG}
|
|
58
|
+
range = T.must(foo..bar)
|
|
59
|
+
^^^^^^^^^^^^^^^^ #{MSG}
|
|
60
|
+
RUBY
|
|
61
|
+
|
|
62
|
+
assert_correction(<<~RUBY)
|
|
63
|
+
conditional = (condition ? foo : bar).not_nil!
|
|
64
|
+
range = (foo..bar).not_nil!
|
|
65
|
+
RUBY
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "parenthesizes every expression that requires it" do
|
|
69
|
+
assert_offense(<<~RUBY)
|
|
70
|
+
operator = T.must(foo + bar)
|
|
71
|
+
^^^^^^^^^^^^^^^^^ #{MSG}
|
|
72
|
+
logical = T.must(foo || bar)
|
|
73
|
+
^^^^^^^^^^^^^^^^^^ #{MSG}
|
|
74
|
+
grouped = T.must((foo || bar))
|
|
75
|
+
^^^^^^^^^^^^^^^^^^^^ #{MSG}
|
|
76
|
+
assignment = T.must(foo = bar)
|
|
77
|
+
^^^^^^^^^^^^^^^^^ #{MSG}
|
|
78
|
+
block_value = T.must(foo { bar })
|
|
79
|
+
^^^^^^^^^^^^^^^^^^^ #{MSG}
|
|
80
|
+
defined_value = T.must(defined?(foo))
|
|
81
|
+
^^^^^^^^^^^^^^^^^^^^^ #{MSG}
|
|
82
|
+
def example
|
|
83
|
+
T.must(yield foo)
|
|
84
|
+
^^^^^^^^^^^^^^^^^ #{MSG}
|
|
85
|
+
end
|
|
86
|
+
def implicit_super
|
|
87
|
+
T.must(super)
|
|
88
|
+
^^^^^^^^^^^^^ #{MSG}
|
|
89
|
+
end
|
|
90
|
+
def explicit_super
|
|
91
|
+
T.must(super(foo))
|
|
92
|
+
^^^^^^^^^^^^^^^^^^ #{MSG}
|
|
93
|
+
end
|
|
94
|
+
RUBY
|
|
95
|
+
|
|
96
|
+
assert_correction(<<~RUBY)
|
|
97
|
+
operator = (foo + bar).not_nil!
|
|
98
|
+
logical = (foo || bar).not_nil!
|
|
99
|
+
grouped = (foo || bar).not_nil!
|
|
100
|
+
assignment = (foo = bar).not_nil!
|
|
101
|
+
block_value = (foo { bar }).not_nil!
|
|
102
|
+
defined_value = (defined?(foo)).not_nil!
|
|
103
|
+
def example
|
|
104
|
+
(yield foo).not_nil!
|
|
105
|
+
end
|
|
106
|
+
def implicit_super
|
|
107
|
+
(super).not_nil!
|
|
108
|
+
end
|
|
109
|
+
def explicit_super
|
|
110
|
+
(super(foo)).not_nil!
|
|
111
|
+
end
|
|
112
|
+
RUBY
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it "preserves the precedence of command calls" do
|
|
116
|
+
assert_offense(<<~RUBY)
|
|
117
|
+
value = T.must(fetch value)
|
|
118
|
+
^^^^^^^^^^^^^^^^^^^ #{MSG}
|
|
119
|
+
RUBY
|
|
120
|
+
|
|
121
|
+
assert_correction(<<~RUBY)
|
|
122
|
+
value = (fetch value).not_nil!
|
|
123
|
+
RUBY
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it "does not add unnecessary parentheses to parenthesized calls" do
|
|
127
|
+
assert_offense(<<~RUBY)
|
|
128
|
+
value = T.must(fetch(value))
|
|
129
|
+
^^^^^^^^^^^^^^^^^^^^ #{MSG}
|
|
130
|
+
RUBY
|
|
131
|
+
|
|
132
|
+
assert_correction(<<~RUBY)
|
|
133
|
+
value = fetch(value).not_nil!
|
|
134
|
+
RUBY
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it "preserves comments in multiline calls" do
|
|
138
|
+
assert_offense(<<~RUBY)
|
|
139
|
+
value = T.must(
|
|
140
|
+
^^^^^^^ #{MSG}
|
|
141
|
+
# Proven non-nil by validation.
|
|
142
|
+
foo,
|
|
143
|
+
)
|
|
144
|
+
RUBY
|
|
145
|
+
|
|
146
|
+
assert_correction(<<~RUBY)
|
|
147
|
+
value = (
|
|
148
|
+
# Proven non-nil by validation.
|
|
149
|
+
foo
|
|
150
|
+
).not_nil!
|
|
151
|
+
RUBY
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it "autocorrects multiline calls with whitespace before the method" do
|
|
155
|
+
assert_offense(<<~RUBY)
|
|
156
|
+
first = T .must(
|
|
157
|
+
^^^^^^^^ #{MSG}
|
|
158
|
+
foo,
|
|
159
|
+
)
|
|
160
|
+
second = T
|
|
161
|
+
^ #{MSG}
|
|
162
|
+
.must(
|
|
163
|
+
bar,
|
|
164
|
+
)
|
|
165
|
+
RUBY
|
|
166
|
+
|
|
167
|
+
assert_correction(<<~RUBY)
|
|
168
|
+
first = (
|
|
169
|
+
foo
|
|
170
|
+
).not_nil!
|
|
171
|
+
second = (
|
|
172
|
+
bar
|
|
173
|
+
).not_nil!
|
|
174
|
+
RUBY
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
it "removes a trailing comma before an inline comment" do
|
|
178
|
+
assert_offense(<<~RUBY)
|
|
179
|
+
value = T.must(
|
|
180
|
+
^^^^^^^ #{MSG}
|
|
181
|
+
foo, # Proven non-nil.
|
|
182
|
+
)
|
|
183
|
+
RUBY
|
|
184
|
+
|
|
185
|
+
assert_correction(<<~RUBY)
|
|
186
|
+
value = (
|
|
187
|
+
foo # Proven non-nil.
|
|
188
|
+
).not_nil!
|
|
189
|
+
RUBY
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
it "autocorrects nested T.must calls" do
|
|
193
|
+
assert_offense(<<~RUBY)
|
|
194
|
+
value = T.must(T.must(foo).bar)
|
|
195
|
+
^^^^^^^^^^^^^^^^^^^^^^^ #{MSG}
|
|
196
|
+
^^^^^^^^^^^ #{MSG}
|
|
197
|
+
RUBY
|
|
198
|
+
|
|
199
|
+
assert_correction(<<~RUBY)
|
|
200
|
+
value = foo.not_nil!.bar.not_nil!
|
|
201
|
+
RUBY
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
it "autocorrects three nested T.must calls" do
|
|
205
|
+
assert_offense(<<~RUBY)
|
|
206
|
+
value = T.must(T.must(T.must(foo).bar).baz)
|
|
207
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{MSG}
|
|
208
|
+
^^^^^^^^^^^^^^^^^^^^^^^ #{MSG}
|
|
209
|
+
^^^^^^^^^^^ #{MSG}
|
|
210
|
+
RUBY
|
|
211
|
+
|
|
212
|
+
assert_correction(<<~RUBY)
|
|
213
|
+
value = foo.not_nil!.bar.not_nil!.baz.not_nil!
|
|
214
|
+
RUBY
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it "ignores other receivers, methods, and argument counts" do
|
|
218
|
+
assert_no_offenses(<<~RUBY)
|
|
219
|
+
Other::T.must(foo)
|
|
220
|
+
object.must(foo)
|
|
221
|
+
T.let(foo, String)
|
|
222
|
+
T.must(foo, bar)
|
|
223
|
+
T.must(*values)
|
|
224
|
+
RUBY
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -6,3 +6,17 @@ require "type_toolkit"
|
|
|
6
6
|
|
|
7
7
|
require "minitest/autorun"
|
|
8
8
|
require "minitest/spec"
|
|
9
|
+
|
|
10
|
+
module Minitest
|
|
11
|
+
class Spec
|
|
12
|
+
#: () { () -> void } -> TypeToolkit::AbstractMethodNotImplementedError
|
|
13
|
+
def assert_abstract(&block)
|
|
14
|
+
assert_raises(TypeToolkit::AbstractMethodNotImplementedError, &block)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
#: -> bot
|
|
20
|
+
def assert_never_called!
|
|
21
|
+
raise Minitest::Assertion, "This should never be called"
|
|
22
|
+
end
|