type_toolkit 0.0.5 → 0.0.7
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/.github/workflows/ci.yml +0 -4
- data/README.md +61 -0
- data/Rakefile +0 -7
- 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 +258 -0
- data/docs/design/self_dot_methods.md +79 -0
- data/lib/rubocop/cop/type_toolkit/plugin.rb +1 -1
- data/lib/rubocop/cop/type_toolkit/prefer_not_nil.rb +150 -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 +66 -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/sorbet/rbi/shims/rubocop_minitest.rbi +5 -2
- data/spec/interface_spec.rb +419 -0
- data/spec/misc/how_method_added_hooks_work_spec.rb +63 -0
- data/spec/rubocop/cop/type_toolkit/prefer_not_nil_spec.rb +464 -0
- data/spec/spec_helper.rb +14 -0
- metadata +24 -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
|
|
@@ -4,14 +4,17 @@
|
|
|
4
4
|
module RuboCop
|
|
5
5
|
module Minitest
|
|
6
6
|
module AssertOffense
|
|
7
|
-
sig { params(source: String).void }
|
|
8
|
-
def assert_offense(source); end
|
|
7
|
+
sig { params(source: String, file: T.nilable(String), replacements: String).void }
|
|
8
|
+
def assert_offense(source, file = nil, **replacements); end
|
|
9
9
|
|
|
10
10
|
sig { params(source: String).void }
|
|
11
11
|
def assert_no_offenses(source); end
|
|
12
12
|
|
|
13
13
|
sig { params(source: String).void }
|
|
14
14
|
def assert_correction(source); end
|
|
15
|
+
|
|
16
|
+
sig { void }
|
|
17
|
+
def assert_no_corrections; end
|
|
15
18
|
end
|
|
16
19
|
end
|
|
17
20
|
end
|
|
@@ -0,0 +1,419 @@
|
|
|
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
|
+
|
|
405
|
+
describe "Abstract class methods" do
|
|
406
|
+
it "are not yet supported" do
|
|
407
|
+
test_context = self
|
|
408
|
+
|
|
409
|
+
Module.new do
|
|
410
|
+
interface!
|
|
411
|
+
|
|
412
|
+
test_context.assert_raises(NotImplementedError) do
|
|
413
|
+
abstract def self.abstract_class_method; end # rubocop:disable Style/ClassMethodsDefinitions
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
module TypeToolkit
|
|
6
|
+
module Misc
|
|
7
|
+
# This test just characterizes how `method_added` hooks work. It doesn't have anything to do with TypeToolkit itself,
|
|
8
|
+
# but it's useful for devs to confirm how this works across instance and class methods.
|
|
9
|
+
class HowMethodAddedHooksWorkSpec < Minitest::Spec
|
|
10
|
+
it "characterizes how method_added hooks work" do
|
|
11
|
+
test_context = self
|
|
12
|
+
|
|
13
|
+
Class.new do
|
|
14
|
+
class << self
|
|
15
|
+
attr_reader :__last_method_def
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
def __last_method_def
|
|
19
|
+
attached_object.__last_method_def
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def method_added(m)
|
|
24
|
+
is_singleton_method = false
|
|
25
|
+
@__last_method_def = [m, is_singleton_method]
|
|
26
|
+
|
|
27
|
+
super
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def singleton_method_added(m)
|
|
31
|
+
return super if m == :singleton_method_added
|
|
32
|
+
|
|
33
|
+
is_singleton_method = true
|
|
34
|
+
@__last_method_def = [m, is_singleton_method]
|
|
35
|
+
|
|
36
|
+
super
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
test_context.assert_nil __last_method_def
|
|
41
|
+
|
|
42
|
+
def instance_method; end
|
|
43
|
+
|
|
44
|
+
test_context.assert_equal [:instance_method, false], __last_method_def
|
|
45
|
+
|
|
46
|
+
def self.singleton_method_via_self_dot; end # rubocop:disable Style/ClassMethodsDefinitions
|
|
47
|
+
|
|
48
|
+
test_context.assert_equal [:singleton_method_via_self_dot, true], __last_method_def
|
|
49
|
+
|
|
50
|
+
r = class << self
|
|
51
|
+
def singleton_method_via_sclass; end
|
|
52
|
+
|
|
53
|
+
__last_method_def
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
test_context.assert_equal [:singleton_method_via_sclass, true], r
|
|
57
|
+
|
|
58
|
+
test_context.assert_equal [:singleton_method_via_sclass, true], __last_method_def
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|