rbs 0.3.1 → 0.8.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/.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)
|