rbs 0.2.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.
Files changed (81) 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/COPYING +1 -1
  6. data/Gemfile +16 -2
  7. data/README.md +87 -48
  8. data/Rakefile +54 -22
  9. data/bin/rbs-prof +9 -0
  10. data/bin/run_in_md.rb +49 -0
  11. data/bin/test_runner.rb +0 -2
  12. data/docs/sigs.md +6 -6
  13. data/docs/stdlib.md +3 -5
  14. data/docs/syntax.md +6 -3
  15. data/goodcheck.yml +65 -0
  16. data/lib/rbs.rb +3 -0
  17. data/lib/rbs/ast/declarations.rb +115 -14
  18. data/lib/rbs/ast/members.rb +41 -17
  19. data/lib/rbs/cli.rb +301 -123
  20. data/lib/rbs/constant.rb +4 -4
  21. data/lib/rbs/constant_table.rb +64 -53
  22. data/lib/rbs/definition.rb +175 -59
  23. data/lib/rbs/definition_builder.rb +646 -603
  24. data/lib/rbs/environment.rb +352 -210
  25. data/lib/rbs/environment_walker.rb +14 -23
  26. data/lib/rbs/errors.rb +159 -3
  27. data/lib/rbs/factory.rb +14 -0
  28. data/lib/rbs/namespace.rb +18 -0
  29. data/lib/rbs/parser.y +75 -21
  30. data/lib/rbs/prototype/rb.rb +119 -117
  31. data/lib/rbs/prototype/rbi.rb +5 -3
  32. data/lib/rbs/prototype/runtime.rb +34 -7
  33. data/lib/rbs/substitution.rb +8 -1
  34. data/lib/rbs/test.rb +81 -3
  35. data/lib/rbs/test/errors.rb +1 -1
  36. data/lib/rbs/test/hook.rb +133 -259
  37. data/lib/rbs/test/observer.rb +17 -0
  38. data/lib/rbs/test/setup.rb +13 -14
  39. data/lib/rbs/test/spy.rb +0 -321
  40. data/lib/rbs/test/tester.rb +116 -0
  41. data/lib/rbs/test/type_check.rb +44 -7
  42. data/lib/rbs/type_name_resolver.rb +58 -0
  43. data/lib/rbs/types.rb +94 -2
  44. data/lib/rbs/validator.rb +51 -0
  45. data/lib/rbs/variance_calculator.rb +12 -2
  46. data/lib/rbs/version.rb +1 -1
  47. data/lib/rbs/writer.rb +127 -91
  48. data/rbs.gemspec +0 -9
  49. data/schema/annotation.json +14 -0
  50. data/schema/comment.json +26 -0
  51. data/schema/decls.json +353 -0
  52. data/schema/function.json +87 -0
  53. data/schema/location.json +56 -0
  54. data/schema/members.json +248 -0
  55. data/schema/methodType.json +44 -0
  56. data/schema/types.json +299 -0
  57. data/stdlib/benchmark/benchmark.rbs +151 -151
  58. data/stdlib/builtin/encoding.rbs +2 -0
  59. data/stdlib/builtin/enumerable.rbs +2 -2
  60. data/stdlib/builtin/enumerator.rbs +3 -1
  61. data/stdlib/builtin/fiber.rbs +5 -1
  62. data/stdlib/builtin/file.rbs +0 -3
  63. data/stdlib/builtin/io.rbs +4 -4
  64. data/stdlib/builtin/proc.rbs +1 -2
  65. data/stdlib/builtin/symbol.rbs +1 -1
  66. data/stdlib/builtin/thread.rbs +2 -2
  67. data/stdlib/csv/csv.rbs +4 -6
  68. data/stdlib/fiber/fiber.rbs +117 -0
  69. data/stdlib/json/json.rbs +1 -1
  70. data/stdlib/logger/formatter.rbs +23 -0
  71. data/stdlib/logger/log_device.rbs +39 -0
  72. data/stdlib/logger/logger.rbs +507 -0
  73. data/stdlib/logger/period.rbs +7 -0
  74. data/stdlib/logger/severity.rbs +8 -0
  75. data/stdlib/mutex_m/mutex_m.rbs +77 -0
  76. data/stdlib/pathname/pathname.rbs +6 -6
  77. data/stdlib/prime/integer-extension.rbs +1 -1
  78. data/stdlib/prime/prime.rbs +44 -44
  79. data/stdlib/tmpdir/tmpdir.rbs +1 -1
  80. metadata +26 -116
  81. data/lib/rbs/test/test_helper.rb +0 -183
@@ -1,6 +1,7 @@
1
1
  module RBS
2
2
  class Substitution
3
3
  attr_reader :mapping
4
+ attr_accessor :instance_type
4
5
 
5
6
  def initialize()
6
7
  @mapping = {}
@@ -10,7 +11,7 @@ module RBS
10
11
  mapping[from] = to
11
12
  end
12
13
 
13
- def self.build(variables, types, &block)
14
+ def self.build(variables, types, instance_type: Types::Bases::Instance.new(location: nil), &block)
14
15
  unless variables.size == types.size
15
16
  raise "Broken substitution: variables=#{variables}, types=#{types}"
16
17
  end
@@ -22,6 +23,8 @@ module RBS
22
23
  type = block_given? ? yield(t) : t
23
24
  subst.add(from: v, to: type)
24
25
  end
26
+
27
+ subst.instance_type = instance_type
25
28
  end
26
29
  end
27
30
 
@@ -29,6 +32,8 @@ module RBS
29
32
  case ty
30
33
  when Types::Variable
31
34
  mapping[ty.name] || ty
35
+ when Types::Bases::Instance
36
+ instance_type
32
37
  else
33
38
  ty
34
39
  end
@@ -40,6 +45,8 @@ module RBS
40
45
  vars.each do |var|
41
46
  subst.mapping.delete(var)
42
47
  end
48
+
49
+ subst.instance_type = self.instance_type
43
50
  end
44
51
  end
45
52
  end
@@ -1,6 +1,9 @@
1
+ require "securerandom"
2
+ require "rbs/test/observer"
1
3
  require "rbs/test/spy"
2
4
  require "rbs/test/errors"
3
5
  require "rbs/test/type_check"
6
+ require "rbs/test/tester"
4
7
  require "rbs/test/hook"
5
8
 
6
9
  module RBS
@@ -16,11 +19,86 @@ module RBS
16
19
  INSPECT = Kernel.instance_method(:inspect)
17
20
  METHODS = Kernel.instance_method(:methods)
18
21
 
19
- ArgumentsReturn = Struct.new(:arguments, :return_value, :exception, keyword_init: true)
22
+ class ArgumentsReturn
23
+ attr_reader :arguments
24
+ attr_reader :exit_value
25
+ attr_reader :exit_type
26
+
27
+ def initialize(arguments:, exit_value:, exit_type:)
28
+ @arguments = arguments
29
+ @exit_value = exit_value
30
+ @exit_type = exit_type
31
+ end
32
+
33
+ def self.return(arguments:, value:)
34
+ new(arguments: arguments, exit_value: value, exit_type: :return)
35
+ end
36
+
37
+ def self.exception(arguments:, exception:)
38
+ new(arguments: arguments, exit_value: exception, exit_type: :exception)
39
+ end
40
+
41
+ def self.break(arguments:)
42
+ new(arguments: arguments, exit_value: nil, exit_type: :break)
43
+ end
44
+
45
+ def return_value
46
+ raise unless exit_type == :return
47
+ exit_value
48
+ end
49
+
50
+ def exception
51
+ raise unless exit_type == :exception
52
+ exit_value
53
+ end
54
+
55
+ def return?
56
+ exit_type == :return
57
+ end
58
+
59
+ def exception?
60
+ exit_type == :exception
61
+ end
62
+
63
+ def break?
64
+ exit_type == :break
65
+ end
66
+ end
67
+
20
68
  CallTrace = Struct.new(:method_name, :method_call, :block_calls, :block_given, keyword_init: true)
21
69
 
22
- def self.call(receiver, method, *args, **kwargs, &block)
23
- method.bind_call(receiver, *args, **kwargs, &block)
70
+ class <<self
71
+ attr_accessor :suffix
72
+
73
+ def reset_suffix
74
+ self.suffix = "RBS_TEST_#{SecureRandom.hex(3)}"
75
+ end
76
+ end
77
+
78
+ reset_suffix
79
+
80
+ if ::UnboundMethod.instance_methods.include?(:bind_call)
81
+ def self.call(receiver, method, *args, &block)
82
+ method.bind_call(receiver, *args, &block)
83
+ end
84
+ else
85
+ def self.call(receiver, method, *args, &block)
86
+ method.bind(receiver).call(*args, &block)
87
+ end
24
88
  end
25
89
  end
26
90
  end
91
+
92
+ unless ::Module.private_instance_methods.include?(:ruby2_keywords)
93
+ class Module
94
+ private
95
+ def ruby2_keywords(*)
96
+ end
97
+ end
98
+ end
99
+
100
+ unless ::Proc.instance_methods.include?(:ruby2_keywords)
101
+ class Proc
102
+ def ruby2_keywords; end
103
+ end
104
+ end
@@ -28,7 +28,7 @@ module RBS
28
28
  end
29
29
 
30
30
  def self.inspect_(obj)
31
- Hook.inspect_(obj)
31
+ Test::INSPECT.bind(obj).call
32
32
  end
33
33
 
34
34
  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