Skip to content

Commit 9076d43

Browse files
committed
Tentative support for pattern match syntax
At the moment, assignments to variables are not even supported, but we can avoid syntax errors for now.
1 parent d526bb9 commit 9076d43

18 files changed

Lines changed: 561 additions & 6 deletions

lib/typeprof/core.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
require_relative "core/ast/value"
1313
require_relative "core/ast/misc"
1414
require_relative "core/ast/meta"
15+
require_relative "core/ast/pattern"
1516
require_relative "core/ast/sig_decl"
1617
require_relative "core/ast/sig_type"
1718
require_relative "core/type"

lib/typeprof/core/ast.rb

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def self.create_node(raw_node, lenv, use_result = true)
4848
when :if_node then IfNode.new(raw_node, lenv)
4949
when :unless_node then UnlessNode.new(raw_node, lenv)
5050
when :case_node then CaseNode.new(raw_node, lenv)
51+
when :case_match_node then CaseMatchNode.new(raw_node, lenv)
5152
when :while_node then WhileNode.new(raw_node, lenv)
5253
when :until_node then UntilNode.new(raw_node, lenv)
5354
when :break_node then BreakNode.new(raw_node, lenv)
@@ -194,15 +195,15 @@ def self.create_node(raw_node, lenv, use_result = true)
194195
when :float_node then FloatNode.new(raw_node, lenv)
195196
when :rational_node then RationalNode.new(raw_node, lenv)
196197
when :imaginary_node then ComplexNode.new(raw_node, lenv)
198+
when :source_file_node then StringNode.new(raw_node, lenv, "")
199+
when :source_line_node then IntegerNode.new(raw_node, lenv, 0)
200+
when :source_encoding_node then SourceEncodingNode.new(raw_node, lenv)
197201
when :symbol_node then SymbolNode.new(raw_node, lenv)
198202
when :interpolated_symbol_node then InterpolatedSymbolNode.new(raw_node, lenv)
199203
when :string_node then StringNode.new(raw_node, lenv, raw_node.content)
204+
when :interpolated_string_node then InterpolatedStringNode.new(raw_node, lenv)
200205
when :x_string_node then StringNode.new(raw_node, lenv, "")
201206
when :interpolated_x_string_node then InterpolatedStringNode.new(raw_node, lenv)
202-
when :source_file_node then StringNode.new(raw_node, lenv, "")
203-
when :source_line_node then IntegerNode.new(raw_node, lenv, 0)
204-
when :source_encoding_node then SourceEncodingNode.new(raw_node, lenv)
205-
when :interpolated_string_node then InterpolatedStringNode.new(raw_node, lenv)
206207
when :regular_expression_node then RegexpNode.new(raw_node, lenv)
207208
when :interpolated_regular_expression_node then InterpolatedRegexpNode.new(raw_node, lenv)
208209
when :match_last_line_node then MatchLastLineNode.new(raw_node, lenv)
@@ -220,8 +221,9 @@ def self.create_node(raw_node, lenv, use_result = true)
220221
when :alias_global_variable_node then AliasGlobalVariableNode.new(raw_node, lenv)
221222
when :post_execution_node then PostExecutionNode.new(raw_node, lenv)
222223
when :flip_flop_node then FlipFlopNode.new(raw_node, lenv)
223-
when :shareable_constant_node
224-
create_node(raw_node.write, lenv)
224+
when :shareable_constant_node then create_node(raw_node.write, lenv)
225+
when :match_required_node then MatchRequiredNode.new(raw_node, lenv)
226+
when :match_predicate_node then MatchPreidcateNode.new(raw_node, lenv)
225227

226228
# call
227229
when :super_node then SuperNode.new(raw_node, lenv)
@@ -271,6 +273,67 @@ def self.create_target_node(raw_node, lenv)
271273
end
272274
end
273275

276+
def self.create_pattern_node(raw_node, lenv)
277+
while true
278+
case raw_node.type
279+
when :parentheses_node
280+
raw_node = raw_node.body
281+
when :implicit_node
282+
raw_node = raw_node.value
283+
else
284+
break
285+
end
286+
end
287+
288+
case raw_node.type
289+
when :array_pattern_node then ArrayPatternNode.new(raw_node, lenv)
290+
when :hash_pattern_node then HashPatternNode.new(raw_node, lenv)
291+
when :find_pattern_node then FindPatternNode.new(raw_node, lenv)
292+
293+
when :alternation_pattern_node then AltPatternNode.new(raw_node, lenv)
294+
295+
when :capture_pattern_node then CapturePatternNode.new(raw_node, lenv)
296+
297+
when :if_node then IfPatternNode.new(raw_node, lenv)
298+
299+
when :pinned_variable_node then PinnedPatternNode.new(raw_node, lenv)
300+
when :pinned_expression_node then PinnedPatternNode.new(raw_node, lenv)
301+
302+
when :local_variable_target_node
303+
dummy_node = DummyRHSNode.new(TypeProf::CodeRange.from_node(raw_node.location), lenv)
304+
LocalVariableWriteNode.new(raw_node, dummy_node, lenv)
305+
306+
when :constant_read_node, :constant_path_node
307+
ConstantReadNode.new(raw_node, lenv)
308+
309+
when :self_node then SelfNode.new(raw_node, lenv)
310+
when :nil_node then NilNode.new(raw_node, lenv)
311+
when :true_node then TrueNode.new(raw_node, lenv)
312+
when :false_node then FalseNode.new(raw_node, lenv)
313+
when :integer_node then IntegerNode.new(raw_node, lenv)
314+
when :float_node then FloatNode.new(raw_node, lenv)
315+
when :rational_node then RationalNode.new(raw_node, lenv)
316+
when :imaginary_node then ComplexNode.new(raw_node, lenv)
317+
when :source_file_node then StringNode.new(raw_node, lenv, "")
318+
when :source_line_node then IntegerNode.new(raw_node, lenv, 0)
319+
when :source_encoding_node then SourceEncodingNode.new(raw_node, lenv)
320+
when :symbol_node then SymbolNode.new(raw_node, lenv)
321+
when :interpolated_symbol_node then InterpolatedSymbolNode.new(raw_node, lenv)
322+
when :string_node then StringNode.new(raw_node, lenv, raw_node.content)
323+
when :interpolated_string_node then InterpolatedStringNode.new(raw_node, lenv)
324+
when :x_string_node then StringNode.new(raw_node, lenv, "")
325+
when :interpolated_x_string_node then InterpolatedStringNode.new(raw_node, lenv)
326+
when :regular_expression_node then RegexpNode.new(raw_node, lenv)
327+
when :interpolated_regular_expression_node then InterpolatedRegexpNode.new(raw_node, lenv)
328+
329+
when :array_node then ArrayNode.new(raw_node, lenv) # for %w[foo bar]
330+
when :range_node then RangeNode.new(raw_node, lenv) # TODO: support range pattern correctly
331+
332+
else
333+
raise "unknown pattern node type: #{ raw_node.type }"
334+
end
335+
end
336+
274337
def self.parse_cpath(raw_node, cref)
275338
names = []
276339
while raw_node

lib/typeprof/core/ast/control.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,36 @@ def install0(genv)
261261
end
262262
end
263263

264+
class CaseMatchNode < Node
265+
def initialize(raw_node, lenv)
266+
super(raw_node, lenv)
267+
@pivot = AST.create_node(raw_node.predicate, lenv)
268+
@patterns = []
269+
@clauses = []
270+
raw_node.conditions.each do |raw_cond|
271+
raise if raw_cond.type != :in_node
272+
@patterns << AST.create_pattern_node(raw_cond.pattern, lenv)
273+
@clauses << (raw_cond.statements ? AST.create_node(raw_cond.statements, lenv) : DummyNilNode.new(code_range, lenv)) # TODO: code_range for NilNode
274+
end
275+
@else_clause = raw_node.else_clause && raw_node.else_clause.statements ? AST.create_node(raw_node.else_clause.statements, lenv) : nil
276+
end
277+
278+
attr_reader :pivot, :patterns, :clauses, :else_clause
279+
280+
def subnodes = { pivot:, patterns:, clauses:, else_clause: }
281+
282+
def install0(genv)
283+
ret = Vertex.new(self)
284+
@pivot&.install(genv)
285+
@patterns.zip(@clauses) do |pattern, clause|
286+
pattern.install(genv)
287+
@changes.add_edge(genv, clause.install(genv), ret)
288+
end
289+
@changes.add_edge(genv, @else_clause.install(genv), ret) if @else_clause
290+
ret
291+
end
292+
end
293+
264294
class AndNode < Node
265295
def initialize(raw_node, e1 = nil, raw_e2 = nil, lenv)
266296
super(raw_node, lenv)

lib/typeprof/core/ast/misc.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,5 +223,41 @@ def install0(genv)
223223
Source.new(genv.true_type, genv.false_type)
224224
end
225225
end
226+
227+
class MatchRequiredNode < Node
228+
def initialize(raw_node, lenv)
229+
super(raw_node, lenv)
230+
@value = AST.create_node(raw_node.value, lenv)
231+
@pat = AST.create_pattern_node(raw_node.pattern, lenv)
232+
end
233+
234+
attr_reader :value, :pat
235+
236+
def subnodes = { value:, pat: }
237+
238+
def install0(genv)
239+
@value.install(genv)
240+
@pat.install(genv)
241+
Source.new(genv.nil_type)
242+
end
243+
end
244+
245+
class MatchPreidcateNode < Node
246+
def initialize(raw_node, lenv)
247+
super(raw_node, lenv)
248+
@value = AST.create_node(raw_node.value, lenv)
249+
@pat = AST.create_pattern_node(raw_node.pattern, lenv)
250+
end
251+
252+
attr_reader :value, :pat
253+
254+
def subnodes = { value:, pat: }
255+
256+
def install0(genv)
257+
@value.install(genv)
258+
@pat.install(genv)
259+
Source.new(genv.true_type, genv.false_type)
260+
end
261+
end
226262
end
227263
end

lib/typeprof/core/ast/pattern.rb

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
module TypeProf::Core
2+
class AST
3+
class ArrayPatternNode < Node
4+
def initialize(raw_node, lenv)
5+
super(raw_node, lenv)
6+
@requireds = raw_node.requireds.map {|raw_pat| AST.create_pattern_node(raw_pat, lenv) }
7+
@rest = !!raw_node.rest
8+
@rest_pattern = raw_node.rest && raw_node.rest.expression ? AST.create_pattern_node(raw_node.rest.expression, lenv) : nil
9+
@posts = raw_node.posts.map {|raw_pat| AST.create_pattern_node(raw_pat, lenv) }
10+
end
11+
12+
attr_reader :requireds, :rest, :rest_pattern, :posts
13+
14+
def attrs = { rest: }
15+
def subnodes = { requireds:, rest_pattern:, posts: }
16+
17+
def install0(genv)
18+
@requireds.each do |pat|
19+
pat.install(genv)
20+
end
21+
@rest_pattern.install(genv) if @rest_pattern
22+
@posts.each do |pat|
23+
pat.install(genv)
24+
end
25+
end
26+
end
27+
28+
class HashPatternNode < Node
29+
def initialize(raw_node, lenv)
30+
super(raw_node, lenv)
31+
@keys = raw_node.elements.map {|raw_assoc| raw_assoc.key.value.to_sym }
32+
@values = raw_node.elements.map {|raw_assoc| AST.create_pattern_node(raw_assoc.value, lenv) }
33+
@rest = !!raw_node.rest
34+
@rest_pattern = raw_node.rest && raw_node.rest.value ? AST.create_pattern_node(raw_node.rest.value, lenv) : nil
35+
end
36+
37+
attr_reader :keys, :values, :rest, :rest_pattern
38+
39+
def attrs = { keys:, rest: }
40+
def subnodes = { values:, rest_pattern: }
41+
42+
def install0(genv)
43+
@values.each do |pat|
44+
pat.install(genv)
45+
end
46+
@rest_pattern.install(genv) if @rest_pattern
47+
end
48+
end
49+
50+
class FindPatternNode < Node
51+
def initialize(raw_node, lenv)
52+
super(raw_node, lenv)
53+
@left = raw_node.left ? AST.create_pattern_node(raw_node.left.expression, lenv) : nil
54+
@requireds = raw_node.requireds.map {|raw_elem| AST.create_pattern_node(raw_elem, lenv) }
55+
@right = raw_node.right ? AST.create_pattern_node(raw_node.right.expression, lenv) : nil
56+
end
57+
58+
attr_reader :left, :requireds, :right
59+
60+
def subnodes = { left:, requireds:, right: }
61+
62+
def install0(genv)
63+
@left.install(genv) if @left
64+
@requireds.each do |pat|
65+
pat.install(genv)
66+
end
67+
@right.install(genv) if @right
68+
end
69+
end
70+
71+
class AltPatternNode < Node
72+
def initialize(raw_node, lenv)
73+
super(raw_node, lenv)
74+
@left = AST.create_pattern_node(raw_node.left, lenv)
75+
@right = AST.create_pattern_node(raw_node.right, lenv)
76+
end
77+
78+
attr_reader :left, :right
79+
80+
def subnodes = { left:, right: }
81+
82+
def install0(genv)
83+
@left.install(genv)
84+
@right.install(genv)
85+
end
86+
end
87+
88+
class CapturePatternNode < Node
89+
def initialize(raw_node, lenv)
90+
super(raw_node, lenv)
91+
@value = AST.create_pattern_node(raw_node.value, lenv)
92+
@target = AST.create_pattern_node(raw_node.target, lenv)
93+
end
94+
95+
attr_reader :value, :target
96+
97+
def subnodes = { value:, target: }
98+
99+
def install0(genv)
100+
@value.install(genv)
101+
@target.install(genv)
102+
end
103+
end
104+
105+
class IfPatternNode < Node
106+
def initialize(raw_node, lenv)
107+
super(raw_node, lenv)
108+
@cond = AST.create_node(raw_node.predicate, lenv)
109+
raise if raw_node.statements.type != :statements_node
110+
raise if raw_node.statements.body.size != 1
111+
@body = AST.create_pattern_node(raw_node.statements.body[0], lenv)
112+
raise if raw_node.subsequent
113+
end
114+
115+
attr_reader :cond, :body
116+
117+
def subnodes = { cond:, body: }
118+
119+
def install0(genv)
120+
@cond.install(genv)
121+
@body.install(genv)
122+
end
123+
end
124+
125+
class PinnedPatternNode < Node
126+
def initialize(raw_node, lenv)
127+
super(raw_node, lenv)
128+
@expr = AST.create_node(raw_node.type == :pinned_variable_node ? raw_node.variable : raw_node.expression, lenv)
129+
end
130+
131+
attr_reader :expr
132+
133+
def subnodes = { expr: }
134+
135+
def install0(genv)
136+
@expr.install(genv)
137+
end
138+
end
139+
end
140+
end

scenario/patterns/alt_pat.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
## update: test.rb
2+
def check(x)
3+
case x
4+
in 1 | 2
5+
:ok
6+
in 3 | 4 | 5
7+
:ok
8+
end
9+
end
10+
11+
## assert
12+
class Object
13+
def check: (untyped) -> :ok
14+
end

scenario/patterns/array_pat.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
## update: test.rb
2+
class MyArray
3+
end
4+
5+
def check(x)
6+
case x
7+
in 1, 2, 3
8+
:foo
9+
in [1, 2, 3, *]
10+
:bar
11+
in [String]
12+
:baz # TODO: this should be excluded
13+
in MyArray[1, 2, 3]
14+
:qux
15+
else
16+
:zzz
17+
end
18+
end
19+
20+
check([1].to_a)
21+
22+
## assert
23+
class MyArray
24+
end
25+
class Object
26+
def check: (Array[Integer]) -> (:bar | :baz | :foo | :qux | :zzz)
27+
end

scenario/patterns/capture.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## update: test.rb
2+
def check(a)
3+
case a
4+
in [Integer => n, String => s]
5+
[n, s]
6+
end
7+
end
8+
9+
check([42, "foo"])

0 commit comments

Comments
 (0)