rbs 0.3.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +7 -1
- data/.gitignore +1 -1
- data/CHANGELOG.md +39 -0
- data/COPYING +1 -1
- data/Gemfile +16 -2
- data/README.md +87 -48
- data/Rakefile +54 -22
- data/bin/rbs-prof +9 -0
- data/bin/run_in_md.rb +49 -0
- data/bin/test_runner.rb +0 -2
- data/docs/sigs.md +6 -6
- data/docs/stdlib.md +3 -5
- data/docs/syntax.md +6 -3
- data/goodcheck.yml +65 -0
- data/lib/rbs.rb +3 -0
- data/lib/rbs/ast/declarations.rb +115 -14
- data/lib/rbs/ast/members.rb +41 -17
- data/lib/rbs/cli.rb +315 -122
- data/lib/rbs/constant.rb +4 -4
- data/lib/rbs/constant_table.rb +51 -45
- data/lib/rbs/definition.rb +175 -59
- data/lib/rbs/definition_builder.rb +802 -604
- data/lib/rbs/environment.rb +352 -210
- data/lib/rbs/environment_walker.rb +14 -23
- data/lib/rbs/errors.rb +184 -3
- data/lib/rbs/factory.rb +14 -0
- data/lib/rbs/parser.y +95 -27
- data/lib/rbs/prototype/rb.rb +119 -117
- data/lib/rbs/prototype/rbi.rb +5 -3
- data/lib/rbs/prototype/runtime.rb +34 -7
- data/lib/rbs/substitution.rb +12 -1
- data/lib/rbs/test.rb +82 -3
- data/lib/rbs/test/errors.rb +5 -1
- data/lib/rbs/test/hook.rb +133 -259
- data/lib/rbs/test/observer.rb +17 -0
- data/lib/rbs/test/setup.rb +35 -19
- data/lib/rbs/test/setup_helper.rb +29 -0
- data/lib/rbs/test/spy.rb +0 -321
- data/lib/rbs/test/tester.rb +116 -0
- data/lib/rbs/test/type_check.rb +43 -7
- data/lib/rbs/type_name_resolver.rb +58 -0
- data/lib/rbs/types.rb +94 -2
- data/lib/rbs/validator.rb +51 -0
- data/lib/rbs/variance_calculator.rb +12 -2
- data/lib/rbs/version.rb +1 -1
- data/lib/rbs/writer.rb +127 -91
- data/rbs.gemspec +0 -9
- data/schema/annotation.json +14 -0
- data/schema/comment.json +26 -0
- data/schema/decls.json +353 -0
- data/schema/function.json +87 -0
- data/schema/location.json +56 -0
- data/schema/members.json +248 -0
- data/schema/methodType.json +44 -0
- data/schema/types.json +299 -0
- data/stdlib/benchmark/benchmark.rbs +151 -151
- data/stdlib/builtin/encoding.rbs +2 -0
- data/stdlib/builtin/enumerable.rbs +4 -4
- data/stdlib/builtin/enumerator.rbs +3 -1
- data/stdlib/builtin/fiber.rbs +5 -1
- data/stdlib/builtin/file.rbs +0 -3
- data/stdlib/builtin/io.rbs +4 -4
- data/stdlib/builtin/proc.rbs +1 -2
- data/stdlib/builtin/symbol.rbs +1 -1
- data/stdlib/builtin/thread.rbs +2 -2
- data/stdlib/csv/csv.rbs +4 -6
- data/stdlib/fiber/fiber.rbs +117 -0
- data/stdlib/json/json.rbs +1 -1
- 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
- data/stdlib/mutex_m/mutex_m.rbs +77 -0
- data/stdlib/pathname/pathname.rbs +6 -6
- data/stdlib/prime/integer-extension.rbs +1 -1
- data/stdlib/prime/prime.rbs +44 -44
- data/stdlib/pty/pty.rbs +159 -0
- data/stdlib/tmpdir/tmpdir.rbs +1 -1
- metadata +28 -116
- data/lib/rbs/test/test_helper.rb +0 -183
@@ -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, sample_size:)
|
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, sample_size: sample_size))
|
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, sample_size: sample_size))
|
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 :sample_size
|
70
|
+
|
71
|
+
def initialize(self_class, builder, definition, kind:, sample_size:)
|
72
|
+
@self_class = self_class
|
73
|
+
@definition = definition
|
74
|
+
@builder = builder
|
75
|
+
@kind = kind
|
76
|
+
@sample_size = sample_size
|
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, sample_size: sample_size)
|
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
|
data/lib/rbs/test/type_check.rb
CHANGED
@@ -3,10 +3,38 @@ module RBS
|
|
3
3
|
class TypeCheck
|
4
4
|
attr_reader :self_class
|
5
5
|
attr_reader :builder
|
6
|
+
attr_reader :sample_size
|
6
7
|
|
7
|
-
|
8
|
+
DEFAULT_SAMPLE_SIZE = 100
|
9
|
+
|
10
|
+
def initialize(self_class:, builder:, sample_size:)
|
8
11
|
@self_class = self_class
|
9
12
|
@builder = builder
|
13
|
+
@sample_size = sample_size
|
14
|
+
end
|
15
|
+
|
16
|
+
def overloaded_call(method, method_name, call, errors:)
|
17
|
+
es = method.method_types.map do |method_type|
|
18
|
+
es = method_call(method_name, method_type, call, errors: [])
|
19
|
+
|
20
|
+
if es.empty?
|
21
|
+
return errors
|
22
|
+
else
|
23
|
+
es
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if es.size == 1
|
28
|
+
errors.push(*es[0])
|
29
|
+
else
|
30
|
+
errors << Errors::UnresolvedOverloadingError.new(
|
31
|
+
klass: self_class,
|
32
|
+
method_name: method_name,
|
33
|
+
method_types: method.method_types
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
errors
|
10
38
|
end
|
11
39
|
|
12
40
|
def method_call(method_name, method_type, call, errors:)
|
@@ -56,7 +84,7 @@ module RBS
|
|
56
84
|
end
|
57
85
|
|
58
86
|
def return(method_name, method_type, fun, call, errors, return_error:)
|
59
|
-
|
87
|
+
if call.return?
|
60
88
|
unless value(call.return_value, fun.return_type)
|
61
89
|
errors << return_error.new(klass: self_class,
|
62
90
|
method_name: method_name,
|
@@ -151,6 +179,10 @@ module RBS
|
|
151
179
|
end
|
152
180
|
end
|
153
181
|
|
182
|
+
def sample(array)
|
183
|
+
sample_size && (array.size > sample_size) ? array.sample(sample_size) : array
|
184
|
+
end
|
185
|
+
|
154
186
|
def value(val, type)
|
155
187
|
case type
|
156
188
|
when Types::Bases::Any
|
@@ -175,9 +207,14 @@ module RBS
|
|
175
207
|
klass = Object.const_get(type.name.to_s)
|
176
208
|
case
|
177
209
|
when klass == ::Array
|
178
|
-
Test.call(val, IS_AP, klass) && val.
|
210
|
+
Test.call(val, IS_AP, klass) && sample(val).yield_self do |val|
|
211
|
+
val.all? {|v| value(v, type.args[0]) }
|
212
|
+
end
|
179
213
|
when klass == ::Hash
|
180
|
-
Test.call(val, IS_AP, klass) && val.
|
214
|
+
Test.call(val, IS_AP, klass) && sample(val.keys).yield_self do |keys|
|
215
|
+
values = val.values_at(*keys)
|
216
|
+
keys.all? {|key| value(key, type.args[0]) } && values.all? {|v| value(v, type.args[1]) }
|
217
|
+
end
|
181
218
|
when klass == ::Range
|
182
219
|
Test.call(val, IS_AP, klass) && value(val.begin, type.args[0]) && value(val.end, type.args[0])
|
183
220
|
when klass == ::Enumerator
|
@@ -198,7 +235,7 @@ module RBS
|
|
198
235
|
end
|
199
236
|
end
|
200
237
|
|
201
|
-
values.all? do |v|
|
238
|
+
sample(values).all? do |v|
|
202
239
|
if v.size == 1
|
203
240
|
# Only one block argument.
|
204
241
|
value(v[0], type.args[0]) || value(v, type.args[0])
|
@@ -220,8 +257,7 @@ module RBS
|
|
220
257
|
val == klass
|
221
258
|
when Types::Interface
|
222
259
|
methods = Set.new(Test.call(val, METHODS))
|
223
|
-
|
224
|
-
if (definition = builder.build_interface(type.name, decl))
|
260
|
+
if (definition = builder.build_interface(type.name))
|
225
261
|
definition.methods.each_key.all? do |method_name|
|
226
262
|
methods.member?(method_name)
|
227
263
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module RBS
|
2
|
+
class TypeNameResolver
|
3
|
+
Query = Struct.new(:type_name, :context, keyword_init: true)
|
4
|
+
|
5
|
+
attr_reader :all_names
|
6
|
+
attr_reader :cache
|
7
|
+
|
8
|
+
def initialize()
|
9
|
+
@all_names = Set[]
|
10
|
+
@cache = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.from_env(env)
|
14
|
+
new.add_names(env.class_decls.keys)
|
15
|
+
.add_names(env.interface_decls.keys)
|
16
|
+
.add_names(env.alias_decls.keys)
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_names(names)
|
20
|
+
all_names.merge(names)
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def try_cache(query)
|
25
|
+
cache.fetch(query) do
|
26
|
+
result = yield
|
27
|
+
cache[query] = result
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def resolve(type_name, context:)
|
32
|
+
if type_name.absolute?
|
33
|
+
return type_name
|
34
|
+
end
|
35
|
+
|
36
|
+
query = Query.new(type_name: type_name, context: context)
|
37
|
+
try_cache(query) do
|
38
|
+
path_head, *path_tail = type_name.to_namespace.path
|
39
|
+
name_head = TypeName.new(name: path_head, namespace: Namespace.empty)
|
40
|
+
|
41
|
+
absolute_head = context.each.find do |namespace|
|
42
|
+
full_name = name_head.with_prefix(namespace)
|
43
|
+
has_name?(full_name) and break full_name
|
44
|
+
end
|
45
|
+
|
46
|
+
if absolute_head
|
47
|
+
has_name?(Namespace.new(path: absolute_head.to_namespace.path.push(*path_tail), absolute: true).to_type_name)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def has_name?(full_name)
|
53
|
+
if all_names.include?(full_name)
|
54
|
+
full_name
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/rbs/types.rb
CHANGED
@@ -12,6 +12,12 @@ module RBS
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
+
module NoTypeName
|
16
|
+
def map_type_name
|
17
|
+
self
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
15
21
|
module EmptyEachType
|
16
22
|
def each_type
|
17
23
|
if block_given?
|
@@ -43,6 +49,7 @@ module RBS
|
|
43
49
|
include NoFreeVariables
|
44
50
|
include NoSubst
|
45
51
|
include EmptyEachType
|
52
|
+
include NoTypeName
|
46
53
|
|
47
54
|
def to_json(*a)
|
48
55
|
klass = to_s.to_sym
|
@@ -82,7 +89,11 @@ module RBS
|
|
82
89
|
class Top < Base; end
|
83
90
|
class Bottom < Base; end
|
84
91
|
class Self < Base; end
|
85
|
-
class Instance < Base
|
92
|
+
class Instance < Base
|
93
|
+
def sub(s)
|
94
|
+
s.apply(self)
|
95
|
+
end
|
96
|
+
end
|
86
97
|
class Class < Base; end
|
87
98
|
end
|
88
99
|
|
@@ -90,6 +101,8 @@ module RBS
|
|
90
101
|
attr_reader :name
|
91
102
|
attr_reader :location
|
92
103
|
|
104
|
+
include NoTypeName
|
105
|
+
|
93
106
|
def initialize(name:, location:)
|
94
107
|
@name = name
|
95
108
|
@location = location
|
@@ -174,6 +187,13 @@ module RBS
|
|
174
187
|
end
|
175
188
|
|
176
189
|
include EmptyEachType
|
190
|
+
|
191
|
+
def map_type_name
|
192
|
+
ClassSingleton.new(
|
193
|
+
name: yield(name, location, self),
|
194
|
+
location: location
|
195
|
+
)
|
196
|
+
end
|
177
197
|
end
|
178
198
|
|
179
199
|
module Application
|
@@ -235,6 +255,14 @@ module RBS
|
|
235
255
|
args: args.map {|ty| ty.sub(s) },
|
236
256
|
location: location)
|
237
257
|
end
|
258
|
+
|
259
|
+
def map_type_name(&block)
|
260
|
+
Interface.new(
|
261
|
+
name: yield(name, location, self),
|
262
|
+
args: args.map {|type| type.map_type_name(&block) },
|
263
|
+
location: location
|
264
|
+
)
|
265
|
+
end
|
238
266
|
end
|
239
267
|
|
240
268
|
class ClassInstance
|
@@ -257,6 +285,14 @@ module RBS
|
|
257
285
|
args: args.map {|ty| ty.sub(s) },
|
258
286
|
location: location)
|
259
287
|
end
|
288
|
+
|
289
|
+
def map_type_name(&block)
|
290
|
+
ClassInstance.new(
|
291
|
+
name: yield(name, location, self),
|
292
|
+
args: args.map {|type| type.map_type_name(&block) },
|
293
|
+
location: location
|
294
|
+
)
|
295
|
+
end
|
260
296
|
end
|
261
297
|
|
262
298
|
class Alias
|
@@ -290,6 +326,13 @@ module RBS
|
|
290
326
|
end
|
291
327
|
|
292
328
|
include EmptyEachType
|
329
|
+
|
330
|
+
def map_type_name
|
331
|
+
Alias.new(
|
332
|
+
name: yield(name, location, self),
|
333
|
+
location: location
|
334
|
+
)
|
335
|
+
end
|
293
336
|
end
|
294
337
|
|
295
338
|
class Tuple
|
@@ -343,6 +386,13 @@ module RBS
|
|
343
386
|
enum_for :each_type
|
344
387
|
end
|
345
388
|
end
|
389
|
+
|
390
|
+
def map_type_name(&block)
|
391
|
+
Tuple.new(
|
392
|
+
types: types.map {|type| type.map_type_name(&block) },
|
393
|
+
location: location
|
394
|
+
)
|
395
|
+
end
|
346
396
|
end
|
347
397
|
|
348
398
|
class Record
|
@@ -401,6 +451,13 @@ module RBS
|
|
401
451
|
enum_for :each_type
|
402
452
|
end
|
403
453
|
end
|
454
|
+
|
455
|
+
def map_type_name(&block)
|
456
|
+
Record.new(
|
457
|
+
fields: fields.transform_values {|ty| ty.map_type_name(&block) },
|
458
|
+
location: location
|
459
|
+
)
|
460
|
+
end
|
404
461
|
end
|
405
462
|
|
406
463
|
class Optional
|
@@ -449,6 +506,13 @@ module RBS
|
|
449
506
|
enum_for :each_type
|
450
507
|
end
|
451
508
|
end
|
509
|
+
|
510
|
+
def map_type_name(&block)
|
511
|
+
Optional.new(
|
512
|
+
type: type.map_type_name(&block),
|
513
|
+
location: location
|
514
|
+
)
|
515
|
+
end
|
452
516
|
end
|
453
517
|
|
454
518
|
class Union
|
@@ -510,6 +574,13 @@ module RBS
|
|
510
574
|
enum_for :map_type
|
511
575
|
end
|
512
576
|
end
|
577
|
+
|
578
|
+
def map_type_name(&block)
|
579
|
+
Union.new(
|
580
|
+
types: types.map {|type| type.map_type_name(&block) },
|
581
|
+
location: location
|
582
|
+
)
|
583
|
+
end
|
513
584
|
end
|
514
585
|
|
515
586
|
class Intersection
|
@@ -572,6 +643,13 @@ module RBS
|
|
572
643
|
enum_for :map_type
|
573
644
|
end
|
574
645
|
end
|
646
|
+
|
647
|
+
def map_type_name(&block)
|
648
|
+
Intersection.new(
|
649
|
+
types: types.map {|type| type.map_type_name(&block) },
|
650
|
+
location: location
|
651
|
+
)
|
652
|
+
end
|
575
653
|
end
|
576
654
|
|
577
655
|
class Function
|
@@ -648,7 +726,7 @@ module RBS
|
|
648
726
|
other.required_keywords == required_keywords &&
|
649
727
|
other.optional_keywords == optional_keywords &&
|
650
728
|
other.rest_keywords == rest_keywords &&
|
651
|
-
return_type == return_type
|
729
|
+
other.return_type == return_type
|
652
730
|
end
|
653
731
|
|
654
732
|
alias eql? ==
|
@@ -710,6 +788,12 @@ module RBS
|
|
710
788
|
end
|
711
789
|
end
|
712
790
|
|
791
|
+
def map_type_name(&block)
|
792
|
+
map_type do |type|
|
793
|
+
type.map_type_name(&block)
|
794
|
+
end
|
795
|
+
end
|
796
|
+
|
713
797
|
def each_type
|
714
798
|
if block_given?
|
715
799
|
required_positionals.each {|param| yield param.type }
|
@@ -899,6 +983,13 @@ module RBS
|
|
899
983
|
enum_for :each_type
|
900
984
|
end
|
901
985
|
end
|
986
|
+
|
987
|
+
def map_type_name(&block)
|
988
|
+
Proc.new(
|
989
|
+
type: type.map_type_name(&block),
|
990
|
+
location: location
|
991
|
+
)
|
992
|
+
end
|
902
993
|
end
|
903
994
|
|
904
995
|
class Literal
|
@@ -923,6 +1014,7 @@ module RBS
|
|
923
1014
|
include NoFreeVariables
|
924
1015
|
include NoSubst
|
925
1016
|
include EmptyEachType
|
1017
|
+
include NoTypeName
|
926
1018
|
|
927
1019
|
def to_json(*a)
|
928
1020
|
{ class: :literal, literal: literal.inspect, location: location }.to_json(*a)
|