rbs 0.4.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +7 -1
  3. data/.gitignore +1 -1
  4. data/CHANGELOG.md +35 -0
  5. data/Gemfile +14 -0
  6. data/README.md +86 -47
  7. data/Rakefile +53 -21
  8. data/bin/rbs-prof +9 -0
  9. data/bin/run_in_md.rb +49 -0
  10. data/docs/stdlib.md +0 -2
  11. data/docs/syntax.md +6 -3
  12. data/goodcheck.yml +65 -0
  13. data/lib/rbs.rb +3 -0
  14. data/lib/rbs/ast/comment.rb +6 -0
  15. data/lib/rbs/ast/declarations.rb +106 -13
  16. data/lib/rbs/ast/members.rb +41 -17
  17. data/lib/rbs/cli.rb +317 -121
  18. data/lib/rbs/constant.rb +4 -4
  19. data/lib/rbs/constant_table.rb +51 -45
  20. data/lib/rbs/definition.rb +175 -59
  21. data/lib/rbs/definition_builder.rb +814 -604
  22. data/lib/rbs/environment.rb +352 -210
  23. data/lib/rbs/environment_walker.rb +14 -23
  24. data/lib/rbs/errors.rb +184 -3
  25. data/lib/rbs/factory.rb +14 -0
  26. data/lib/rbs/location.rb +15 -0
  27. data/lib/rbs/parser.y +100 -34
  28. data/lib/rbs/prototype/rb.rb +101 -113
  29. data/lib/rbs/prototype/rbi.rb +5 -3
  30. data/lib/rbs/prototype/runtime.rb +11 -7
  31. data/lib/rbs/substitution.rb +12 -1
  32. data/lib/rbs/test.rb +82 -3
  33. data/lib/rbs/test/errors.rb +5 -1
  34. data/lib/rbs/test/hook.rb +133 -259
  35. data/lib/rbs/test/observer.rb +17 -0
  36. data/lib/rbs/test/setup.rb +35 -19
  37. data/lib/rbs/test/setup_helper.rb +29 -0
  38. data/lib/rbs/test/spy.rb +0 -321
  39. data/lib/rbs/test/tester.rb +116 -0
  40. data/lib/rbs/test/type_check.rb +43 -7
  41. data/lib/rbs/type_name_resolver.rb +58 -0
  42. data/lib/rbs/types.rb +94 -2
  43. data/lib/rbs/validator.rb +55 -0
  44. data/lib/rbs/variance_calculator.rb +12 -2
  45. data/lib/rbs/version.rb +1 -1
  46. data/lib/rbs/writer.rb +127 -91
  47. data/rbs.gemspec +0 -10
  48. data/schema/decls.json +36 -10
  49. data/schema/members.json +3 -0
  50. data/stdlib/benchmark/benchmark.rbs +151 -151
  51. data/stdlib/builtin/enumerable.rbs +3 -3
  52. data/stdlib/builtin/file.rbs +0 -3
  53. data/stdlib/builtin/io.rbs +4 -4
  54. data/stdlib/builtin/proc.rbs +1 -2
  55. data/stdlib/builtin/thread.rbs +2 -2
  56. data/stdlib/csv/csv.rbs +4 -6
  57. data/stdlib/fiber/fiber.rbs +1 -1
  58. data/stdlib/json/json.rbs +7 -1
  59. data/stdlib/logger/formatter.rbs +23 -0
  60. data/stdlib/logger/log_device.rbs +39 -0
  61. data/stdlib/logger/logger.rbs +507 -0
  62. data/stdlib/logger/period.rbs +7 -0
  63. data/stdlib/logger/severity.rbs +8 -0
  64. data/stdlib/mutex_m/mutex_m.rbs +77 -0
  65. data/stdlib/pathname/pathname.rbs +6 -6
  66. data/stdlib/prime/integer-extension.rbs +1 -1
  67. data/stdlib/prime/prime.rbs +44 -44
  68. data/stdlib/pty/pty.rbs +159 -0
  69. data/stdlib/tmpdir/tmpdir.rbs +1 -1
  70. metadata +19 -130
  71. data/lib/rbs/test/test_helper.rb +0 -183
@@ -28,7 +28,11 @@ module RBS
28
28
  end
29
29
 
30
30
  def self.inspect_(obj)
31
- Hook.inspect_(obj)
31
+ if obj.respond_to?(:inspect)
32
+ obj.inspect
33
+ else
34
+ Test::INSPECT.bind(obj).call # For the case inspect is not defined (like BasicObject)
35
+ end
32
36
  end
33
37
 
34
38
  def self.to_string(error)
@@ -3,291 +3,165 @@ require "pp"
3
3
 
4
4
  module RBS
5
5
  module Test
6
- class Hook
7
- class Error < Exception
8
- attr_reader :errors
9
-
10
- def initialize(errors)
11
- @errors = errors
12
- super "Type error detected: [#{errors.map {|e| Errors.to_string(e) }.join(", ")}]"
13
- end
14
- end
15
-
16
- attr_reader :env
17
- attr_reader :logger
18
-
19
- attr_reader :instance_module
20
- attr_reader :instance_methods
21
- attr_reader :singleton_module
22
- attr_reader :singleton_methods
23
-
24
- attr_reader :klass
25
- attr_reader :errors
26
-
27
- def builder
28
- @builder ||= DefinitionBuilder.new(env: env)
29
- end
30
-
31
- def typecheck
32
- @typecheck ||= TypeCheck.new(self_class: klass, builder: builder)
33
- end
34
-
35
- def initialize(env, klass, logger:, raise_on_error: false)
36
- @env = env
37
- @logger = logger
38
- @klass = klass
39
-
40
- @instance_module = Module.new
41
- @instance_methods = []
42
-
43
- @singleton_module = Module.new
44
- @singleton_methods = []
45
-
46
- @errors = []
47
-
48
- @raise_on_error = raise_on_error
49
- end
50
-
51
- def raise_on_error!(error = true)
52
- @raise_on_error = error
53
- self
54
- end
55
-
56
- def raise_on_error?
57
- @raise_on_error
58
- end
59
-
60
- def prepend!
61
- klass.prepend @instance_module
62
- klass.singleton_class.prepend @singleton_module
6
+ module Hook
7
+ OPERATORS = {
8
+ :== => "eqeq",
9
+ :=== => "eqeqeq",
10
+ :+ => "plus",
11
+ :- => "minus",
12
+ :* => "star",
13
+ :/ => "slash",
14
+ :> => "gt",
15
+ :>= => "gteq",
16
+ :< => "lt",
17
+ :<= => "lteq",
18
+ :<=> => "ufo",
19
+ :& => "amp",
20
+ :| => "vbar",
21
+ :^ => "hat",
22
+ :! => "not",
23
+ :<< => "lshift",
24
+ :>> => "rshift",
25
+ :~ => "tilda"
26
+ }
27
+ def self.alias_names(target)
28
+ case target
29
+ when *OPERATORS.keys
30
+ name = OPERATORS[target]
31
+ [
32
+ "#{name}____with__#{Test.suffix}",
33
+ "#{name}____without__#{Test.suffix}"
34
+ ]
35
+ else
36
+ aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
63
37
 
64
- if block_given?
65
- yield
66
- disable
38
+ [
39
+ "#{aliased_target}__with__#{Test.suffix}#{punctuation}",
40
+ "#{aliased_target}__without__#{Test.suffix}#{punctuation}"
41
+ ]
67
42
  end
68
-
69
- self
70
43
  end
71
44
 
72
- def self.install(env, klass, logger:)
73
- new(env, klass, logger: logger).prepend!
74
- end
75
-
76
- def refinement
77
- klass = self.klass
78
- instance_module = self.instance_module
79
- singleton_module = self.singleton_module
80
-
81
- Module.new do
82
- refine klass do
83
- prepend instance_module
84
- end
85
-
86
- refine klass.singleton_class do
87
- prepend singleton_module
88
- end
89
- end
90
- end
45
+ def self.setup_alias_method_chain(klass, target)
46
+ with_method, without_method = alias_names(target)
91
47
 
92
- def verify_all
93
- type_name = Namespace.parse(klass.name).to_type_name.absolute!
48
+ RBS.logger.debug "alias name: #{target}, #{with_method}, #{without_method}"
94
49
 
95
- builder.build_instance(type_name).tap do |definition|
96
- definition.methods.each do |name, method|
97
- if method.defined_in.name.absolute! == type_name
98
- unless method.annotations.any? {|a| a.string == "rbs:test:skip" }
99
- logger.info "Installing a hook on #{type_name}##{name}: #{method.method_types.join(" | ")}"
100
- verify instance_method: name, types: method.method_types
101
- else
102
- logger.info "Skipping test of #{type_name}##{name}"
103
- end
104
- end
105
- end
106
- end
50
+ klass.instance_eval do
51
+ alias_method without_method, target
52
+ alias_method target, with_method
107
53
 
108
- builder.build_singleton(type_name).tap do |definition|
109
- definition.methods.each do |name, method|
110
- if method.defined_in&.name&.absolute! == type_name || name == :new
111
- unless method.annotations.any? {|a| a.string == "rbs:test:skip" }
112
- logger.info "Installing a hook on #{type_name}.#{name}: #{method.method_types.join(" | ")}"
113
- verify singleton_method: name, types: method.method_types
114
- else
115
- logger.info "Skipping test of #{type_name}.#{name}"
116
- end
117
- end
54
+ case
55
+ when public_method_defined?(without_method)
56
+ public target
57
+ when protected_method_defined?(without_method)
58
+ protected target
59
+ when private_method_defined?(without_method)
60
+ private target
118
61
  end
119
62
  end
120
-
121
- self
122
- end
123
-
124
- def delegation(name, method_types, method_name)
125
- hook = self
126
-
127
- -> (*args, &block) do
128
- hook.logger.debug { "#{method_name} receives arguments: #{hook.inspect_(args)}" }
129
-
130
- block_calls = []
131
-
132
- if block
133
- original_block = block
134
-
135
- block = hook.call(Object.new, INSTANCE_EVAL) do |fresh_obj|
136
- ->(*as) do
137
- hook.logger.debug { "#{method_name} receives block arguments: #{hook.inspect_(as)}" }
138
-
139
- ret = if self.equal?(fresh_obj)
140
- original_block[*as]
141
- else
142
- hook.call(self, INSTANCE_EXEC, *as, &original_block)
143
- end
144
-
145
- block_calls << ArgumentsReturn.new(
146
- arguments: as,
147
- return_value: ret,
148
- exception: nil
149
- )
150
-
151
- hook.logger.debug { "#{method_name} returns from block: #{hook.inspect_(ret)}" }
152
-
153
- ret
154
- end.ruby2_keywords
155
- end
156
- end
157
-
158
- method = hook.call(self, METHOD, name)
159
- klass = hook.call(self, CLASS)
160
- singleton_klass = begin
161
- hook.call(self, SINGLETON_CLASS)
162
- rescue TypeError
163
- nil
164
- end
165
- prepended = klass.ancestors.include?(hook.instance_module) || singleton_klass&.ancestors&.include?(hook.singleton_module)
166
- exception = nil
167
- result = begin
168
- if prepended
169
- method.super_method.call(*args, &block)
170
- else
171
- # Using refinement
172
- method.call(*args, &block)
173
- end
174
- rescue Exception => e
175
- exception = e
176
- nil
177
- end
178
-
179
- hook.logger.debug { "#{method_name} returns: #{hook.inspect_(result)}" }
180
-
181
- call = CallTrace.new(method_call: ArgumentsReturn.new(arguments: args, return_value: result, exception: exception),
182
- block_calls: block_calls,
183
- block_given: block != nil)
184
-
185
- method_type_errors = method_types.map do |method_type|
186
- hook.typecheck.method_call(method_name, method_type, call, errors: [])
187
- end
188
-
189
- new_errors = []
190
-
191
- if method_type_errors.none?(&:empty?)
192
- if (best_errors = hook.find_best_errors(method_type_errors))
193
- new_errors.push(*best_errors)
194
- else
195
- new_errors << Errors::UnresolvedOverloadingError.new(
196
- klass: hook.klass,
197
- method_name: method_name,
198
- method_types: method_types
199
- )
200
- end
201
- end
202
-
203
- unless new_errors.empty?
204
- new_errors.each do |error|
205
- hook.logger.error Errors.to_string(error)
206
- end
207
-
208
- hook.errors.push(*new_errors)
209
-
210
- if hook.raise_on_error?
211
- raise Error.new(new_errors)
212
- end
213
- end
214
-
215
- result
216
- end.ruby2_keywords
217
63
  end
218
64
 
219
- def verify(instance_method: nil, singleton_method: nil, types:)
220
- method_types = types.map do |type|
221
- case type
222
- when String
223
- Parser.parse_method_type(type)
65
+ def self.hook_method_source(prefix, method_name, key)
66
+ with_name, without_name = alias_names(method_name)
67
+ full_method_name = "#{prefix}#{method_name}"
68
+
69
+ [__LINE__ + 1, <<RUBY]
70
+ def #{with_name}(*args)
71
+ ::RBS.logger.debug { "#{full_method_name} with arguments: [" + args.map(&:inspect).join(", ") + "]" }
72
+
73
+ begin
74
+ return_from_call = false
75
+ block_calls = []
76
+
77
+ if block_given?
78
+ result = __send__(:"#{without_name}", *args) do |*block_args|
79
+ return_from_block = false
80
+
81
+ begin
82
+ block_result = yield(*block_args)
83
+ return_from_block = true
84
+ ensure
85
+ exn = $!
86
+
87
+ case
88
+ when return_from_block
89
+ # Returned from yield
90
+ block_calls << ::RBS::Test::ArgumentsReturn.return(
91
+ arguments: block_args,
92
+ value: block_result
93
+ )
94
+ when exn
95
+ # Exception
96
+ block_calls << ::RBS::Test::ArgumentsReturn.exception(
97
+ arguments: block_args,
98
+ exception: exn
99
+ )
224
100
  else
225
- type
101
+ # break?
102
+ block_calls << ::RBS::Test::ArgumentsReturn.break(
103
+ arguments: block_args
104
+ )
226
105
  end
227
106
  end
228
107
 
229
- case
230
- when instance_method
231
- instance_methods << instance_method
232
- call(self.instance_module, DEFINE_METHOD, instance_method, &delegation(instance_method, method_types, "##{instance_method}"))
233
- when singleton_method
234
- call(self.singleton_module, DEFINE_METHOD, singleton_method, &delegation(singleton_method, method_types, ".#{singleton_method}"))
235
- end
236
-
237
- self
108
+ block_result
238
109
  end
110
+ else
111
+ result = __send__(:"#{without_name}", *args)
112
+ end
113
+ return_from_call = true
114
+ result
115
+ ensure
116
+ exn = $!
117
+
118
+ case
119
+ when return_from_call
120
+ ::RBS.logger.debug { "#{full_method_name} return with value: " + result.inspect }
121
+ method_call = ::RBS::Test::ArgumentsReturn.return(
122
+ arguments: args,
123
+ value: result
124
+ )
125
+ when exn
126
+ ::RBS.logger.debug { "#{full_method_name} exit with exception: " + exn.inspect }
127
+ method_call = ::RBS::Test::ArgumentsReturn.exception(
128
+ arguments: args,
129
+ exception: exn
130
+ )
131
+ else
132
+ ::RBS.logger.debug { "#{full_method_name} exit with jump" }
133
+ method_call = ::RBS::Test::ArgumentsReturn.break(arguments: args)
134
+ end
239
135
 
240
- def find_best_errors(errorss)
241
- if errorss.size == 1
242
- errorss[0]
243
- else
244
- no_arity_errors = errorss.select do |errors|
245
- errors.none? do |error|
246
- error.is_a?(Errors::ArgumentError) ||
247
- error.is_a?(Errors::BlockArgumentError) ||
248
- error.is_a?(Errors::MissingBlockError) ||
249
- error.is_a?(Errors::UnexpectedBlockError)
250
- end
251
- end
136
+ trace = ::RBS::Test::CallTrace.new(
137
+ method_name: #{method_name.inspect},
138
+ method_call: method_call,
139
+ block_calls: block_calls,
140
+ block_given: block_given?,
141
+ )
252
142
 
253
- unless no_arity_errors.empty?
254
- # Choose a error set which doesn't include arity error
255
- return no_arity_errors[0] if no_arity_errors.size == 1
256
- end
257
- end
258
- end
143
+ ::RBS::Test::Observer.notify(#{key.inspect}, self, trace)
144
+ end
259
145
 
260
- def self.backtrace(skip: 2)
261
- raise
262
- rescue => exn
263
- exn.backtrace.drop(skip)
264
- end
146
+ result
147
+ end
265
148
 
266
- def run
267
- yield
268
- self
269
- ensure
270
- disable
149
+ ruby2_keywords :#{with_name}
150
+ RUBY
271
151
  end
272
152
 
273
- def call(receiver, method, *args, &block)
274
- method.bind(receiver).call(*args, &block)
275
- end
153
+ def self.hook_instance_method(klass, method, key:)
154
+ line, source = hook_method_source("#{klass}#", method, key)
276
155
 
277
- def inspect_(obj)
278
- Hook.inspect_(obj)
156
+ klass.module_eval(source, __FILE__, line)
157
+ setup_alias_method_chain klass, method
279
158
  end
280
159
 
281
- def self.inspect_(obj)
282
- obj.inspect
283
- rescue
284
- INSPECT.bind(obj).call()
285
- end
160
+ def self.hook_singleton_method(klass, method, key:)
161
+ line, source = hook_method_source("#{klass}.",method, key)
286
162
 
287
- def disable
288
- self.instance_module.remove_method(*instance_methods)
289
- self.singleton_module.remove_method(*singleton_methods)
290
- self
163
+ klass.singleton_class.module_eval(source, __FILE__, line)
164
+ setup_alias_method_chain klass.singleton_class, method
291
165
  end
292
166
  end
293
167
  end
@@ -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