rbs 4.0.3 → 4.1.3

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 (155) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +37 -0
  3. data/.github/dependabot.yml +1 -1
  4. data/.github/workflows/bundle-update.yml +2 -2
  5. data/.github/workflows/c-check.yml +14 -6
  6. data/.github/workflows/comments.yml +2 -2
  7. data/.github/workflows/dependabot.yml +2 -2
  8. data/.github/workflows/jruby.yml +74 -0
  9. data/.github/workflows/release-gems.yml +235 -0
  10. data/.github/workflows/ruby.yml +43 -3
  11. data/.github/workflows/rust.yml +13 -10
  12. data/.github/workflows/truffleruby.yml +54 -0
  13. data/.github/workflows/typecheck.yml +5 -2
  14. data/.github/workflows/wasm.yml +55 -0
  15. data/.github/workflows/windows.yml +5 -2
  16. data/.gitignore +8 -0
  17. data/CHANGELOG.md +113 -0
  18. data/Dockerfile.jruby +53 -0
  19. data/README.md +4 -4
  20. data/Rakefile +512 -82
  21. data/Steepfile +9 -0
  22. data/config.yml +26 -0
  23. data/core/array.rbs +243 -180
  24. data/core/builtin.rbs +7 -6
  25. data/core/class.rbs +5 -3
  26. data/core/enumerable.rbs +109 -109
  27. data/core/enumerator/product.rbs +5 -5
  28. data/core/enumerator.rbs +28 -28
  29. data/core/file.rbs +24 -1018
  30. data/core/file_constants.rbs +463 -0
  31. data/core/file_stat.rbs +534 -0
  32. data/core/float.rbs +0 -24
  33. data/core/hash.rbs +117 -101
  34. data/core/integer.rbs +21 -58
  35. data/core/io.rbs +25 -7
  36. data/core/kernel.rbs +26 -11
  37. data/core/match_data.rbs +1 -1
  38. data/core/module.rbs +88 -74
  39. data/core/numeric.rbs +3 -0
  40. data/core/object_space/weak_key_map.rbs +7 -7
  41. data/core/pathname.rbs +0 -10
  42. data/core/ractor.rbs +0 -10
  43. data/core/range.rbs +23 -23
  44. data/core/rbs/ops.rbs +154 -0
  45. data/core/rbs/unnamed/argf.rbs +3 -3
  46. data/core/ruby_vm.rbs +40 -0
  47. data/core/rubygems/errors.rbs +4 -1
  48. data/core/rubygems/requirement.rbs +0 -10
  49. data/core/rubygems/rubygems.rbs +4 -1
  50. data/core/rubygems/specification.rbs +8 -0
  51. data/core/rubygems/version.rbs +0 -160
  52. data/core/set.rbs +3 -3
  53. data/core/struct.rbs +16 -16
  54. data/core/thread.rbs +9 -14
  55. data/docs/CONTRIBUTING.md +2 -1
  56. data/docs/inline.md +65 -7
  57. data/docs/rbs_by_example.md +20 -20
  58. data/docs/release.md +247 -0
  59. data/docs/syntax.md +2 -2
  60. data/docs/wasm_serialization.md +80 -0
  61. data/ext/rbs_extension/ast_translation.c +1298 -956
  62. data/ext/rbs_extension/ast_translation.h +4 -0
  63. data/ext/rbs_extension/class_constants.c +2 -0
  64. data/ext/rbs_extension/class_constants.h +1 -0
  65. data/ext/rbs_extension/extconf.rb +1 -0
  66. data/ext/rbs_extension/legacy_location.c +11 -6
  67. data/ext/rbs_extension/main.c +139 -4
  68. data/include/rbs/ast.h +323 -298
  69. data/include/rbs/defines.h +18 -0
  70. data/include/rbs/lexer.h +1 -0
  71. data/include/rbs/serialize.h +39 -0
  72. data/lib/rbs/annotate/rdoc_annotator.rb +27 -31
  73. data/lib/rbs/ast/ruby/annotations.rb +42 -0
  74. data/lib/rbs/ast/ruby/comment_block.rb +6 -4
  75. data/lib/rbs/ast/ruby/declarations.rb +11 -1
  76. data/lib/rbs/ast/ruby/helpers/location_helper.rb +1 -1
  77. data/lib/rbs/ast/ruby/members.rb +40 -1
  78. data/lib/rbs/buffer.rb +48 -11
  79. data/lib/rbs/cli.rb +3 -5
  80. data/lib/rbs/collection/config/lockfile_generator.rb +14 -1
  81. data/lib/rbs/collection/sources/git.rb +6 -0
  82. data/lib/rbs/definition_builder/method_builder.rb +12 -6
  83. data/lib/rbs/environment.rb +10 -3
  84. data/lib/rbs/inline_parser.rb +54 -28
  85. data/lib/rbs/namespace.rb +47 -11
  86. data/lib/rbs/parser_aux.rb +4 -2
  87. data/lib/rbs/prototype/rbi.rb +193 -25
  88. data/lib/rbs/prototype/runtime.rb +2 -0
  89. data/lib/rbs/resolver/type_name_resolver.rb +12 -14
  90. data/lib/rbs/rewriter.rb +70 -0
  91. data/lib/rbs/test/type_check.rb +6 -1
  92. data/lib/rbs/type_name.rb +33 -13
  93. data/lib/rbs/unit_test/type_assertions.rb +14 -5
  94. data/lib/rbs/version.rb +1 -1
  95. data/lib/rbs/wasm/deserializer.rb +213 -0
  96. data/lib/rbs/wasm/location.rb +61 -0
  97. data/lib/rbs/wasm/parser.rb +137 -0
  98. data/lib/rbs/wasm/runtime.rb +196 -0
  99. data/lib/rbs/wasm/serialization_schema.rb +110 -0
  100. data/lib/rbs.rb +14 -2
  101. data/lib/rbs_jars.rb +39 -0
  102. data/lib/rdoc_plugin/parser.rb +5 -0
  103. data/rbs.gemspec +32 -2
  104. data/sig/annotate/rdoc_annotater.rbs +12 -9
  105. data/sig/ast/ruby/annotations.rbs +49 -0
  106. data/sig/ast/ruby/members.rbs +21 -1
  107. data/sig/buffer.rbs +19 -1
  108. data/sig/collection/config/lockfile_generator.rbs +2 -0
  109. data/sig/inline_parser.rbs +2 -0
  110. data/sig/manifest.yaml +0 -1
  111. data/sig/namespace.rbs +20 -0
  112. data/sig/parser.rbs +10 -0
  113. data/sig/prototype/rbi.rbs +33 -4
  114. data/sig/resolver/type_name_resolver.rbs +2 -4
  115. data/sig/rewriter.rbs +45 -0
  116. data/sig/typename.rbs +15 -0
  117. data/sig/unit_test/type_assertions.rbs +6 -2
  118. data/sig/wasm/deserializer.rbs +66 -0
  119. data/sig/wasm/serialization_schema.rbs +13 -0
  120. data/src/ast.c +186 -162
  121. data/src/lexer.c +220 -193
  122. data/src/lexer.re +2 -1
  123. data/src/lexstate.c +10 -1
  124. data/src/parser.c +61 -8
  125. data/src/serialize.c +958 -0
  126. data/src/util/rbs_allocator.c +14 -8
  127. data/stdlib/abbrev/0/array.rbs +1 -1
  128. data/stdlib/csv/0/csv.rbs +5 -5
  129. data/stdlib/delegate/0/delegator.rbs +2 -1
  130. data/stdlib/digest/0/digest.rbs +11 -5
  131. data/stdlib/erb/0/erb.rbs +1 -1
  132. data/stdlib/etc/0/etc.rbs +18 -4
  133. data/stdlib/fileutils/0/fileutils.rbs +21 -21
  134. data/stdlib/ipaddr/0/ipaddr.rbs +0 -5
  135. data/stdlib/json/0/json.rbs +6 -6
  136. data/stdlib/monitor/0/monitor.rbs +2 -2
  137. data/stdlib/openssl/0/openssl.rbs +46 -40
  138. data/stdlib/resolv/0/resolv.rbs +1 -1
  139. data/stdlib/shellwords/0/shellwords.rbs +1 -1
  140. data/stdlib/stringio/0/stringio.rbs +32 -10
  141. data/stdlib/strscan/0/string_scanner.rbs +74 -55
  142. data/stdlib/tempfile/0/manifest.yaml +3 -0
  143. data/stdlib/timeout/0/timeout.rbs +0 -5
  144. data/stdlib/tsort/0/cyclic.rbs +1 -1
  145. data/stdlib/tsort/0/interfaces.rbs +8 -8
  146. data/stdlib/tsort/0/tsort.rbs +9 -9
  147. data/stdlib/uri/0/generic.rbs +0 -5
  148. data/stdlib/zlib/0/gzip_reader.rbs +2 -2
  149. data/stdlib/zlib/0/zstream.rbs +0 -1
  150. data/wasm/README.md +93 -0
  151. data/wasm/rbs_wasm.c +423 -0
  152. metadata +29 -6
  153. data/.github/workflows/milestone.yml +0 -80
  154. data/.vscode/extensions.json +0 -5
  155. data/.vscode/settings.json +0 -19
@@ -29,11 +29,9 @@ module RBS
29
29
  new(all_names, aliases)
30
30
  end
31
31
 
32
- def try_cache(query)
33
- cache.fetch(query) do
34
- result = yield
35
- cache[query] = result
36
- end
32
+ def try_cache(type_name, context)
33
+ inner = cache[context] ||= {}
34
+ inner.fetch(type_name) { inner[type_name] = yield }
37
35
  end
38
36
 
39
37
  def resolve(type_name, context:)
@@ -41,7 +39,7 @@ module RBS
41
39
  return type_name
42
40
  end
43
41
 
44
- try_cache([type_name, context]) do
42
+ try_cache(type_name, context) do
45
43
  if type_name.class?
46
44
  resolve_namespace0(type_name, context, Set.new) || nil
47
45
  else
@@ -51,7 +49,7 @@ module RBS
51
49
  resolve_type_name(type_name.name, context)
52
50
  else
53
51
  if namespace = resolve_namespace0(namespace.to_type_name, context, Set.new)
54
- type_name = TypeName.new(name: type_name.name, namespace: namespace.to_namespace)
52
+ type_name = TypeName[namespace.to_namespace, type_name.name]
55
53
  has_type_name?(type_name)
56
54
  end
57
55
  end
@@ -68,7 +66,7 @@ module RBS
68
66
  raise "Type name must be a class name: #{type_name}"
69
67
  end
70
68
 
71
- try_cache([type_name, context]) do
69
+ try_cache(type_name, context) do
72
70
  ns = resolve_namespace0(type_name, context, Set.new) or return ns
73
71
  end
74
72
  end
@@ -93,10 +91,10 @@ module RBS
93
91
  resolve_type_name(type_name, outer)
94
92
  else
95
93
  has_type_name?(inner) or raise "Context must be normalized: #{inner.inspect}"
96
- has_type_name?(TypeName.new(name: type_name, namespace: inner.to_namespace)) || resolve_type_name(type_name, outer)
94
+ has_type_name?(TypeName[inner.to_namespace, type_name]) || resolve_type_name(type_name, outer)
97
95
  end
98
96
  else
99
- type_name = TypeName.new(name: type_name, namespace: Namespace.root)
97
+ type_name = TypeName[Namespace.root, type_name]
100
98
  has_type_name?(type_name)
101
99
  end
102
100
  end
@@ -109,11 +107,11 @@ module RBS
109
107
  resolve_head_namespace(head, outer)
110
108
  when TypeName
111
109
  has_type_name?(inner) or raise "Context must be normalized: #{inner.inspect}"
112
- type_name = TypeName.new(name: head, namespace: inner.to_namespace)
110
+ type_name = TypeName[inner.to_namespace, head]
113
111
  has_type_name?(type_name) || aliased_name?(type_name) || resolve_head_namespace(head, outer)
114
112
  end
115
113
  else
116
- type_name = TypeName.new(name: head, namespace: Namespace.root)
114
+ type_name = TypeName[Namespace.root, head]
117
115
  has_type_name?(type_name) || aliased_name?(type_name)
118
116
  end
119
117
  end
@@ -140,7 +138,7 @@ module RBS
140
138
 
141
139
  head =
142
140
  if type_name.absolute?
143
- root_name = TypeName.new(name: head, namespace: Namespace.root)
141
+ root_name = TypeName[Namespace.root, head]
144
142
  has_type_name?(root_name) || aliased_name?(root_name)
145
143
  else
146
144
  resolve_head_namespace(head, context)
@@ -152,7 +150,7 @@ module RBS
152
150
  end
153
151
 
154
152
  tail.inject(head) do |namespace, name|
155
- type_name = TypeName.new(name: name, namespace: namespace.to_namespace)
153
+ type_name = TypeName[namespace.to_namespace, name]
156
154
  case
157
155
  when has_type_name?(type_name)
158
156
  type_name
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBS
4
+ class Rewriter
5
+ attr_reader :buffer
6
+
7
+ def initialize(buffer)
8
+ raise "Rewriter only supports toplevel buffers" if buffer.parent
9
+
10
+ @buffer = buffer
11
+ @rewrites = []
12
+ end
13
+
14
+ def rewrite(location, string)
15
+ @rewrites.each do |existing_location, _|
16
+ if location.start_pos < existing_location.end_pos && existing_location.start_pos < location.end_pos
17
+ raise "Overlapping rewrites: #{existing_location} and #{location}"
18
+ end
19
+ end
20
+
21
+ @rewrites << [location, string]
22
+ self
23
+ end
24
+
25
+ def add_comment(*locations, content:)
26
+ earliest = locations.min_by(&:start_pos) or raise "At least one location is required"
27
+ insert_pos = earliest.start_pos
28
+ indent = " " * earliest.start_column
29
+
30
+ formatted = format_comment(content, indent)
31
+
32
+ loc = Location.new(buffer, insert_pos, insert_pos)
33
+ rewrite(loc, "#{formatted}\n#{indent}")
34
+ end
35
+
36
+ def replace_comment(comment, content:)
37
+ location = comment.location or raise "Comment must have a location"
38
+ indent = " " * location.start_column
39
+
40
+ rewrite(location, format_comment(content, indent))
41
+ end
42
+
43
+ def delete_comment(comment)
44
+ location = comment.location or raise "Comment must have a location"
45
+ line_start = location.start_pos - location.start_column
46
+ line_end = location.end_pos + 1
47
+ loc = Location.new(buffer, line_start, line_end)
48
+ rewrite(loc, "")
49
+ end
50
+
51
+ def string
52
+ result = buffer.content.dup
53
+
54
+ @rewrites.sort_by { |location, _| location.start_pos }.reverse_each do |location, replacement|
55
+ result[location.start_pos...location.end_pos] = replacement
56
+ end
57
+
58
+ result
59
+ end
60
+
61
+ private
62
+
63
+ def format_comment(content, indent)
64
+ content.lines.map do |line|
65
+ line = line.chomp
66
+ line.empty? ? "#" : "# #{line}"
67
+ end.join("\n#{indent}")
68
+ end
69
+ end
70
+ end
@@ -356,7 +356,12 @@ module RBS
356
356
  when Types::Variable
357
357
  true
358
358
  when Types::Literal
359
- type.literal == val
359
+ begin
360
+ type.literal == val
361
+ rescue NoMethodError
362
+ raise if defined?(val.==)
363
+ false
364
+ end
360
365
  when Types::Union
361
366
  type.types.any? {|type| value(val, type) }
362
367
  when Types::Intersection
data/lib/rbs/type_name.rb CHANGED
@@ -22,14 +22,40 @@ module RBS
22
22
  end
23
23
  end
24
24
 
25
+ # Process-wide flyweight cache. Two-level Hash keyed by canonical
26
+ # Namespace identity (outer uses `compare_by_identity`) and name
27
+ # Symbol.
28
+ @intern_mutex = Mutex.new
29
+ @intern_cache = {} #: Hash[Namespace, Hash[Symbol, TypeName]]
30
+ @intern_cache.compare_by_identity
31
+
32
+ # Returns a canonical `TypeName` instance for the given `namespace` /
33
+ # `name` pair. The namespace is canonicalized through `Namespace.[]`
34
+ # so identity-based lookup works regardless of the caller passing a
35
+ # fresh `Namespace.new` or an already-interned instance.
36
+ def self.[](namespace, name)
37
+ ns = Namespace[namespace.path, namespace.absolute?]
38
+
39
+ inner = @intern_cache[ns]
40
+ if inner && (cached = inner[name])
41
+ return cached
42
+ end
43
+
44
+ @intern_mutex.synchronize do
45
+ inner = (@intern_cache[ns] ||= {})
46
+ inner[name] ||= new(namespace: ns, name: name)
47
+ end
48
+ end
49
+
25
50
  def ==(other)
51
+ return true if equal?(other)
26
52
  other.is_a?(self.class) && other.namespace == namespace && other.name == name
27
53
  end
28
54
 
29
55
  alias eql? ==
30
56
 
31
57
  def hash
32
- namespace.hash ^ name.hash
58
+ @hash ||= namespace.hash ^ name.hash
33
59
  end
34
60
 
35
61
  def to_s
@@ -53,7 +79,7 @@ module RBS
53
79
  end
54
80
 
55
81
  def absolute!
56
- self.class.new(namespace: namespace.absolute!, name: name)
82
+ TypeName[namespace.absolute!, name]
57
83
  end
58
84
 
59
85
  def absolute?
@@ -61,7 +87,7 @@ module RBS
61
87
  end
62
88
 
63
89
  def relative!
64
- self.class.new(namespace: namespace.relative!, name: name)
90
+ TypeName[namespace.relative!, name]
65
91
  end
66
92
 
67
93
  def interface?
@@ -69,7 +95,7 @@ module RBS
69
95
  end
70
96
 
71
97
  def with_prefix(namespace)
72
- self.class.new(namespace: namespace + self.namespace, name: name)
98
+ TypeName[namespace + self.namespace, name]
73
99
  end
74
100
 
75
101
  def split
@@ -80,23 +106,17 @@ module RBS
80
106
  if other.absolute?
81
107
  other
82
108
  else
83
- TypeName.new(
84
- namespace: self.to_namespace + other.namespace,
85
- name: other.name
86
- )
109
+ TypeName[self.to_namespace + other.namespace, other.name]
87
110
  end
88
111
  end
89
-
112
+
90
113
  def self.parse(string)
91
114
  absolute = string.start_with?("::")
92
115
 
93
116
  *path, name = string.delete_prefix("::").split("::").map(&:to_sym)
94
117
  raise unless name
95
118
 
96
- TypeName.new(
97
- name: name,
98
- namespace: RBS::Namespace.new(path: path, absolute: absolute)
99
- )
119
+ TypeName[Namespace[path, absolute], name]
100
120
  end
101
121
  end
102
122
  end
@@ -180,11 +180,11 @@ module RBS
180
180
  )
181
181
  errors = typecheck.method_call(method, method_type, trace, errors: [])
182
182
 
183
- assert_empty errors.map {|x| RBS::Test::Errors.to_string(x) }, "Call trace does not match with given method type: #{trace.inspect}"
183
+ assert_empty errors.map {|x| RBS::Test::Errors.to_string(x) }, -> { "Call trace does not match with given method type: #{trace.inspect}" }
184
184
 
185
185
  method_defs = method_defs(method)
186
186
  all_errors = method_defs.map {|t| typecheck.method_call(method, t.type, trace, errors: [], annotations: t.each_annotation.to_a) }
187
- assert all_errors.any? {|es| es.empty? }, "Call trace does not match one of method definitions:\n #{trace.inspect}\n #{method_defs.map(&:type).join(" | ")}"
187
+ assert all_errors.any? {|es| es.empty? }, -> { "Call trace does not match one of method definitions:\n #{trace.inspect}\n #{method_defs.map(&:type).join(" | ")}" }
188
188
 
189
189
  raise exception if exception
190
190
 
@@ -204,11 +204,11 @@ module RBS
204
204
  )
205
205
  errors = typecheck.method_call(method, method_type, trace, errors: [])
206
206
 
207
- assert_empty errors.map {|x| RBS::Test::Errors.to_string(x) }, "Call trace does not match with given method type: #{trace.inspect}"
207
+ assert_empty errors.map {|x| RBS::Test::Errors.to_string(x) }, -> { "Call trace does not match with given method type: #{trace.inspect}" }
208
208
 
209
209
  method_defs = method_defs(method)
210
210
  all_errors = method_defs.map {|t| typecheck.method_call(method, t.type, trace, errors: [], annotations: t.each_annotation.to_a) }
211
- assert all_errors.any? {|es| es.empty? }, "Call trace does not match one of method definitions:\n #{trace.inspect}\n #{method_defs.map(&:type).join(" | ")}"
211
+ assert all_errors.any? {|es| es.empty? }, -> { "Call trace does not match one of method definitions:\n #{trace.inspect}\n #{method_defs.map(&:type).join(" | ")}" }
212
212
 
213
213
  # Use `instnace_of?` instead of `is_a?` as we want to check for _the exact exception class_.
214
214
  assert exception.instance_of? error_type
@@ -246,7 +246,7 @@ module RBS
246
246
 
247
247
  method_defs = method_defs(method)
248
248
  all_errors = method_defs.map {|t| typecheck.method_call(method, t.type, trace, errors: [], annotations: t.each_annotation.to_a) }
249
- assert all_errors.all? {|es| es.size > 0 }, "Call trace unexpectedly matches one of method definitions:\n #{trace.inspect}\n #{method_defs.map(&:type).join(" | ")}"
249
+ assert all_errors.all? {|es| es.size > 0 }, -> { "Call trace unexpectedly matches one of method definitions:\n #{trace.inspect}\n #{method_defs.map(&:type).join(" | ")}" }
250
250
 
251
251
  result
252
252
  end
@@ -323,6 +323,15 @@ module RBS
323
323
  assert typecheck.value(constant, definition_type), "`#{constant_name}` (#{constant.inspect}) must be compatible with RBS type definition `#{definition_type}`"
324
324
  end
325
325
 
326
+ def assert_visibility(visibility, method)
327
+ _, definition = target
328
+ method_entry = definition.methods[method]
329
+
330
+ assert method_entry, "Method `#{method}` not found in RBS definition"
331
+ assert visibility == method_entry.accessibility,
332
+ "Expected `#{method}` to be #{visibility}, but was #{method_entry.accessibility}"
333
+ end
334
+
326
335
  def assert_type(type, value)
327
336
  typecheck = RBS::Test::TypeCheck.new(
328
337
  self_class: value.class,
data/lib/rbs/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RBS
4
- VERSION = "4.0.3"
4
+ VERSION = "4.1.3"
5
5
  end
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "serialization_schema"
4
+
5
+ module RBS
6
+ module WASM
7
+ # Rebuilds RBS::AST objects from the binary buffer produced by
8
+ # `rbs_serialize_node` (src/serialize.c), driven by the generated
9
+ # SerializationSchema. This is the pure-Ruby counterpart of the C extension's
10
+ # ast_translation.c, used when the parser runs inside WebAssembly (JRuby).
11
+ #
12
+ # All locations are reconstructed through the public RBS::Location API, so the
13
+ # same code works whether RBS::Location is backed by the C extension (CRuby)
14
+ # or by a pure-Ruby implementation (JRuby).
15
+ class Deserializer
16
+ # Deserialize a buffer produced for a whole signature, returning
17
+ # `[directives, declarations]` to match RBS::Parser._parse_signature.
18
+ def self.deserialize(bytes, buffer)
19
+ new(bytes, buffer).read_node
20
+ end
21
+
22
+ # Deserialize a bare node list (rbs_serialize_node_list), e.g. the result
23
+ # of RBS::Parser._parse_type_params.
24
+ def self.deserialize_node_list(bytes, buffer)
25
+ new(bytes, buffer).read_node_list
26
+ end
27
+
28
+ # Deserialize the token stream produced by rbs_wasm_lex into the
29
+ # [type, location] pairs RBS::Parser._lex returns.
30
+ def self.deserialize_tokens(bytes, buffer)
31
+ new(bytes, buffer).read_tokens
32
+ end
33
+
34
+ def initialize(bytes, buffer)
35
+ @bytes = bytes
36
+ @buffer = buffer
37
+ # Symbols and rbs_string fields (comments, annotations) inherit the
38
+ # source encoding, matching ast_translation.c. String/Integer literal
39
+ # nodes are always UTF-8 (see read_node).
40
+ @encoding = buffer.content.encoding
41
+ @pos = 0
42
+ @class_cache = {} #: Hash[String, untyped]
43
+ end
44
+
45
+ def read_node
46
+ tag = read_u8
47
+ return nil if tag == 0
48
+ return read_string(@encoding).to_sym if tag == SerializationSchema::SYMBOL_TAG
49
+
50
+ entry = SerializationSchema::SCHEMA[tag] or raise "Unknown node tag: #{tag}"
51
+
52
+ case entry[0]
53
+ when :node then read_struct(entry)
54
+ when :bool then read_u8 != 0
55
+ when :integer then read_string(Encoding::UTF_8).to_i
56
+ when :string_value then read_string(Encoding::UTF_8)
57
+ when :record_field then [read_node, read_u8 != 0]
58
+ when :signature then [read_node_list, read_node_list]
59
+ when :namespace then RBS::Namespace[read_node_list, read_u8 != 0]
60
+ when :type_name then RBS::TypeName[read_node, read_node]
61
+ else raise "Unknown schema entry kind: #{entry[0].inspect}"
62
+ end
63
+ end
64
+
65
+ def read_node_list
66
+ Array.new(read_count) { read_node }
67
+ end
68
+
69
+ # The lex stream has no leading count: read records until the buffer is
70
+ # exhausted. Each is a token type name followed by its character range.
71
+ def read_tokens
72
+ tokens = [] #: Array[[ Symbol, Location ]]
73
+ until @pos >= @bytes.bytesize
74
+ type = read_string(Encoding::UTF_8).to_sym
75
+ start_char = read_i32
76
+ end_char = read_i32
77
+ tokens << [type, RBS::Location.new(@buffer, start_char, end_char)]
78
+ end
79
+ tokens
80
+ end
81
+
82
+ private
83
+
84
+ def read_struct(entry)
85
+ _, class_name, expose_location, loc_children, fields, resolve_type_params = entry
86
+
87
+ location = read_location(loc_children) if expose_location
88
+
89
+ kwargs = {} #: Hash[Symbol, untyped]
90
+ (fields || []).each do |name, reader|
91
+ kwargs[name] = read_field(reader)
92
+ end
93
+
94
+ RBS::AST::TypeParam.resolve_variables(kwargs[:type_params]) if resolve_type_params
95
+
96
+ klass = class_for(class_name)
97
+ if expose_location
98
+ klass.new(location: location, **kwargs)
99
+ else
100
+ klass.new(**kwargs)
101
+ end
102
+ end
103
+
104
+ def read_field(reader)
105
+ case reader
106
+ when :node then read_node
107
+ when :node_list then read_node_list
108
+ when :hash then read_hash
109
+ when :string then read_string(@encoding)
110
+ when :bool then read_u8 != 0
111
+ when :location_range then read_location_value
112
+ when :location_range_list then read_location_value_list
113
+ when :attr_ivar_name then read_attr_ivar_name
114
+ else # [:enum, [value_or_nil, ...]]
115
+ reader[1][read_u8]
116
+ end
117
+ end
118
+
119
+ def read_hash
120
+ hash = {} #: Hash[untyped, untyped]
121
+ read_count.times do
122
+ key = read_node
123
+ hash[key] = read_node
124
+ end
125
+ hash
126
+ end
127
+
128
+ # A count of nested items. Each item is at least one byte, so a count that
129
+ # exceeds the bytes remaining signals the cursor has drifted out of sync.
130
+ def read_count
131
+ count = read_u32
132
+ if count > @bytes.bytesize - @pos
133
+ raise "Corrupt buffer: count #{count} exceeds #{@bytes.bytesize - @pos} remaining bytes at offset #{@pos}"
134
+ end
135
+ count
136
+ end
137
+
138
+ # The base location of a node, followed by its named child ranges.
139
+ def read_location(loc_children)
140
+ base = read_range
141
+ children = (loc_children || []).map { |name, required| [name, required, read_range] }
142
+
143
+ return nil unless base
144
+
145
+ location = RBS::Location.new(@buffer, base[0], base[1])
146
+ children.each do |name, required, range|
147
+ if required
148
+ location.add_required_child(name, range[0]...range[1]) if range
149
+ else
150
+ location.add_optional_child(name, range ? (range[0]...range[1]) : nil)
151
+ end
152
+ end
153
+ location
154
+ end
155
+
156
+ # A standalone location range field: nil or an RBS::Location without children.
157
+ def read_location_value
158
+ range = read_range
159
+ range && RBS::Location.new(@buffer, range[0], range[1])
160
+ end
161
+
162
+ def read_location_value_list
163
+ Array.new(read_count) { read_location_value }
164
+ end
165
+
166
+ def read_attr_ivar_name
167
+ case read_u8
168
+ when 0 then nil # inferred instance variable
169
+ when 1 then false # no instance variable
170
+ else read_string(@encoding).to_sym
171
+ end
172
+ end
173
+
174
+ # Reads a presence byte and, when present, the start/end character positions.
175
+ def read_range
176
+ return nil if read_u8 == 0
177
+
178
+ start_char = read_i32
179
+ end_char = read_i32
180
+ [start_char, end_char]
181
+ end
182
+
183
+ def read_u8
184
+ byte = @bytes.getbyte(@pos) or raise "Unexpected end of buffer"
185
+ @pos += 1
186
+ byte
187
+ end
188
+
189
+ def read_u32
190
+ value = @bytes.unpack1("L<", offset: @pos) #: Integer
191
+ @pos += 4
192
+ value
193
+ end
194
+
195
+ def read_i32
196
+ value = @bytes.unpack1("l<", offset: @pos) #: Integer
197
+ @pos += 4
198
+ value
199
+ end
200
+
201
+ def read_string(encoding)
202
+ length = read_u32
203
+ string = @bytes.byteslice(@pos, length) or raise "Unexpected end of buffer"
204
+ @pos += length
205
+ string.force_encoding(encoding)
206
+ end
207
+
208
+ def class_for(name)
209
+ @class_cache[name] ||= Object.const_get(name)
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBS
4
+ # Pure-Ruby implementation of the primitives that back RBS::Location.
5
+ #
6
+ # On CRuby these come from the C extension (ext/rbs_extension/legacy_location.c).
7
+ # JRuby loads this instead, before rbs/location_aux.rb layers the public API on
8
+ # top, so RBS::Location behaves identically without the native extension.
9
+ class Location
10
+ attr_reader :buffer
11
+
12
+ def initialize(buffer, start_pos, end_pos)
13
+ @buffer = buffer
14
+ @start_pos = start_pos
15
+ @end_pos = end_pos
16
+ @required_children = {} #: Hash[Symbol, [ Integer, Integer ]]
17
+ @optional_children = {} #: Hash[Symbol, [ Integer, Integer ]?]
18
+ end
19
+
20
+ def _start_pos
21
+ @start_pos
22
+ end
23
+
24
+ def _end_pos
25
+ @end_pos
26
+ end
27
+
28
+ def _add_required_child(name, start_pos, end_pos)
29
+ @required_children[name] = [start_pos, end_pos]
30
+ end
31
+
32
+ def _add_optional_child(name, start_pos, end_pos)
33
+ @optional_children[name] = [start_pos, end_pos]
34
+ end
35
+
36
+ def _add_optional_no_child(name)
37
+ @optional_children[name] = nil
38
+ end
39
+
40
+ def _required_keys
41
+ @required_children.keys
42
+ end
43
+
44
+ def _optional_keys
45
+ @optional_children.keys
46
+ end
47
+
48
+ def [](name)
49
+ if (range = @required_children[name])
50
+ return Location.new(@buffer, range[0], range[1])
51
+ end
52
+
53
+ if @optional_children.key?(name)
54
+ range = @optional_children[name]
55
+ return range && Location.new(@buffer, range[0], range[1])
56
+ end
57
+
58
+ raise "Unknown child name given: #{name}"
59
+ end
60
+ end
61
+ end