prism 0.24.0 → 0.25.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/BSDmakefile +58 -0
  3. data/CHANGELOG.md +50 -1
  4. data/Makefile +5 -2
  5. data/README.md +45 -6
  6. data/config.yml +499 -4
  7. data/docs/build_system.md +31 -0
  8. data/docs/configuration.md +2 -0
  9. data/docs/cruby_compilation.md +1 -1
  10. data/docs/parser_translation.md +14 -9
  11. data/docs/releasing.md +2 -2
  12. data/docs/ripper_translation.md +50 -0
  13. data/docs/ruby_api.md +1 -0
  14. data/docs/serialization.md +26 -5
  15. data/ext/prism/api_node.c +911 -815
  16. data/ext/prism/api_pack.c +9 -0
  17. data/ext/prism/extconf.rb +27 -11
  18. data/ext/prism/extension.c +313 -66
  19. data/ext/prism/extension.h +5 -4
  20. data/include/prism/ast.h +213 -64
  21. data/include/prism/defines.h +106 -2
  22. data/include/prism/diagnostic.h +134 -71
  23. data/include/prism/encoding.h +22 -4
  24. data/include/prism/node.h +93 -0
  25. data/include/prism/options.h +82 -7
  26. data/include/prism/pack.h +11 -0
  27. data/include/prism/parser.h +198 -53
  28. data/include/prism/prettyprint.h +8 -0
  29. data/include/prism/static_literals.h +118 -0
  30. data/include/prism/util/pm_buffer.h +65 -2
  31. data/include/prism/util/pm_constant_pool.h +18 -1
  32. data/include/prism/util/pm_integer.h +119 -0
  33. data/include/prism/util/pm_list.h +1 -1
  34. data/include/prism/util/pm_newline_list.h +8 -0
  35. data/include/prism/util/pm_string.h +26 -2
  36. data/include/prism/version.h +2 -2
  37. data/include/prism.h +59 -1
  38. data/lib/prism/compiler.rb +8 -1
  39. data/lib/prism/debug.rb +46 -3
  40. data/lib/prism/desugar_compiler.rb +1 -1
  41. data/lib/prism/dispatcher.rb +29 -0
  42. data/lib/prism/dot_visitor.rb +87 -16
  43. data/lib/prism/dsl.rb +24 -12
  44. data/lib/prism/ffi.rb +67 -12
  45. data/lib/prism/lex_compat.rb +17 -15
  46. data/lib/prism/mutation_compiler.rb +11 -0
  47. data/lib/prism/node.rb +2096 -2499
  48. data/lib/prism/node_ext.rb +77 -29
  49. data/lib/prism/pack.rb +4 -0
  50. data/lib/prism/parse_result/comments.rb +34 -17
  51. data/lib/prism/parse_result/newlines.rb +3 -1
  52. data/lib/prism/parse_result.rb +78 -32
  53. data/lib/prism/pattern.rb +16 -4
  54. data/lib/prism/polyfill/string.rb +12 -0
  55. data/lib/prism/serialize.rb +439 -102
  56. data/lib/prism/translation/parser/compiler.rb +152 -50
  57. data/lib/prism/translation/parser/lexer.rb +103 -22
  58. data/lib/prism/translation/parser/rubocop.rb +41 -13
  59. data/lib/prism/translation/parser.rb +119 -7
  60. data/lib/prism/translation/parser33.rb +1 -1
  61. data/lib/prism/translation/parser34.rb +1 -1
  62. data/lib/prism/translation/ripper/sexp.rb +125 -0
  63. data/lib/prism/translation/ripper/shim.rb +5 -0
  64. data/lib/prism/translation/ripper.rb +3212 -462
  65. data/lib/prism/translation/ruby_parser.rb +35 -18
  66. data/lib/prism/translation.rb +3 -1
  67. data/lib/prism/visitor.rb +10 -0
  68. data/lib/prism.rb +8 -2
  69. data/prism.gemspec +33 -4
  70. data/rbi/prism/compiler.rbi +14 -0
  71. data/rbi/prism/desugar_compiler.rbi +5 -0
  72. data/rbi/prism/mutation_compiler.rbi +5 -0
  73. data/rbi/prism/node.rbi +8221 -0
  74. data/rbi/prism/node_ext.rbi +102 -0
  75. data/rbi/prism/parse_result.rbi +304 -0
  76. data/rbi/prism/translation/parser/compiler.rbi +13 -0
  77. data/rbi/prism/translation/ripper/ripper_compiler.rbi +5 -0
  78. data/rbi/prism/translation/ripper.rbi +25 -0
  79. data/rbi/prism/translation/ruby_parser.rbi +11 -0
  80. data/rbi/prism/visitor.rbi +470 -0
  81. data/rbi/prism.rbi +39 -7749
  82. data/sig/prism/compiler.rbs +9 -0
  83. data/sig/prism/dispatcher.rbs +16 -0
  84. data/sig/prism/dot_visitor.rbs +6 -0
  85. data/sig/prism/dsl.rbs +462 -0
  86. data/sig/prism/mutation_compiler.rbs +158 -0
  87. data/sig/prism/node.rbs +3529 -0
  88. data/sig/prism/node_ext.rbs +78 -0
  89. data/sig/prism/pack.rbs +43 -0
  90. data/sig/prism/parse_result.rbs +127 -0
  91. data/sig/prism/pattern.rbs +13 -0
  92. data/sig/prism/serialize.rbs +7 -0
  93. data/sig/prism/visitor.rbs +168 -0
  94. data/sig/prism.rbs +188 -4767
  95. data/src/diagnostic.c +575 -230
  96. data/src/encoding.c +211 -108
  97. data/src/node.c +7526 -447
  98. data/src/options.c +36 -12
  99. data/src/pack.c +33 -17
  100. data/src/prettyprint.c +1294 -1385
  101. data/src/prism.c +3628 -1099
  102. data/src/regexp.c +17 -2
  103. data/src/serialize.c +47 -28
  104. data/src/static_literals.c +552 -0
  105. data/src/token_type.c +1 -0
  106. data/src/util/pm_buffer.c +147 -20
  107. data/src/util/pm_char.c +4 -4
  108. data/src/util/pm_constant_pool.c +35 -11
  109. data/src/util/pm_integer.c +629 -0
  110. data/src/util/pm_list.c +1 -1
  111. data/src/util/pm_newline_list.c +14 -5
  112. data/src/util/pm_string.c +134 -5
  113. data/src/util/pm_string_list.c +2 -2
  114. metadata +35 -6
  115. data/docs/ripper.md +0 -36
  116. data/rbi/prism_static.rbi +0 -207
  117. data/sig/prism_static.rbs +0 -201
@@ -49,21 +49,39 @@ module Prism
49
49
 
50
50
  class StringNode < Node
51
51
  include HeredocQuery
52
+
53
+ # Occasionally it's helpful to treat a string as if it were interpolated so
54
+ # that there's a consistent interface for working with strings.
55
+ def to_interpolated
56
+ InterpolatedStringNode.new(
57
+ source,
58
+ frozen? ? InterpolatedStringNodeFlags::FROZEN : 0,
59
+ opening_loc,
60
+ [copy(opening_loc: nil, closing_loc: nil, location: content_loc)],
61
+ closing_loc,
62
+ location
63
+ )
64
+ end
52
65
  end
53
66
 
54
67
  class XStringNode < Node
55
68
  include HeredocQuery
56
- end
57
-
58
- private_constant :HeredocQuery
59
69
 
60
- class FloatNode < Node
61
- # Returns the value of the node as a Ruby Float.
62
- def value
63
- Float(slice)
70
+ # Occasionally it's helpful to treat a string as if it were interpolated so
71
+ # that there's a consistent interface for working with strings.
72
+ def to_interpolated
73
+ InterpolatedXStringNode.new(
74
+ source,
75
+ opening_loc,
76
+ [StringNode.new(source, 0, nil, content_loc, nil, unescaped, content_loc)],
77
+ closing_loc,
78
+ location
79
+ )
64
80
  end
65
81
  end
66
82
 
83
+ private_constant :HeredocQuery
84
+
67
85
  class ImaginaryNode < Node
68
86
  # Returns the value of the node as a Ruby Complex.
69
87
  def value
@@ -71,13 +89,6 @@ module Prism
71
89
  end
72
90
  end
73
91
 
74
- class IntegerNode < Node
75
- # Returns the value of the node as a Ruby Integer.
76
- def value
77
- Integer(slice)
78
- end
79
- end
80
-
81
92
  class RationalNode < Node
82
93
  # Returns the value of the node as a Ruby Rational.
83
94
  def value
@@ -98,6 +109,19 @@ module Prism
98
109
  end
99
110
  end
100
111
 
112
+ class ConstantWriteNode < Node
113
+ # Returns the list of parts for the full name of this constant.
114
+ # For example: [:Foo]
115
+ def full_name_parts
116
+ [name]
117
+ end
118
+
119
+ # Returns the full name of this constant. For example: "Foo"
120
+ def full_name
121
+ name.to_s
122
+ end
123
+ end
124
+
101
125
  class ConstantPathNode < Node
102
126
  # An error class raised when dynamic parts are found while computing a
103
127
  # constant path's full name. For example:
@@ -107,14 +131,23 @@ module Prism
107
131
  # local variable
108
132
  class DynamicPartsInConstantPathError < StandardError; end
109
133
 
134
+ # An error class raised when missing nodes are found while computing a
135
+ # constant path's full name. For example:
136
+ # Foo:: -> raises because the constant path is missing the last part
137
+ class MissingNodesInConstantPathError < StandardError; end
138
+
110
139
  # Returns the list of parts for the full name of this constant path.
111
140
  # For example: [:Foo, :Bar]
112
141
  def full_name_parts
113
- parts = [child.name]
114
- current = parent
142
+ parts = [] #: Array[Symbol]
143
+ current = self #: node?
115
144
 
116
145
  while current.is_a?(ConstantPathNode)
117
- parts.unshift(current.child.name)
146
+ child = current.child
147
+ if child.is_a?(MissingNode)
148
+ raise MissingNodesInConstantPathError, "Constant path contains missing nodes. Cannot compute full name"
149
+ end
150
+ parts.unshift(child.name)
118
151
  current = current.parent
119
152
  end
120
153
 
@@ -135,14 +168,19 @@ module Prism
135
168
  # Returns the list of parts for the full name of this constant path.
136
169
  # For example: [:Foo, :Bar]
137
170
  def full_name_parts
138
- parts = case parent
139
- when ConstantPathNode, ConstantReadNode
140
- parent.full_name_parts
141
- when nil
142
- [:""]
143
- else
144
- raise ConstantPathNode::DynamicPartsInConstantPathError,
145
- "Constant path target contains dynamic parts. Cannot compute full name"
171
+ parts =
172
+ case parent
173
+ when ConstantPathNode, ConstantReadNode
174
+ parent.full_name_parts
175
+ when nil
176
+ [:""]
177
+ else
178
+ # e.g. self::Foo, (var)::Bar = baz
179
+ raise ConstantPathNode::DynamicPartsInConstantPathError, "Constant target path contains dynamic parts. Cannot compute full name"
180
+ end
181
+
182
+ if child.is_a?(MissingNode)
183
+ raise ConstantPathNode::MissingNodesInConstantPathError, "Constant target path contains missing nodes. Cannot compute full name"
146
184
  end
147
185
 
148
186
  parts.push(child.name)
@@ -170,22 +208,32 @@ module Prism
170
208
  class ParametersNode < Node
171
209
  # Mirrors the Method#parameters method.
172
210
  def signature
173
- names = []
211
+ names = [] #: Array[[Symbol, Symbol] | [Symbol]]
174
212
 
175
213
  requireds.each do |param|
176
214
  names << (param.is_a?(MultiTargetNode) ? [:req] : [:req, param.name])
177
215
  end
178
216
 
179
217
  optionals.each { |param| names << [:opt, param.name] }
180
- names << [:rest, rest.name || :*] if rest
218
+
219
+ if rest && rest.is_a?(RestParameterNode)
220
+ names << [:rest, rest.name || :*]
221
+ end
181
222
 
182
223
  posts.each do |param|
183
- names << (param.is_a?(MultiTargetNode) ? [:req] : [:req, param.name])
224
+ if param.is_a?(MultiTargetNode)
225
+ names << [:req]
226
+ elsif param.is_a?(NoKeywordsParameterNode)
227
+ # Invalid syntax, e.g. "def f(**nil, ...)" moves the NoKeywordsParameterNode to posts
228
+ raise "Invalid syntax"
229
+ else
230
+ names << [:req, param.name]
231
+ end
184
232
  end
185
233
 
186
234
  # Regardless of the order in which the keywords were defined, the required
187
235
  # keywords always come first followed by the optional keywords.
188
- keyopt = []
236
+ keyopt = [] #: Array[OptionalKeywordParameterNode]
189
237
  keywords.each do |param|
190
238
  if param.is_a?(OptionalKeywordParameterNode)
191
239
  keyopt << param
data/lib/prism/pack.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ # typed: ignore
2
3
 
3
4
  module Prism
4
5
  # A parser for the pack template language.
@@ -148,6 +149,8 @@ module Prism
148
149
  end
149
150
  when LENGTH_MAX
150
151
  base + ", as many as possible"
152
+ else
153
+ raise
151
154
  end
152
155
  when UTF8
153
156
  "UTF-8 character"
@@ -214,6 +217,7 @@ module Prism
214
217
  else
215
218
  source = directive.source
216
219
  end
220
+ # @type var source_width: Integer
217
221
  " #{source.ljust(source_width)} #{directive.describe}"
218
222
  end
219
223
 
@@ -27,11 +27,11 @@ module Prism
27
27
  end
28
28
 
29
29
  def start_offset
30
- node.location.start_offset
30
+ node.start_offset
31
31
  end
32
32
 
33
33
  def end_offset
34
- node.location.end_offset
34
+ node.end_offset
35
35
  end
36
36
 
37
37
  def encloses?(comment)
@@ -39,8 +39,12 @@ module Prism
39
39
  comment.location.end_offset <= end_offset
40
40
  end
41
41
 
42
- def <<(comment)
43
- node.location.comments << comment
42
+ def leading_comment(comment)
43
+ node.location.leading_comment(comment)
44
+ end
45
+
46
+ def trailing_comment(comment)
47
+ node.location.trailing_comment(comment)
44
48
  end
45
49
  end
46
50
 
@@ -65,8 +69,12 @@ module Prism
65
69
  false
66
70
  end
67
71
 
68
- def <<(comment)
69
- location.comments << comment
72
+ def leading_comment(comment)
73
+ location.leading_comment(comment)
74
+ end
75
+
76
+ def trailing_comment(comment)
77
+ location.trailing_comment(comment)
70
78
  end
71
79
  end
72
80
 
@@ -84,15 +92,23 @@ module Prism
84
92
  def attach!
85
93
  parse_result.comments.each do |comment|
86
94
  preceding, enclosing, following = nearest_targets(parse_result.value, comment)
87
- target =
88
- if comment.trailing?
89
- preceding || following || enclosing || NodeTarget.new(parse_result.value)
95
+
96
+ if comment.trailing?
97
+ if preceding
98
+ preceding.trailing_comment(comment)
90
99
  else
91
- # If a comment exists on its own line, prefer a leading comment.
92
- following || preceding || enclosing || NodeTarget.new(parse_result.value)
100
+ (following || enclosing || NodeTarget.new(parse_result.value)).leading_comment(comment)
93
101
  end
94
-
95
- target << comment
102
+ else
103
+ # If a comment exists on its own line, prefer a leading comment.
104
+ if following
105
+ following.leading_comment(comment)
106
+ elsif preceding
107
+ preceding.trailing_comment(comment)
108
+ else
109
+ (enclosing || NodeTarget.new(parse_result.value)).leading_comment(comment)
110
+ end
111
+ end
96
112
  end
97
113
  end
98
114
 
@@ -104,7 +120,7 @@ module Prism
104
120
  comment_start = comment.location.start_offset
105
121
  comment_end = comment.location.end_offset
106
122
 
107
- targets = []
123
+ targets = [] #: Array[_Target]
108
124
  node.comment_targets.map do |value|
109
125
  case value
110
126
  when StatementsNode
@@ -117,8 +133,8 @@ module Prism
117
133
  end
118
134
 
119
135
  targets.sort_by!(&:start_offset)
120
- preceding = nil
121
- following = nil
136
+ preceding = nil #: _Target?
137
+ following = nil #: _Target?
122
138
 
123
139
  left = 0
124
140
  right = targets.length
@@ -134,6 +150,7 @@ module Prism
134
150
  target_end = target.end_offset
135
151
 
136
152
  if target.encloses?(comment)
153
+ # @type var target: NodeTarget
137
154
  # The comment is completely contained by this target. Abandon the
138
155
  # binary search at this level.
139
156
  return nearest_targets(target.node, comment)
@@ -171,7 +188,7 @@ module Prism
171
188
 
172
189
  # Attach the list of comments to their respective locations in the tree.
173
190
  def attach_comments!
174
- Comments.new(self).attach!
191
+ Comments.new(self).attach! # steep:ignore
175
192
  end
176
193
  end
177
194
  end
@@ -58,7 +58,9 @@ module Prism
58
58
 
59
59
  # Walk the tree and mark nodes that are on a new line.
60
60
  def mark_newlines!
61
- value.accept(Newlines.new(Array.new(1 + source.offsets.size, false)))
61
+ value = self.value
62
+ raise "This method should only be called on a parse result that contains a node" unless Node === value
63
+ value.accept(Newlines.new(Array.new(1 + source.offsets.size, false))) # steep:ignore
62
64
  end
63
65
  end
64
66
  end
@@ -21,10 +21,16 @@ module Prism
21
21
  @offsets = offsets # set after parsing is done
22
22
  end
23
23
 
24
+ # Returns the encoding of the source code, which is set by parameters to the
25
+ # parser or by the encoding magic comment.
26
+ def encoding
27
+ source.encoding
28
+ end
29
+
24
30
  # Perform a byteslice on the source code using the given byte offset and
25
31
  # byte length.
26
32
  def slice(byte_offset, length)
27
- source.byteslice(byte_offset, length)
33
+ source.byteslice(byte_offset, length) or raise
28
34
  end
29
35
 
30
36
  # Binary search through the offsets to find the line number for the given
@@ -46,7 +52,7 @@ module Prism
46
52
 
47
53
  # Return the character offset for the given byte offset.
48
54
  def character_offset(byte_offset)
49
- source.byteslice(0, byte_offset).length
55
+ (source.byteslice(0, byte_offset) or raise).length
50
56
  end
51
57
 
52
58
  # Return the column number in characters for the given byte offset.
@@ -61,7 +67,7 @@ module Prism
61
67
  # concept of code units that differs from the number of characters in other
62
68
  # encodings, it is not captured here.
63
69
  def code_units_offset(byte_offset, encoding)
64
- byteslice = source.byteslice(0, byte_offset).encode(encoding)
70
+ byteslice = (source.byteslice(0, byte_offset) or raise).encode(encoding)
65
71
  (encoding == Encoding::UTF_16LE || encoding == Encoding::UTF_16BE) ? (byteslice.bytesize / 2) : byteslice.length
66
72
  end
67
73
 
@@ -81,9 +87,9 @@ module Prism
81
87
 
82
88
  while left <= right
83
89
  mid = left + (right - left) / 2
84
- return mid if offsets[mid] == byte_offset
90
+ return mid if (offset = offsets[mid]) == byte_offset
85
91
 
86
- if offsets[mid] < byte_offset
92
+ if offset < byte_offset
87
93
  left = mid + 1
88
94
  else
89
95
  right = mid - 1
@@ -108,25 +114,51 @@ module Prism
108
114
  # The length of this location in bytes.
109
115
  attr_reader :length
110
116
 
111
- # The list of comments attached to this location
112
- attr_reader :comments
113
-
114
117
  # Create a new location object with the given source, start byte offset, and
115
118
  # byte length.
116
119
  def initialize(source, start_offset, length)
117
120
  @source = source
118
121
  @start_offset = start_offset
119
122
  @length = length
120
- @comments = []
123
+
124
+ # These are used to store comments that are associated with this location.
125
+ # They are initialized to `nil` to save on memory when there are no
126
+ # comments to be attached and/or the comment-related APIs are not used.
127
+ @leading_comments = nil
128
+ @trailing_comments = nil
129
+ end
130
+
131
+ # These are the comments that are associated with this location that exist
132
+ # before the start of this location.
133
+ def leading_comments
134
+ @leading_comments ||= []
135
+ end
136
+
137
+ # Attach a comment to the leading comments of this location.
138
+ def leading_comment(comment)
139
+ leading_comments << comment
140
+ end
141
+
142
+ # These are the comments that are associated with this location that exist
143
+ # after the end of this location.
144
+ def trailing_comments
145
+ @trailing_comments ||= []
146
+ end
147
+
148
+ # Attach a comment to the trailing comments of this location.
149
+ def trailing_comment(comment)
150
+ trailing_comments << comment
151
+ end
152
+
153
+ # Returns all comments that are associated with this location (both leading
154
+ # and trailing comments).
155
+ def comments
156
+ [*@leading_comments, *@trailing_comments]
121
157
  end
122
158
 
123
159
  # Create a new location object with the given options.
124
- def copy(**options)
125
- Location.new(
126
- options.fetch(:source) { source },
127
- options.fetch(:start_offset) { start_offset },
128
- options.fetch(:length) { length }
129
- )
160
+ def copy(source: self.source, start_offset: self.start_offset, length: self.length)
161
+ Location.new(source, start_offset, length)
130
162
  end
131
163
 
132
164
  # Returns a string representation of this location.
@@ -230,7 +262,7 @@ module Prism
230
262
 
231
263
  # Returns true if the given other location is equal to this location.
232
264
  def ==(other)
233
- other.is_a?(Location) &&
265
+ Location === other &&
234
266
  other.start_offset == start_offset &&
235
267
  other.end_offset == end_offset
236
268
  end
@@ -244,13 +276,6 @@ module Prism
244
276
 
245
277
  Location.new(source, start_offset, other.end_offset - start_offset)
246
278
  end
247
-
248
- # Returns a null location that does not correspond to a source and points to
249
- # the beginning of the file. Useful for when you want a location object but
250
- # do not care where it points.
251
- def self.null
252
- new(nil, 0, 0)
253
- end
254
279
  end
255
280
 
256
281
  # This represents a comment that was encountered during parsing. It is the
@@ -268,6 +293,11 @@ module Prism
268
293
  def deconstruct_keys(keys)
269
294
  { location: location }
270
295
  end
296
+
297
+ # Returns the content of the comment by slicing it from the source code.
298
+ def slice
299
+ location.slice
300
+ end
271
301
  end
272
302
 
273
303
  # InlineComment objects are the most common. They correspond to comments in
@@ -336,6 +366,10 @@ module Prism
336
366
 
337
367
  # This represents an error that was encountered during parsing.
338
368
  class ParseError
369
+ # The type of error. This is an _internal_ symbol that is used for
370
+ # communicating with translation layers. It is not meant to be public API.
371
+ attr_reader :type
372
+
339
373
  # The message associated with this error.
340
374
  attr_reader :message
341
375
 
@@ -346,7 +380,8 @@ module Prism
346
380
  attr_reader :level
347
381
 
348
382
  # Create a new error object with the given message and location.
349
- def initialize(message, location, level)
383
+ def initialize(type, message, location, level)
384
+ @type = type
350
385
  @message = message
351
386
  @location = location
352
387
  @level = level
@@ -354,17 +389,21 @@ module Prism
354
389
 
355
390
  # Implement the hash pattern matching interface for ParseError.
356
391
  def deconstruct_keys(keys)
357
- { message: message, location: location, level: level }
392
+ { type: type, message: message, location: location, level: level }
358
393
  end
359
394
 
360
395
  # Returns a string representation of this error.
361
396
  def inspect
362
- "#<Prism::ParseError @message=#{@message.inspect} @location=#{@location.inspect} @level=#{@level.inspect}>"
397
+ "#<Prism::ParseError @type=#{@type.inspect} @message=#{@message.inspect} @location=#{@location.inspect} @level=#{@level.inspect}>"
363
398
  end
364
399
  end
365
400
 
366
401
  # This represents a warning that was encountered during parsing.
367
402
  class ParseWarning
403
+ # The type of warning. This is an _internal_ symbol that is used for
404
+ # communicating with translation layers. It is not meant to be public API.
405
+ attr_reader :type
406
+
368
407
  # The message associated with this warning.
369
408
  attr_reader :message
370
409
 
@@ -375,7 +414,8 @@ module Prism
375
414
  attr_reader :level
376
415
 
377
416
  # Create a new warning object with the given message and location.
378
- def initialize(message, location, level)
417
+ def initialize(type, message, location, level)
418
+ @type = type
379
419
  @message = message
380
420
  @location = location
381
421
  @level = level
@@ -383,12 +423,12 @@ module Prism
383
423
 
384
424
  # Implement the hash pattern matching interface for ParseWarning.
385
425
  def deconstruct_keys(keys)
386
- { message: message, location: location, level: level }
426
+ { type: type, message: message, location: location, level: level }
387
427
  end
388
428
 
389
429
  # Returns a string representation of this warning.
390
430
  def inspect
391
- "#<Prism::ParseWarning @message=#{@message.inspect} @location=#{@location.inspect} @level=#{@level.inspect}>"
431
+ "#<Prism::ParseWarning @type=#{@type.inspect} @message=#{@message.inspect} @location=#{@location.inspect} @level=#{@level.inspect}>"
392
432
  end
393
433
  end
394
434
 
@@ -437,6 +477,11 @@ module Prism
437
477
  { value: value, comments: comments, magic_comments: magic_comments, data_loc: data_loc, errors: errors, warnings: warnings }
438
478
  end
439
479
 
480
+ # Returns the encoding of the source code that was parsed.
481
+ def encoding
482
+ source.encoding
483
+ end
484
+
440
485
  # Returns true if there were no errors during parsing and false if there
441
486
  # were.
442
487
  def success?
@@ -477,8 +522,9 @@ module Prism
477
522
 
478
523
  # A Location object representing the location of this token in the source.
479
524
  def location
480
- return @location if @location.is_a?(Location)
481
- @location = Location.new(source, @location >> 32, @location & 0xFFFFFFFF)
525
+ location = @location
526
+ return location if location.is_a?(Location)
527
+ @location = Location.new(source, location >> 32, location & 0xFFFFFFFF)
482
528
  end
483
529
 
484
530
  # Implement the pretty print interface for Token.
@@ -498,7 +544,7 @@ module Prism
498
544
 
499
545
  # Returns true if the given other token is equal to this token.
500
546
  def ==(other)
501
- other.is_a?(Token) &&
547
+ Token === other &&
502
548
  other.type == type &&
503
549
  other.value == value
504
550
  end
data/lib/prism/pattern.rb CHANGED
@@ -69,7 +69,14 @@ module Prism
69
69
  # nodes.
70
70
  def compile
71
71
  result = Prism.parse("case nil\nin #{query}\nend")
72
- compile_node(result.value.statements.body.last.conditions.last.pattern)
72
+
73
+ case_match_node = result.value.statements.body.last
74
+ raise CompilationError, case_match_node.inspect unless case_match_node.is_a?(CaseMatchNode)
75
+
76
+ in_node = case_match_node.conditions.last
77
+ raise CompilationError, in_node.inspect unless in_node.is_a?(InNode)
78
+
79
+ compile_node(in_node.pattern)
73
80
  end
74
81
 
75
82
  # Scan the given node and all of its children for nodes that match the
@@ -77,13 +84,13 @@ module Prism
77
84
  # matches the pattern. If no block is given, an enumerator will be returned
78
85
  # that will yield each node that matches the pattern.
79
86
  def scan(root)
80
- return to_enum(__method__, root) unless block_given?
87
+ return to_enum(:scan, root) unless block_given?
81
88
 
82
89
  @compiled ||= compile
83
90
  queue = [root]
84
91
 
85
92
  while (node = queue.shift)
86
- yield node if @compiled.call(node)
93
+ yield node if @compiled.call(node) # steep:ignore
87
94
  queue.concat(node.compact_child_nodes)
88
95
  end
89
96
  end
@@ -174,7 +181,12 @@ module Prism
174
181
 
175
182
  preprocessed =
176
183
  node.elements.to_h do |element|
177
- [element.key.unescaped.to_sym, compile_node(element.value)]
184
+ key = element.key
185
+ if key.is_a?(SymbolNode)
186
+ [key.unescaped.to_sym, compile_node(element.value)]
187
+ else
188
+ raise CompilationError, element.inspect
189
+ end
178
190
  end
179
191
 
180
192
  compiled_keywords = ->(other) do
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Polyfill for String#unpack1 with the offset parameter.
4
+ if String.instance_method(:unpack1).parameters.none? { |_, name| name == :offset }
5
+ String.prepend(
6
+ Module.new {
7
+ def unpack1(format, offset: 0) # :nodoc:
8
+ offset == 0 ? super(format) : self[offset..].unpack1(format) # steep:ignore
9
+ end
10
+ }
11
+ )
12
+ end