Skip to content

Commit dc19e6a

Browse files
author
Aleksander Boruch-Gruszecki
committed
Add new space definitions and operations
1 parent 296e963 commit dc19e6a

File tree

2 files changed

+256
-0
lines changed

2 files changed

+256
-0
lines changed
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
package dotty.tools.dotc.transform.patmat
2+
package `new logic`
3+
4+
import dotty.tools.dotc.core.Symbols.Symbol
5+
import dotty.tools.dotc.core.Types.Type
6+
7+
/** Space logic for checking exhaustivity and unreachability of pattern matching.
8+
*
9+
* Space can be thought of as a set of possible values. A type or a pattern
10+
* both refer to spaces. The space of a type is the values that inhabit the
11+
* type. The space of a pattern is the values that can be covered by the
12+
* pattern.
13+
*
14+
* Space is recursively defined as follows:
15+
*
16+
* 1. For a type T, `Typ(T)` is a space
17+
* 2. `Prod(S1, S2, ..., Sn)` is a product space.
18+
*
19+
* To correctly handle GADTs, we think in terms of _constrained_ spaces.
20+
* A constrained space containst a list of spaces and represents a set of _vectors_
21+
* of values. This would be a natural representation if we could pattern match on multiple values.
22+
* Additionally, as the name suggests, a constrained space can have _constraints_,
23+
* both term and type-level. The constraints are simply predicates restricting the value vectors
24+
* contained in the set represented by a constrained space.
25+
* Finally, a list of constrained spaces is used exactly like a union would.
26+
*
27+
* For the problem of exhaustivity check, its formulation in terms of space is as follows:
28+
*
29+
* Is the space Typ(T) a subspace of the union of spaces covered by all the patterns?
30+
*
31+
* The problem of unreachable patterns can be formulated as follows:
32+
*
33+
* Is the space covered by a pattern a subspace of the spaces covered by previous patterns?
34+
*
35+
* Assumption:
36+
* (1) One case class cannot be inherited directly or indirectly by another
37+
* case class.
38+
* (2) Inheritance of a case class cannot be well handled by the algorithm.
39+
*
40+
*/
41+
trait SpaceLogic {
42+
/** Display space in string format */
43+
def show(spaces: List[Space]): String
44+
45+
def debugShow(s: ConstrainedSpace): String
46+
47+
def show(a: ConstrainedSpace): String = {
48+
val shownVec = show(a.vec)
49+
if (a.vec.length == 1) shownVec else s"[$shownVec]"
50+
}
51+
52+
def show(as: Seq[ConstrainedSpace]): String =
53+
as.map(show).mkString("{", ", ", "}")
54+
55+
def debugShow(as: Seq[ConstrainedSpace]): String =
56+
as.map(debugShow).mkString("{", ", ", "}")
57+
58+
/** Run `thunk` in a debugging block, indenting all messages */
59+
def doDebug[T](pre: => String, post: T => String)(thunk: => T): T
60+
61+
/** Is `tp1` a subtype of `tp2`? */
62+
def isSubType(tp1: Type, tp2: Type): Boolean
63+
64+
/** Is `tp1` the same type as `tp2`? */
65+
def isEqualType(tp1: Type, tp2: Type): Boolean
66+
67+
/** Return a space containing the values of both types.
68+
*
69+
* The types should be atomic (non-decomposable) and unrelated (neither
70+
* should be a subtype of the other).
71+
*/
72+
def intersectUnrelatedAtomicTypes(tp1: Type, tp2: Type): Option[Space]
73+
74+
/** Is the type `tp` decomposable? i.e. all values of the type can be covered
75+
* by its decomposed types.
76+
*
77+
* Abstract sealed class, OrType, Boolean and Java enums can be decomposed.
78+
*/
79+
def canDecompose(tp: Type): Boolean
80+
81+
/** Return term parameter types of the extractor `unapp` */
82+
def signature(unapp: Type, unappSym: Symbol, argLen: Int): List[Type]
83+
84+
/** Get components of decomposable types */
85+
def decompose(a: ConstrainedSpace): List[ConstrainedSpace]
86+
87+
/** Intersection of two spaces */
88+
def intersect(a: ConstrainedSpace, b: ConstrainedSpace): List[ConstrainedSpace] =
89+
doDebug[List[ConstrainedSpace]](s"${debugShow(a)} intersect ${debugShow(b)}", res => s"= ${debugShow(res)}") {
90+
def doDecomposeA = decompose(a).flatMap(intersect(_, b))
91+
def doDecomposeB = decompose(b).flatMap(intersect(a, _))
92+
93+
val res: List[ConstrainedSpace] = (a.vec, b.vec) match {
94+
case (Nil, Nil) =>
95+
// TODO: optimize this?
96+
def union[T](l1: List[T], l2: List[T]) =
97+
(l1 ::: l2).distinct
98+
99+
List(ConstrainedSpace(Nil, union(a.termConstraints, b.termConstraints), union(a.typeConstraints, b.typeConstraints)))
100+
101+
case (n :: ns, m :: ms) => (n, m) match {
102+
case (nprod@Prod(_, nfun, nsym, nss, _), Prod(_, mfun, msym, mss, _)) =>
103+
if (nsym != msym || !isEqualType(nfun, mfun)) Nil
104+
else {
105+
val arity = nss.length // assuming arity == mps.length
106+
intersect(a withVec (nss ::: ns), b withVec (mss ::: ms)).map { c =>
107+
val (kps, rest) = c.vec.splitAt(arity)
108+
c.withVec(nprod.copy(params = kps) :: rest)
109+
}
110+
}
111+
112+
case (Prod(tp1, _, _, _, _), Typ(tp2, _)) =>
113+
if (isSubType(tp1, tp2) || isSubType(tp2, tp1)) List(a)
114+
else if (canDecompose(tp2)) doDecomposeB
115+
else Nil
116+
117+
case (Typ(tp1, _), Prod(tp2, _, _, _, _)) =>
118+
if (isSubType(tp1, tp2) || isSubType(tp2, tp1)) List(b)
119+
else if (canDecompose(tp1)) doDecomposeA
120+
else Nil
121+
122+
case (Typ(tp1, _), Typ(tp2, _)) =>
123+
if (isSubType(tp1, tp2)) List(a)
124+
else if (isSubType(tp2, tp1)) List(b)
125+
else if (canDecompose(tp1)) doDecomposeA
126+
else if (canDecompose(tp2)) doDecomposeB
127+
else intersectUnrelatedAtomicTypes(tp1, tp2) match {
128+
case None => Nil
129+
case Some(space) =>
130+
intersect(a.withVec(ns), b.withVec(ms)).map {
131+
c => c.withVec(space :: c.vec)
132+
}
133+
}
134+
135+
case _ => Nil
136+
}
137+
case _ => Nil
138+
}
139+
140+
res
141+
}
142+
143+
/** The space of a not covered by b */
144+
def subtract(a: ConstrainedSpace, b: ConstrainedSpace): List[ConstrainedSpace] =
145+
doDebug[List[ConstrainedSpace]](s"${debugShow(a)} substract ${debugShow(b)}", res => s"= ${debugShow(res)}") {
146+
def doDecomposeA = decompose(a).flatMap(subtract(_, b))
147+
def doDecomposeB = decompose(b).foldLeft(List(a)) { (as, b) => as.flatMap(subtract(_, b)) }
148+
149+
val res = (a.vec, b.vec) match {
150+
case (Nil, Nil) =>
151+
if (b.termConstraints.isEmpty) Nil
152+
else b.termConstraints.map { d => a.withTermConstraints(d.neg :: a.termConstraints) }
153+
154+
case (n :: ns, m :: ms) => (n, m) match {
155+
case (nprod@Prod(_, nfun, nsym, nss, _), Prod(_, mfun, msym, mss, _)) =>
156+
if (nsym != msym || !isEqualType(nfun, mfun)) List(a)
157+
else {
158+
val arity = nss.length // assuming nss.length == mss.length
159+
subtract(a withVec (nss ::: ns), b withVec (mss ::: ms)).map { c =>
160+
val (kps, rest) = c.vec.splitAt(arity)
161+
c.withVec(nprod.copy(params = kps) :: rest)
162+
}
163+
}
164+
165+
case (p@Prod(tp1, _, _, _, full), Typ(tp2, _)) =>
166+
if (isSubType(tp1, tp2)) {
167+
val tailSubtraction = subtract(a withVec ns, b withVec ms)
168+
tailSubtraction.map(_.withPrependendSpace(p))
169+
} else if (full && canDecompose(tp2)) doDecomposeB
170+
else List(a)
171+
172+
case (Typ(_, _), Prod(_, _, _, _, false)) =>
173+
List(a) // approximation
174+
175+
case (Typ(tp1, _), p@Prod(tp2, fun, sym, ss, true)) =>
176+
if (isSubType(tp1, tp2)) {
177+
val newParams = signature(fun, sym, ss.length).map(Typ(_))
178+
val _a = a.withVec(p.copy(tp = tp2, params = newParams) :: ns)
179+
subtract(_a, b)
180+
} else if (canDecompose(tp1)) doDecomposeA
181+
else List(a)
182+
183+
case (Typ(tp1, _), Typ(tp2, _)) =>
184+
if (isSubType(tp1, tp2)) {
185+
val tailSubtraction = subtract(a withVec ns, b withVec ms)
186+
tailSubtraction.map(_.withPrependendSpace(Typ(tp1)))
187+
} else if (canDecompose(tp1)) doDecomposeA
188+
else if (canDecompose(tp2)) doDecomposeB
189+
else List(a)
190+
191+
case _ => List(a)
192+
}
193+
case _ => List(a)
194+
}
195+
196+
res
197+
}
198+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package dotty.tools.dotc.transform.patmat
2+
package `new logic`
3+
4+
import dotty.tools.dotc.core.Symbols.Symbol
5+
import dotty.tools.dotc.core.Types.Type
6+
7+
/** Space definition */
8+
sealed trait Space
9+
10+
/** Space representing the set of all values of a type
11+
*
12+
* @param tp: the type this space represents
13+
* @param decomposed: does the space result from decomposition? Used for pretty print
14+
*
15+
*/
16+
case class Typ(tp: Type, decomposed: Boolean = false) extends Space
17+
18+
/** Space representing an extractor pattern */
19+
case class Prod(tp: Type, unappTp: Type, unappSym: Symbol, params: List[Space], full: Boolean) extends Space
20+
21+
/** Represents a set of _vectors_ of values, possibly constrained
22+
*
23+
* Before and after exhaustivity checks, [[vec]] is exactly one space long.
24+
*/
25+
case class ConstrainedSpace(
26+
vec: List[Space],
27+
termConstraints: List[TermConstraint],
28+
typeConstraints: List[TypeConstraint]
29+
) {
30+
def withPrependendSpace(s: Space) = copy(vec = s :: vec)
31+
def withVec(vec: List[Space]) = copy(vec = vec)
32+
def withTermConstraints(termConstraints: List[TermConstraint]) = copy(termConstraints = termConstraints)
33+
def withTypeConstraints(typeConstraints: List[TypeConstraint]) = copy(typeConstraints = typeConstraints)
34+
}
35+
36+
/** A term-level constraint */
37+
sealed trait TermConstraint {
38+
final def neg: TermConstraint = this match {
39+
case Neg(c) => c
40+
case c: PositiveTermConstraint => Neg(c)
41+
}
42+
}
43+
44+
/** Negated constraint. Cannot be nested by construction */
45+
case class Neg(c: PositiveTermConstraint) extends TermConstraint
46+
47+
/** A constraint which isn't negated */
48+
sealed trait PositiveTermConstraint extends TermConstraint
49+
50+
/** Represents a presence of some constraint (like a pattern guard) */
51+
case object Dummy extends PositiveTermConstraint
52+
53+
/** A constraint which is always satisfied */
54+
case object AlwaysSatisfied extends PositiveTermConstraint
55+
56+
/** A type-level constraint */
57+
sealed trait TypeConstraint
58+
case class TypeEquality(tp1: Type, tp2: Type) extends TypeConstraint

0 commit comments

Comments
 (0)