rbs 0.3.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +7 -1
  3. data/.gitignore +1 -1
  4. data/CHANGELOG.md +39 -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 +315 -122
  20. data/lib/rbs/constant.rb +4 -4
  21. data/lib/rbs/constant_table.rb +51 -45
  22. data/lib/rbs/definition.rb +175 -59
  23. data/lib/rbs/definition_builder.rb +802 -604
  24. data/lib/rbs/environment.rb +352 -210
  25. data/lib/rbs/environment_walker.rb +14 -23
  26. data/lib/rbs/errors.rb +184 -3
  27. data/lib/rbs/factory.rb +14 -0
  28. data/lib/rbs/parser.y +95 -27
  29. data/lib/rbs/prototype/rb.rb +119 -117
  30. data/lib/rbs/prototype/rbi.rb +5 -3
  31. data/lib/rbs/prototype/runtime.rb +34 -7
  32. data/lib/rbs/substitution.rb +12 -1
  33. data/lib/rbs/test.rb +82 -3
  34. data/lib/rbs/test/errors.rb +5 -1
  35. data/lib/rbs/test/hook.rb +133 -259
  36. data/lib/rbs/test/observer.rb +17 -0
  37. data/lib/rbs/test/setup.rb +35 -19
  38. data/lib/rbs/test/setup_helper.rb +29 -0
  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 +43 -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 +4 -4
  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/pty/pty.rbs +159 -0
  80. data/stdlib/tmpdir/tmpdir.rbs +1 -1
  81. metadata +28 -116
  82. 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: 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,12 @@ module RBS
29
32
  case ty
30
33
  when Types::Variable
31
34
  mapping[ty.name] || ty
35
+ when Types::Bases::Instance
36
+ if instance_type
37
+ instance_type
38
+ else
39
+ ty
40
+ end
32
41
  else
33
42
  ty
34
43
  end
@@ -40,6 +49,8 @@ module RBS
40
49
  vars.each do |var|
41
50
  subst.mapping.delete(var)
42
51
  end
52
+
53
+ subst.instance_type = self.instance_type
43
54
  end
44
55
  end
45
56
  end
@@ -1,7 +1,11 @@
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"
8
+ require "rbs/test/setup_helper"
5
9
 
6
10
  module RBS
7
11
  module Test
@@ -16,11 +20,86 @@ module RBS
16
20
  INSPECT = Kernel.instance_method(:inspect)
17
21
  METHODS = Kernel.instance_method(:methods)
18
22
 
19
- ArgumentsReturn = Struct.new(:arguments, :return_value, :exception, keyword_init: true)
23
+ class ArgumentsReturn
24
+ attr_reader :arguments
25
+ attr_reader :exit_value
26
+ attr_reader :exit_type
27
+
28
+ def initialize(arguments:, exit_value:, exit_type:)
29
+ @arguments = arguments
30
+ @exit_value = exit_value
31
+ @exit_type = exit_type
32
+ end
33
+
34
+ def self.return(arguments:, value:)
35
+ new(arguments: arguments, exit_value: value, exit_type: :return)
36
+ end
37
+
38
+ def self.exception(arguments:, exception:)
39
+ new(arguments: arguments, exit_value: exception, exit_type: :exception)
40
+ end
41
+
42
+ def self.break(arguments:)
43
+ new(arguments: arguments, exit_value: nil, exit_type: :break)
44
+ end
45
+
46
+ def return_value
47
+ raise unless exit_type == :return
48
+ exit_value
49
+ end
50
+
51
+ def exception
52
+ raise unless exit_type == :exception
53
+ exit_value
54
+ end
55
+
56
+ def return?
57
+ exit_type == :return
58
+ end
59
+
60
+ def exception?
61
+ exit_type == :exception
62
+ end
63
+
64
+ def break?
65
+ exit_type == :break
66
+ end
67
+ end
68
+
20
69
  CallTrace = Struct.new(:method_name, :method_call, :block_calls, :block_given, keyword_init: true)
21
70
 
22
- def self.call(receiver, method, *args, **kwargs, &block)
23
- method.bind_call(receiver, *args, **kwargs, &block)
71
+ class <<self
72
+ attr_accessor :suffix
73
+
74
+ def reset_suffix
75
+ self.suffix = "RBS_TEST_#{SecureRandom.hex(3)}"
76
+ end
77
+ end
78
+
79
+ reset_suffix
80
+
81
+ if ::UnboundMethod.instance_methods.include?(:bind_call)
82
+ def self.call(receiver, method, *args, &block)
83
+ method.bind_call(receiver, *args, &block)
84
+ end
85
+ else
86
+ def self.call(receiver, method, *args, &block)
87
+ method.bind(receiver).call(*args, &block)
88
+ end
24
89
  end
25
90
  end
26
91
  end
92
+
93
+ unless ::Module.private_instance_methods.include?(:ruby2_keywords)
94
+ class Module
95
+ private
96
+ def ruby2_keywords(*)
97
+ end
98
+ end
99
+ end
100
+
101
+ unless ::Proc.instance_methods.include?(:ruby2_keywords)
102
+ class Proc
103
+ def ruby2_keywords; end
104
+ end
105
+ end
@@ -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