Skip to content

Commit 63885f9

Browse files
committed
Recognize overlapping tuple patterns
1 parent b0a4d41 commit 63885f9

File tree

3 files changed

+167
-75
lines changed

3 files changed

+167
-75
lines changed

hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala

Lines changed: 13 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -41,21 +41,16 @@ class Normalization(tl: TraceLogger)(using raise: Raise):
4141
case Split.Let(name, term, tail) => Split.Let(name, term, tail ++ those)
4242
case Split.Else(_) /* impossible */ | Split.End => those)
4343

44-
/** We don't care about `Pattern.Name` because they won't appear in `specialize`. */
44+
/** We don't care about `Pattern.Var` because they won't appear in `specialize`. */
4545
extension (lhs: Pattern)
4646
/** Checks if two patterns are the same. */
4747
def =:=(rhs: Pattern): Bool = (lhs, rhs) match
4848
case (c1: Pattern.ClassLike, c2: Pattern.ClassLike) => c1.sym === c2.sym
4949
case (Pattern.Lit(l1), Pattern.Lit(l2)) => l1 === l2
5050
case (Pattern.Tuple(n1, b1), Pattern.Tuple(n2, b2)) => n1 == n2 && b1 == b2
5151
case (_, _) => false
52-
/** Checks if `self` can be subsumed under `rhs`. */
53-
def <:<(rhs: Pattern): Bool =
54-
def mk(pattern: Pattern): Option[Literal | ClassSymbol | ModuleSymbol] = lhs match
55-
case c: Pattern.ClassLike => S(c.sym)
56-
case Pattern.Lit(l) => S(l)
57-
case _ => N
58-
compareCasePattern(mk(lhs), mk(rhs))
52+
/** Checks if `lhs` can be subsumed under `rhs`. */
53+
def <:<(rhs: Pattern): Bool = compareCasePattern(lhs, rhs)
5954
/**
6055
* If two class-like patterns has different `refined` flag. Report the
6156
* inconsistency as a warning.
@@ -195,31 +190,21 @@ class Normalization(tl: TraceLogger)(using raise: Raise):
195190
end Normalization
196191

197192
object Normalization:
198-
/**
199-
* Hard-coded subtyping relations used in normalization and coverage checking.
200-
*/
201-
def compareCasePattern(
202-
lhs: Opt[Literal | ClassSymbol | ModuleSymbol],
203-
rhs: Opt[Literal | ClassSymbol | ModuleSymbol]
204-
): Bool = (lhs, rhs) match
205-
case (S(lhs), S(rhs)) => compareCasePattern(lhs, rhs)
206-
case (_, _) => false
207193
/**
208194
* Hard-coded subtyping relations used in normalization and coverage checking.
209195
* TODO use base classes and also handle modules
210196
*/
211-
def compareCasePattern(
212-
lhs: Literal | ClassSymbol | ModuleSymbol,
213-
rhs: Literal | ClassSymbol | ModuleSymbol
214-
): Bool = (lhs, rhs) match
215-
case (_, s: ClassSymbol) if s.nme === "Object" => true
216-
case (s1: ClassSymbol, s2: ClassSymbol) if s1.nme === "Int" && s2.nme === "Num" => true
197+
def compareCasePattern(lhs: Pattern, rhs: Pattern): Bool = (lhs, rhs) match
198+
case (_, Pattern.ClassLike(s: ClassSymbol, _, _, _)) if s.nme === "Object" => true
199+
case (Pattern.Tuple(n1, false), Pattern.Tuple(n2, false)) if n1 == n2 => true
200+
case (Pattern.Tuple(n1, _), Pattern.Tuple(n2, true)) if n2 <= n1 => true
201+
case (Pattern.ClassLike(s1: ClassSymbol, _, _, _), Pattern.ClassLike(s2: ClassSymbol, _, _, _)) if s1.nme === "Int" && s2.nme === "Num" => true
217202
// case (s1: ClassSymbol, s2: ClassSymbol) => s1 <:< s2 // TODO: find a way to check inheritance
218-
case (Tree.IntLit(_), s: ClassSymbol) if s.nme === "Int" || s.nme === "Num" => true
219-
case (Tree.StrLit(_), s: ClassSymbol) if s.nme === "Str" => true
220-
case (Tree.DecLit(_), s: ClassSymbol) if s.nme === "Num" => true
221-
case (Tree.BoolLit(_), s: ClassSymbol) if s.nme === "Bool" => true
222-
case (Tree.UnitLit(true), s: ClassSymbol) if s.nme === "Unit" => true // TODO: how about undefined?
203+
case (Pattern.Lit(Tree.IntLit(_)), Pattern.ClassLike(s: ClassSymbol, _, _, _)) if s.nme === "Int" || s.nme === "Num" => true
204+
case (Pattern.Lit(Tree.StrLit(_)), Pattern.ClassLike(s: ClassSymbol, _, _, _)) if s.nme === "Str" => true
205+
case (Pattern.Lit(Tree.DecLit(_)), Pattern.ClassLike(s: ClassSymbol, _, _, _)) if s.nme === "Num" => true
206+
case (Pattern.Lit(Tree.BoolLit(_)), Pattern.ClassLike(s: ClassSymbol, _, _, _)) if s.nme === "Bool" => true
207+
case (Pattern.Lit(Tree.UnitLit(true)), Pattern.ClassLike(s: ClassSymbol, _, _, _)) if s.nme === "Unit" => true // TODO: how about undefined?
223208
case (_, _) => false
224209

225210
final case class VarSet(declared: Set[BlockLocalSymbol]):

hkmc2/shared/src/test/mlscript/ucs/normalization/UnifyTupleElements.mls

Lines changed: 153 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,54 +4,165 @@
44
:import ../Prelude/Option.mls
55
//│ Imported 3 member(s)
66

7-
fun add_6(x, y) =
7+
:import ../Prelude/Tuple.mls
8+
//│ Imported 2 member(s)
9+
10+
// Normalization should unify fixed-length tuple patterns.
11+
fun sum_options(x, y) =
812
if [x, y] is
913
[Some(xv), Some(yv)] then xv + yv
1014
[Some(xv), None] then xv
1115
[None, Some(yv)] then yv
1216
[None, None] then 0
1317
//│ Desugared:
1418
//│ > if
15-
//│ > let $scrut@34 = [x@32#666, y@33#666]
16-
//│ > $scrut@34 is []=2 and
17-
//│ > let $first0@36 = $scrut@34#10.0
18-
//│ > let $first1@35 = $scrut@34#11.1
19-
//│ > $first0@36 is Some($param0@39) and
20-
//│ > let xv@41 = $param0@39#1
21-
//│ > $first1@35 is Some($param0@37) and
22-
//│ > let yv@42 = $param0@37#1
23-
//│ > else globalThis:import#Prelude#666.+‹member:+›(xv@41#666, yv@42#666)
24-
//│ > $scrut@34 is []=2 and
25-
//│ > let $first0@36 = $scrut@34#7.0
26-
//│ > let $first1@35 = $scrut@34#8.1
27-
//│ > $first0@36 is Some($param0@39) and
28-
//│ > let xv@40 = $param0@39#0
29-
//│ > $first1@35 is None then xv@40#666
30-
//│ > $scrut@34 is []=2 and
31-
//│ > let $first0@36 = $scrut@34#4.0
32-
//│ > let $first1@35 = $scrut@34#5.1
33-
//│ > $first0@36 is None and $first1@35 is Some($param0@37) and
34-
//│ > let yv@38 = $param0@37#0
35-
//│ > else yv@38#666
36-
//│ > $scrut@34 is []=2 and
37-
//│ > let $first0@36 = $scrut@34#1.0
38-
//│ > let $first1@35 = $scrut@34#2.1
39-
//│ > $first0@36 is None $first1@35 is None then 0
19+
//│ > let $scrut@49 = [x@47#666, y@48#666]
20+
//│ > $scrut@49 is []=2 and
21+
//│ > let $first0@51 = $scrut@49#10.0
22+
//│ > let $first1@50 = $scrut@49#11.1
23+
//│ > $first0@51 is Some($param0@54) and
24+
//│ > let xv@56 = $param0@54#1
25+
//│ > $first1@50 is Some($param0@52) and
26+
//│ > let yv@57 = $param0@52#1
27+
//│ > else globalThis:import#Prelude#666.+‹member:+›(xv@56#666, yv@57#666)
28+
//│ > $scrut@49 is []=2 and
29+
//│ > let $first0@51 = $scrut@49#7.0
30+
//│ > let $first1@50 = $scrut@49#8.1
31+
//│ > $first0@51 is Some($param0@54) and
32+
//│ > let xv@55 = $param0@54#0
33+
//│ > $first1@50 is None then xv@55#666
34+
//│ > $scrut@49 is []=2 and
35+
//│ > let $first0@51 = $scrut@49#4.0
36+
//│ > let $first1@50 = $scrut@49#5.1
37+
//│ > $first0@51 is None and $first1@50 is Some($param0@52) and
38+
//│ > let yv@53 = $param0@52#0
39+
//│ > else yv@53#666
40+
//│ > $scrut@49 is []=2 and
41+
//│ > let $first0@51 = $scrut@49#1.0
42+
//│ > let $first1@50 = $scrut@49#2.1
43+
//│ > $first0@51 is None $first1@50 is None then 0
44+
//│ Normalized:
45+
//│ > if
46+
//│ > let $scrut@49 = [x@47#666, y@48#666]
47+
//│ > $scrut@49 is []=2 and
48+
//│ > let $first0@51 = $scrut@49#10.0
49+
//│ > let $first1@50 = $scrut@49#11.1
50+
//│ > $first0@51 is Some($param0@54) and
51+
//│ > let xv@56 = $param0@54#1
52+
//│ > $first1@50 is Some($param0@52) and
53+
//│ > let yv@57 = $param0@52#1
54+
//│ > else globalThis:import#Prelude#666.+‹member:+›(xv@56#666, yv@57#666)
55+
//│ > let xv@55 = $param0@54#0
56+
//│ > $first1@50 is None then xv@55#666
57+
//│ > $first0@51 is None and
58+
//│ > $first1@50 is Some($param0@52) and
59+
//│ > let yv@53 = $param0@52#0
60+
//│ > else yv@53#666
61+
//│ > $first1@50 is None then 0
62+
63+
64+
65+
66+
// ========================================================================== //
67+
68+
69+
// Normalization should also unify the sub-scrutinees at the same position of
70+
// tuple patterns of different lengths.
71+
72+
73+
fun foo(xs) = if xs is
74+
[Some(x)] then x
75+
[Some(y), ..rest] then y
76+
//│ Desugared:
77+
//│ > if
78+
//│ > xs@62 is []=1 and
79+
//│ > let $first0@64 = xs@62#4.0
80+
//│ > $first0@64 is Some($param0@65) and
81+
//│ > let x@68 = $param0@65#1
82+
//│ > else x@68#666
83+
//│ > xs@62 is []>=1 and
84+
//│ > let $first0@64 = xs@62#1.0
85+
//│ > let $rest@63 = globalThis:import#Tuple#666.tupleSlice‹member:tupleSlice›(xs@62#2, 1, 0)
86+
//│ > $first0@64 is Some($param0@65) and
87+
//│ > let y@66 = $param0@65#0
88+
//│ > let rest@67 = $rest@63#0
89+
//│ > else y@66#666
4090
//│ Normalized:
4191
//│ > if
42-
//│ > let $scrut@34 = [x@32#666, y@33#666]
43-
//│ > $scrut@34 is []=2 and
44-
//│ > let $first0@36 = $scrut@34#10.0
45-
//│ > let $first1@35 = $scrut@34#11.1
46-
//│ > $first0@36 is Some($param0@39) and
47-
//│ > let xv@41 = $param0@39#1
48-
//│ > $first1@35 is Some($param0@37) and
49-
//│ > let yv@42 = $param0@37#1
50-
//│ > else globalThis:import#Prelude#666.+‹member:+›(xv@41#666, yv@42#666)
51-
//│ > let xv@40 = $param0@39#0
52-
//│ > $first1@35 is None then xv@40#666
53-
//│ > $first0@36 is None and
54-
//│ > $first1@35 is Some($param0@37) and
55-
//│ > let yv@38 = $param0@37#0
56-
//│ > else yv@38#666
57-
//│ > $first1@35 is None then 0
92+
//│ > xs@62 is []=1 and
93+
//│ > let $first0@64 = xs@62#4.0
94+
//│ > $first0@64 is Some($param0@65) and
95+
//│ > let x@68 = $param0@65#1
96+
//│ > else x@68#666
97+
//│ > let $rest@63 = globalThis:import#Tuple#666.tupleSlice‹member:tupleSlice›(xs@62#2, 1, 0)
98+
//│ > xs@62 is []>=1 and
99+
//│ > let $first0@64 = xs@62#1.0
100+
//│ > let $rest@63 = globalThis:import#Tuple#666.tupleSlice‹member:tupleSlice›(xs@62#2, 1, 0)
101+
//│ > $first0@64 is Some($param0@65) and
102+
//│ > let y@66 = $param0@65#0
103+
//│ > let rest@67 = $rest@63#0
104+
//│ > else y@66#666
105+
106+
fun do_something(x) = "done"
107+
108+
109+
fun foo(zs) = if zs is
110+
[Some(x), Some(y)] then x + y
111+
[None, ..rest] then do_something(rest)
112+
//│ Desugared:
113+
//│ > if
114+
//│ > zs@76 is []=2 and
115+
//│ > let $first0@78 = zs@76#4.0
116+
//│ > let $first1@81 = zs@76#5.1
117+
//│ > $first0@78 is Some($param0@82) and
118+
//│ > let x@83 = $param0@82#0
119+
//│ > $first1@81 is Some($param0@84) and
120+
//│ > let y@85 = $param0@84#0
121+
//│ > else globalThis:import#Prelude#666.+‹member:+›(x@83#666, y@85#666)
122+
//│ > zs@76 is []>=1 and
123+
//│ > let $first0@78 = zs@76#1.0
124+
//│ > let $rest@77 = globalThis:import#Tuple#666.tupleSlice‹member:tupleSlice›(zs@76#2, 1, 0)
125+
//│ > $first0@78 is None and
126+
//│ > let rest@79 = $rest@77#0
127+
//│ > else globalThis:block#3#666.do_something‹member:do_something›(rest@79#666)
128+
//│ Normalized:
129+
//│ > if
130+
//│ > zs@76 is []=2 and
131+
//│ > let $first0@78 = zs@76#4.0
132+
//│ > let $first1@81 = zs@76#5.1
133+
//│ > $first0@78 is Some($param0@82) and
134+
//│ > let x@83 = $param0@82#0
135+
//│ > $first1@81 is Some($param0@84) and
136+
//│ > let y@85 = $param0@84#0
137+
//│ > else globalThis:import#Prelude#666.+‹member:+›(x@83#666, y@85#666)
138+
//│ > let $rest@77 = globalThis:import#Tuple#666.tupleSlice‹member:tupleSlice›(zs@76#2, 1, 0)
139+
//│ > let $rest@77 = globalThis:import#Tuple#666.tupleSlice‹member:tupleSlice›(zs@76#2, 1, 0)
140+
//│ > $first0@78 is None and
141+
//│ > let rest@79 = $rest@77#0
142+
//│ > else globalThis:block#3#666.do_something‹member:do_something›(rest@79#666)
143+
//│ > zs@76 is []>=1 and
144+
//│ > let $first0@78 = zs@76#1.0
145+
//│ > let $rest@77 = globalThis:import#Tuple#666.tupleSlice‹member:tupleSlice›(zs@76#2, 1, 0)
146+
//│ > $first0@78 is None and
147+
//│ > let rest@79 = $rest@77#0
148+
//│ > else globalThis:block#3#666.do_something‹member:do_something›(rest@79#666)
149+
150+
:expect 4
151+
foo([Some(1), Some(3)])
152+
//│ = 4
153+
154+
:expect 'done'
155+
foo([None, Some(1), Some(2)])
156+
//│ = 'done'
157+
158+
:re
159+
foo([Some(0)])
160+
//│ ═══[RUNTIME ERROR] Error: match error
161+
162+
:re
163+
foo([Some(0), Some(1), Some(2)])
164+
//│ ═══[RUNTIME ERROR] Error: match error
165+
166+
:re
167+
foo([])
168+
//│ ═══[RUNTIME ERROR] Error: match error

hkmc2/shared/src/test/mlscript/ucs/patterns/RestTuple.mls

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,7 @@ fun stupid(xs) = if xs is
3535
//│ ys = rest;
3636
//│ return ys
3737
//│ } else {
38-
//│ if (Array.isArray(xs) && xs.length === 0) {
39-
//│ return "empty"
40-
//│ } else {
41-
//│ throw new globalThis.Error("match error")
42-
//│ }
38+
//│ throw new globalThis.Error("match error")
4339
//│ }
4440
//│ };
4541
//│ undefined

0 commit comments

Comments
 (0)