rbs 0.5.0 → 0.6.0
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/CHANGELOG.md +9 -0
- data/Gemfile +2 -0
- data/Rakefile +2 -3
- data/docs/stdlib.md +0 -2
- data/docs/syntax.md +6 -3
- data/goodcheck.yml +65 -0
- data/lib/rbs.rb +1 -0
- data/lib/rbs/ast/declarations.rb +44 -6
- data/lib/rbs/definition_builder.rb +33 -34
- data/lib/rbs/environment.rb +49 -36
- data/lib/rbs/errors.rb +43 -25
- data/lib/rbs/factory.rb +14 -0
- data/lib/rbs/parser.y +60 -11
- data/lib/rbs/prototype/rb.rb +1 -1
- data/lib/rbs/prototype/rbi.rb +1 -1
- data/lib/rbs/prototype/runtime.rb +1 -1
- data/lib/rbs/test.rb +81 -3
- data/lib/rbs/test/errors.rb +1 -1
- data/lib/rbs/test/hook.rb +133 -259
- data/lib/rbs/test/observer.rb +17 -0
- data/lib/rbs/test/setup.rb +12 -15
- data/lib/rbs/test/spy.rb +0 -321
- data/lib/rbs/test/tester.rb +116 -0
- data/lib/rbs/test/type_check.rb +43 -5
- data/lib/rbs/version.rb +1 -1
- data/lib/rbs/writer.rb +2 -2
- data/schema/decls.json +21 -10
- data/stdlib/builtin/proc.rbs +1 -2
- data/stdlib/logger/formatter.rbs +23 -0
- data/stdlib/logger/log_device.rbs +39 -0
- data/stdlib/logger/logger.rbs +507 -0
- data/stdlib/logger/period.rbs +7 -0
- data/stdlib/logger/severity.rbs +8 -0
- metadata +11 -3
- data/lib/rbs/test/test_helper.rb +0 -180
@@ -0,0 +1,17 @@
|
|
1
|
+
module RBS
|
2
|
+
module Test
|
3
|
+
module Observer
|
4
|
+
@@observers = {}
|
5
|
+
|
6
|
+
class <<self
|
7
|
+
def notify(key, *args)
|
8
|
+
@@observers[key]&.call(*args)
|
9
|
+
end
|
10
|
+
|
11
|
+
def register(key, object = nil, &block)
|
12
|
+
@@observers[key] = object || block
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/rbs/test/setup.rb
CHANGED
@@ -10,30 +10,25 @@ begin
|
|
10
10
|
opts = Shellwords.shellsplit(ENV["RBS_TEST_OPT"] || "-I sig")
|
11
11
|
filter = ENV.fetch("RBS_TEST_TARGET").split(",")
|
12
12
|
skips = (ENV["RBS_TEST_SKIP"] || "").split(",")
|
13
|
-
|
14
|
-
|
13
|
+
sampling = !ENV.key?("RBS_TEST_NO_SAMPLE")
|
14
|
+
RBS.logger_level = (ENV["RBS_TEST_LOGLEVEL"] || "info")
|
15
15
|
rescue
|
16
16
|
STDERR.puts "rbs/test/setup handles the following environment variables:"
|
17
17
|
STDERR.puts " [REQUIRED] RBS_TEST_TARGET: test target class name, `Foo::Bar,Foo::Baz` for each class or `Foo::*` for all classes under `Foo`"
|
18
18
|
STDERR.puts " [OPTIONAL] RBS_TEST_SKIP: skip testing classes"
|
19
19
|
STDERR.puts " [OPTIONAL] RBS_TEST_OPT: options for signatures (`-r` for libraries or `-I` for signatures)"
|
20
20
|
STDERR.puts " [OPTIONAL] RBS_TEST_LOGLEVEL: one of debug|info|warn|error|fatal (defaults to info)"
|
21
|
-
STDERR.puts " [OPTIONAL]
|
21
|
+
STDERR.puts " [OPTIONAL] RBS_TEST_NO_SAMPLE: if set, the type checker tests all the values of a collection"
|
22
22
|
exit 1
|
23
23
|
end
|
24
24
|
|
25
|
-
hooks = []
|
26
|
-
|
27
|
-
env = RBS::Environment.new
|
28
|
-
|
29
25
|
loader = RBS::EnvironmentLoader.new
|
30
26
|
OptionParser.new do |opts|
|
31
27
|
opts.on("-r [LIB]") do |name| loader.add(library: name) end
|
32
28
|
opts.on("-I [DIR]") do |dir| loader.add(path: Pathname(dir)) end
|
33
29
|
end.parse!(opts)
|
34
|
-
loader.load(env: env)
|
35
30
|
|
36
|
-
env =
|
31
|
+
env = RBS::Environment.from_loader(loader).resolve_type_names
|
37
32
|
|
38
33
|
def match(filter, name)
|
39
34
|
if filter.end_with?("*")
|
@@ -43,16 +38,18 @@ def match(filter, name)
|
|
43
38
|
end
|
44
39
|
end
|
45
40
|
|
41
|
+
factory = RBS::Factory.new()
|
42
|
+
tester = RBS::Test::Tester.new(env: env)
|
43
|
+
|
46
44
|
TracePoint.trace :end do |tp|
|
47
|
-
class_name = tp.self.name
|
45
|
+
class_name = tp.self.name&.yield_self {|name| factory.type_name(name).absolute! }
|
48
46
|
|
49
47
|
if class_name
|
50
|
-
if filter.any? {|f| match(f, class_name) } && skips.none? {|f| match(f, class_name) }
|
51
|
-
|
52
|
-
|
53
|
-
if env.class_decls.key?(type_name)
|
48
|
+
if filter.any? {|f| match(f, class_name.to_s) } && skips.none? {|f| match(f, class_name.to_s) }
|
49
|
+
if tester.checkers.none? {|hook| hook.klass == tp.self }
|
50
|
+
if env.class_decls.key?(class_name)
|
54
51
|
logger.info "Setting up hooks for #{class_name}"
|
55
|
-
|
52
|
+
tester.install!(tp.self, sampling: sampling)
|
56
53
|
end
|
57
54
|
end
|
58
55
|
end
|
data/lib/rbs/test/spy.rb
CHANGED
@@ -1,325 +1,4 @@
|
|
1
1
|
module RBS
|
2
2
|
module Test
|
3
|
-
module Spy
|
4
|
-
def self.singleton_method(object, method_name)
|
5
|
-
spy = SingletonSpy.new(object: object, method_name: method_name)
|
6
|
-
|
7
|
-
if block_given?
|
8
|
-
begin
|
9
|
-
spy.setup
|
10
|
-
yield spy
|
11
|
-
ensure
|
12
|
-
spy.reset
|
13
|
-
end
|
14
|
-
else
|
15
|
-
spy
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.instance_method(mod, method_name)
|
20
|
-
spy = InstanceSpy.new(mod: mod, method_name: method_name)
|
21
|
-
|
22
|
-
if block_given?
|
23
|
-
begin
|
24
|
-
spy.setup
|
25
|
-
yield spy
|
26
|
-
ensure
|
27
|
-
spy.reset
|
28
|
-
end
|
29
|
-
else
|
30
|
-
spy
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.wrap(object, method_name)
|
35
|
-
spy = WrapSpy.new(object: object, method_name: method_name)
|
36
|
-
|
37
|
-
if block_given?
|
38
|
-
begin
|
39
|
-
yield spy, spy.wrapped_object
|
40
|
-
end
|
41
|
-
else
|
42
|
-
spy
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
class SingletonSpy
|
47
|
-
attr_accessor :callback
|
48
|
-
attr_reader :method_name
|
49
|
-
attr_reader :object
|
50
|
-
|
51
|
-
def initialize(object:, method_name:)
|
52
|
-
@object = object
|
53
|
-
@method_name = method_name
|
54
|
-
@callback = -> (_) { }
|
55
|
-
end
|
56
|
-
|
57
|
-
def setup
|
58
|
-
spy = self
|
59
|
-
|
60
|
-
object.singleton_class.class_eval do
|
61
|
-
remove_method spy.method_name
|
62
|
-
define_method spy.method_name, spy.spy()
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def spy()
|
67
|
-
spy = self
|
68
|
-
|
69
|
-
-> (*args, &block) do
|
70
|
-
return_value = nil
|
71
|
-
exception = nil
|
72
|
-
block_calls = []
|
73
|
-
|
74
|
-
spy_block = if block
|
75
|
-
Object.new.instance_eval do |fresh|
|
76
|
-
-> (*block_args) do
|
77
|
-
block_exn = nil
|
78
|
-
block_return = nil
|
79
|
-
|
80
|
-
begin
|
81
|
-
block_return = if self.equal?(fresh)
|
82
|
-
# no instance eval
|
83
|
-
block.call(*block_args)
|
84
|
-
else
|
85
|
-
self.instance_exec(*block_args, &block)
|
86
|
-
end
|
87
|
-
rescue Exception => exn
|
88
|
-
block_exn = exn
|
89
|
-
end
|
90
|
-
|
91
|
-
block_calls << ArgumentsReturn.new(
|
92
|
-
arguments: block_args,
|
93
|
-
return_value: block_return,
|
94
|
-
exception: block_exn
|
95
|
-
)
|
96
|
-
|
97
|
-
if block_exn
|
98
|
-
raise block_exn
|
99
|
-
else
|
100
|
-
block_return
|
101
|
-
end
|
102
|
-
end.ruby2_keywords
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
begin
|
107
|
-
return_value = super(*args, &spy_block)
|
108
|
-
rescue Exception => exn
|
109
|
-
exception = exn
|
110
|
-
end
|
111
|
-
|
112
|
-
trace = CallTrace.new(
|
113
|
-
method_name: spy.method_name,
|
114
|
-
method_call: ArgumentsReturn.new(
|
115
|
-
arguments: args,
|
116
|
-
return_value: return_value,
|
117
|
-
exception: exception,
|
118
|
-
),
|
119
|
-
block_calls: block_calls,
|
120
|
-
block_given: block != nil
|
121
|
-
)
|
122
|
-
|
123
|
-
spy.callback.call(trace)
|
124
|
-
|
125
|
-
if exception
|
126
|
-
raise exception
|
127
|
-
else
|
128
|
-
return_value
|
129
|
-
end
|
130
|
-
end.ruby2_keywords
|
131
|
-
end
|
132
|
-
|
133
|
-
def reset
|
134
|
-
if object.singleton_class.methods.include?(method_name)
|
135
|
-
object.singleton_class.remove_method method_name
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
class InstanceSpy
|
141
|
-
attr_accessor :callback
|
142
|
-
attr_reader :mod
|
143
|
-
attr_reader :method_name
|
144
|
-
attr_reader :original_method
|
145
|
-
|
146
|
-
def initialize(mod:, method_name:)
|
147
|
-
@mod = mod
|
148
|
-
@method_name = method_name
|
149
|
-
@original_method = mod.instance_method(method_name)
|
150
|
-
@callback = -> (_) { }
|
151
|
-
end
|
152
|
-
|
153
|
-
def setup
|
154
|
-
spy = self
|
155
|
-
|
156
|
-
mod.class_eval do
|
157
|
-
remove_method spy.method_name
|
158
|
-
define_method spy.method_name, spy.spy()
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
def reset
|
163
|
-
spy = self
|
164
|
-
|
165
|
-
mod.class_eval do
|
166
|
-
remove_method spy.method_name
|
167
|
-
define_method spy.method_name, spy.original_method
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
def spy
|
172
|
-
spy = self
|
173
|
-
|
174
|
-
-> (*args, &block) do
|
175
|
-
return_value = nil
|
176
|
-
exception = nil
|
177
|
-
block_calls = []
|
178
|
-
|
179
|
-
spy_block = if block
|
180
|
-
Object.new.instance_eval do |fresh|
|
181
|
-
-> (*block_args) do
|
182
|
-
block_exn = nil
|
183
|
-
block_return = nil
|
184
|
-
|
185
|
-
begin
|
186
|
-
block_return = if self.equal?(fresh)
|
187
|
-
# no instance eval
|
188
|
-
block.call(*block_args)
|
189
|
-
else
|
190
|
-
self.instance_exec(*block_args, &block)
|
191
|
-
end
|
192
|
-
rescue Exception => exn
|
193
|
-
block_exn = exn
|
194
|
-
end
|
195
|
-
|
196
|
-
block_calls << ArgumentsReturn.new(
|
197
|
-
arguments: block_args,
|
198
|
-
return_value: block_return,
|
199
|
-
exception: block_exn
|
200
|
-
)
|
201
|
-
|
202
|
-
if block_exn
|
203
|
-
raise block_exn
|
204
|
-
else
|
205
|
-
block_return
|
206
|
-
end
|
207
|
-
end.ruby2_keywords
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
begin
|
212
|
-
return_value = spy.original_method.bind_call(self, *args, &spy_block)
|
213
|
-
rescue Exception => exn
|
214
|
-
exception = exn
|
215
|
-
end
|
216
|
-
|
217
|
-
trace = CallTrace.new(
|
218
|
-
method_name: spy.method_name,
|
219
|
-
method_call: ArgumentsReturn.new(
|
220
|
-
arguments: args,
|
221
|
-
return_value: return_value,
|
222
|
-
exception: exception,
|
223
|
-
),
|
224
|
-
block_calls: block_calls,
|
225
|
-
block_given: block != nil
|
226
|
-
)
|
227
|
-
|
228
|
-
spy.callback.call(trace)
|
229
|
-
|
230
|
-
if exception
|
231
|
-
raise exception
|
232
|
-
else
|
233
|
-
return_value
|
234
|
-
end
|
235
|
-
end.ruby2_keywords
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
class WrapSpy
|
240
|
-
attr_accessor :callback
|
241
|
-
attr_reader :object
|
242
|
-
attr_reader :method_name
|
243
|
-
|
244
|
-
def initialize(object:, method_name:)
|
245
|
-
@callback = -> (_) { }
|
246
|
-
@object = object
|
247
|
-
@method_name = method_name
|
248
|
-
end
|
249
|
-
|
250
|
-
def wrapped_object
|
251
|
-
spy = self
|
252
|
-
|
253
|
-
Class.new(BasicObject) do
|
254
|
-
define_method(:method_missing) do |name, *args, &block|
|
255
|
-
spy.object.__send__(name, *args, &block)
|
256
|
-
end
|
257
|
-
|
258
|
-
define_method(spy.method_name, -> (*args, &block) {
|
259
|
-
return_value = nil
|
260
|
-
exception = nil
|
261
|
-
block_calls = []
|
262
|
-
|
263
|
-
spy_block = if block
|
264
|
-
Object.new.instance_eval do |fresh|
|
265
|
-
-> (*block_args) do
|
266
|
-
block_exn = nil
|
267
|
-
block_return = nil
|
268
|
-
|
269
|
-
begin
|
270
|
-
block_return = if self.equal?(fresh)
|
271
|
-
# no instance eval
|
272
|
-
block.call(*block_args)
|
273
|
-
else
|
274
|
-
self.instance_exec(*block_args, &block)
|
275
|
-
end
|
276
|
-
rescue Exception => exn
|
277
|
-
block_exn = exn
|
278
|
-
end
|
279
|
-
|
280
|
-
block_calls << ArgumentsReturn.new(
|
281
|
-
arguments: block_args,
|
282
|
-
return_value: block_return,
|
283
|
-
exception: block_exn
|
284
|
-
)
|
285
|
-
|
286
|
-
if block_exn
|
287
|
-
raise block_exn
|
288
|
-
else
|
289
|
-
block_return
|
290
|
-
end
|
291
|
-
end.ruby2_keywords
|
292
|
-
end
|
293
|
-
end
|
294
|
-
|
295
|
-
begin
|
296
|
-
return_value = spy.object.__send__(spy.method_name, *args, &spy_block)
|
297
|
-
rescue ::Exception => exn
|
298
|
-
exception = exn
|
299
|
-
end
|
300
|
-
|
301
|
-
trace = CallTrace.new(
|
302
|
-
method_name: spy.method_name,
|
303
|
-
method_call: ArgumentsReturn.new(
|
304
|
-
arguments: args,
|
305
|
-
return_value: return_value,
|
306
|
-
exception: exception,
|
307
|
-
),
|
308
|
-
block_calls: block_calls,
|
309
|
-
block_given: block != nil
|
310
|
-
)
|
311
|
-
|
312
|
-
spy.callback.call(trace)
|
313
|
-
|
314
|
-
if exception
|
315
|
-
spy.object.__send__(:raise, exception)
|
316
|
-
else
|
317
|
-
return_value
|
318
|
-
end
|
319
|
-
}.ruby2_keywords)
|
320
|
-
end.new()
|
321
|
-
end
|
322
|
-
end
|
323
|
-
end
|
324
3
|
end
|
325
4
|
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module RBS
|
2
|
+
module Test
|
3
|
+
class Tester
|
4
|
+
attr_reader :env
|
5
|
+
attr_reader :checkers
|
6
|
+
|
7
|
+
def initialize(env:)
|
8
|
+
@env = env
|
9
|
+
@checkers = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def factory
|
13
|
+
@factory ||= Factory.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def builder
|
17
|
+
@builder ||= DefinitionBuilder.new(env: env)
|
18
|
+
end
|
19
|
+
|
20
|
+
def install!(klass, sampling:)
|
21
|
+
RBS.logger.info { "Installing runtime type checker in #{klass}..." }
|
22
|
+
|
23
|
+
type_name = factory.type_name(klass.name).absolute!
|
24
|
+
|
25
|
+
builder.build_instance(type_name).tap do |definition|
|
26
|
+
instance_key = new_key(type_name, "InstanceChecker")
|
27
|
+
Observer.register(instance_key, MethodCallTester.new(klass, builder, definition, kind: :instance, sampling: sampling))
|
28
|
+
|
29
|
+
definition.methods.each do |name, method|
|
30
|
+
if method.implemented_in == type_name
|
31
|
+
RBS.logger.info { "Setting up method hook in ##{name}..." }
|
32
|
+
Hook.hook_instance_method klass, name, key: instance_key
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
builder.build_singleton(type_name).tap do |definition|
|
38
|
+
singleton_key = new_key(type_name, "SingletonChecker")
|
39
|
+
Observer.register(singleton_key, MethodCallTester.new(klass.singleton_class, builder, definition, kind: :singleton, sampling: sampling))
|
40
|
+
|
41
|
+
definition.methods.each do |name, method|
|
42
|
+
if method.implemented_in == type_name || name == :new
|
43
|
+
RBS.logger.info { "Setting up method hook in .#{name}..." }
|
44
|
+
Hook.hook_singleton_method klass, name, key: singleton_key
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def new_key(type_name, prefix)
|
51
|
+
"#{prefix}__#{type_name}__#{SecureRandom.hex(10)}"
|
52
|
+
end
|
53
|
+
|
54
|
+
class TypeError < Exception
|
55
|
+
attr_reader :errors
|
56
|
+
|
57
|
+
def initialize(errors)
|
58
|
+
@errors = errors
|
59
|
+
|
60
|
+
super "TypeError: #{errors.map {|e| Errors.to_string(e) }.join(", ")}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class MethodCallTester
|
65
|
+
attr_reader :self_class
|
66
|
+
attr_reader :definition
|
67
|
+
attr_reader :builder
|
68
|
+
attr_reader :kind
|
69
|
+
attr_reader :sampling
|
70
|
+
|
71
|
+
def initialize(self_class, builder, definition, kind:, sampling:)
|
72
|
+
@self_class = self_class
|
73
|
+
@definition = definition
|
74
|
+
@builder = builder
|
75
|
+
@kind = kind
|
76
|
+
@sampling = sampling
|
77
|
+
end
|
78
|
+
|
79
|
+
def env
|
80
|
+
builder.env
|
81
|
+
end
|
82
|
+
|
83
|
+
def check
|
84
|
+
@check ||= TypeCheck.new(self_class: self_class, builder: builder, sampling: sampling)
|
85
|
+
end
|
86
|
+
|
87
|
+
def format_method_name(name)
|
88
|
+
case kind
|
89
|
+
when :instance
|
90
|
+
"##{name}"
|
91
|
+
when :singleton
|
92
|
+
".#{name}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def call(receiver, trace)
|
97
|
+
method_name = trace.method_name
|
98
|
+
method = definition.methods[method_name]
|
99
|
+
if method
|
100
|
+
RBS.logger.debug { "Type checking `#{self_class}#{format_method_name(method_name)}`..."}
|
101
|
+
errors = check.overloaded_call(method, format_method_name(method_name), trace, errors: [])
|
102
|
+
|
103
|
+
if errors.empty?
|
104
|
+
RBS.logger.debug { "No type error detected 👏" }
|
105
|
+
else
|
106
|
+
RBS.logger.debug { "Detected type error 🚨" }
|
107
|
+
raise TypeError.new(errors)
|
108
|
+
end
|
109
|
+
else
|
110
|
+
RBS.logger.error { "Type checking `#{self_class}#{method_name}` call but no method found in definition" }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|