@@ -13,6 +13,7 @@ import core.StdNames._
13
13
import core .NameOps ._
14
14
import core .Constants ._
15
15
import reporting .diagnostic .messages ._
16
+ import config .Printers .{ exhaustivity => debug }
16
17
17
18
/** Space logic for checking exhaustivity and unreachability of pattern matching
18
19
*
@@ -28,8 +29,6 @@ import reporting.diagnostic.messages._
28
29
* 3. A union of spaces `S1 | S2 | ...` is a space
29
30
* 4. For a case class Kon(x1: T1, x2: T2, .., xn: Tn), if S1, S2, ..., Sn
30
31
* are spaces, then `Kon(S1, S2, ..., Sn)` is a space.
31
- * 5. A constant `Const(value, T)` is a point in space
32
- * 6. A stable identifier `Var(sym, T)` is a space
33
32
*
34
33
* For the problem of exhaustivity check, its formulation in terms of space is as follows:
35
34
*
@@ -67,15 +66,6 @@ case class Kon(tp: Type, params: List[Space]) extends Space
67
66
/** Union of spaces */
68
67
case class Or (spaces : List [Space ]) extends Space
69
68
70
- /** Point in space */
71
- sealed trait Point extends Space
72
-
73
- /** Point representing variables(stable identifier) in patterns */
74
- case class Var (sym : Symbol , tp : Type ) extends Point
75
-
76
- /** Point representing literal constants in patterns */
77
- case class Const (value : Constant , tp : Type ) extends Point
78
-
79
69
/** abstract space logic */
80
70
trait SpaceLogic {
81
71
/** Is `tp1` a subtype of `tp2`? */
@@ -97,6 +87,9 @@ trait SpaceLogic {
97
87
/** Get components of decomposable types */
98
88
def decompose (tp : Type ): List [Space ]
99
89
90
+ /** Display space in string format */
91
+ def show (sp : Space ): String
92
+
100
93
/** Simplify space using the laws, there's no nested union after simplify */
101
94
def simplify (space : Space ): Space = space match {
102
95
case Kon (tp, spaces) =>
@@ -137,7 +130,7 @@ trait SpaceLogic {
137
130
def tryDecompose1 (tp : Type ) = canDecompose(tp) && isSubspace(Or (decompose(tp)), b)
138
131
def tryDecompose2 (tp : Type ) = canDecompose(tp) && isSubspace(a, Or (decompose(tp)))
139
132
140
- (a, b) match {
133
+ val res = (a, b) match {
141
134
case (Empty , _) => true
142
135
case (_, Empty ) => false
143
136
case (Or (ss), _) => ss.forall(isSubspace(_, b))
@@ -157,25 +150,19 @@ trait SpaceLogic {
157
150
simplify(minus(a, b)) == Empty
158
151
case (Kon (tp1, ss1), Kon (tp2, ss2)) =>
159
152
isEqualType(tp1, tp2) && ss1.zip(ss2).forall((isSubspace _).tupled)
160
- case (Const (v1, _), Const (v2, _)) => v1 == v2
161
- case (Const (_, tp1), Typ (tp2, _)) => isSubType(tp1, tp2) || tryDecompose2(tp2)
162
- case (Const (_, _), Or (ss)) => ss.exists(isSubspace(a, _))
163
- case (Const (_, _), _) => false
164
- case (_, Const (_, _)) => false
165
- case (Var (x, _), Var (y, _)) => x == y
166
- case (Var (_, tp1), Typ (tp2, _)) => isSubType(tp1, tp2) || tryDecompose2(tp2)
167
- case (Var (_, _), Or (ss)) => ss.exists(isSubspace(a, _))
168
- case (Var (_, _), _) => false
169
- case (_, Var (_, _)) => false
170
153
}
154
+
155
+ debug.println(s " ${show(a)} < ${show(b)} = $res" )
156
+
157
+ res
171
158
}
172
159
173
160
/** Intersection of two spaces */
174
161
def intersect (a : Space , b : Space ): Space = {
175
162
def tryDecompose1 (tp : Type ) = intersect(Or (decompose(tp)), b)
176
163
def tryDecompose2 (tp : Type ) = intersect(a, Or (decompose(tp)))
177
164
178
- (a, b) match {
165
+ val res = (a, b) match {
179
166
case (Empty , _) | (_, Empty ) => Empty
180
167
case (_, Or (ss)) => Or (ss.map(intersect(a, _)).filterConserve(_ ne Empty ))
181
168
case (Or (ss), _) => Or (ss.map(intersect(_, b)).filterConserve(_ ne Empty ))
@@ -199,39 +186,19 @@ trait SpaceLogic {
199
186
if (! isEqualType(tp1, tp2)) Empty
200
187
else if (ss1.zip(ss2).exists(p => simplify(intersect(p._1, p._2)) == Empty )) Empty
201
188
else Kon (tp1, ss1.zip(ss2).map((intersect _).tupled))
202
- case (Const (v1, _), Const (v2, _)) =>
203
- if (v1 == v2) a else Empty
204
- case (Const (_, tp1), Typ (tp2, _)) =>
205
- if (isSubType(tp1, tp2)) a
206
- else if (canDecompose(tp2)) tryDecompose2(tp2)
207
- else Empty
208
- case (Const (_, _), _) => Empty
209
- case (Typ (tp1, _), Const (_, tp2)) =>
210
- if (isSubType(tp2, tp1)) b
211
- else if (canDecompose(tp1)) tryDecompose1(tp1)
212
- else Empty
213
- case (_, Const (_, _)) => Empty
214
- case (Var (x, _), Var (y, _)) =>
215
- if (x == y) a else Empty
216
- case (Var (_, tp1), Typ (tp2, _)) =>
217
- if (isSubType(tp1, tp2)) a
218
- else if (canDecompose(tp2)) tryDecompose2(tp2)
219
- else Empty
220
- case (Var (_, _), _) => Empty
221
- case (Typ (tp1, _), Var (_, tp2)) =>
222
- if (isSubType(tp2, tp1)) b
223
- else if (canDecompose(tp1)) tryDecompose1(tp1)
224
- else Empty
225
- case (_, Var (_, _)) => Empty
226
189
}
190
+
191
+ debug.println(s " ${show(a)} & ${show(b)} = ${show(res)}" )
192
+
193
+ res
227
194
}
228
195
229
196
/** The space of a not covered by b */
230
197
def minus (a : Space , b : Space ): Space = {
231
198
def tryDecompose1 (tp : Type ) = minus(Or (decompose(tp)), b)
232
199
def tryDecompose2 (tp : Type ) = minus(a, Or (decompose(tp)))
233
200
234
- (a, b) match {
201
+ val res = (a, b) match {
235
202
case (Empty , _) => Empty
236
203
case (_, Empty ) => a
237
204
case (Typ (tp1, _), Typ (tp2, _)) =>
@@ -264,26 +231,11 @@ trait SpaceLogic {
264
231
Or (ss1.zip(ss2).map((minus _).tupled).zip(0 to ss2.length - 1 ).map {
265
232
case (ri, i) => Kon (tp1, ss1.updated(i, ri))
266
233
})
267
- case (Const (v1, _), Const (v2, _)) =>
268
- if (v1 == v2) Empty else a
269
- case (Const (_, tp1), Typ (tp2, _)) =>
270
- if (isSubType(tp1, tp2)) Empty
271
- else if (canDecompose(tp2)) tryDecompose2(tp2)
272
- else a
273
- case (Const (_, _), _) => a
274
- case (Typ (tp1, _), Const (_, tp2)) => // Boolean & Java enum
275
- if (canDecompose(tp1)) tryDecompose1(tp1)
276
- else a
277
- case (_, Const (_, _)) => a
278
- case (Var (x, _), Var (y, _)) =>
279
- if (x == y) Empty else a
280
- case (Var (_, tp1), Typ (tp2, _)) =>
281
- if (isSubType(tp1, tp2)) Empty
282
- else if (canDecompose(tp2)) tryDecompose2(tp2)
283
- else a
284
- case (Var (_, _), _) => a
285
- case (_, Var (_, _)) => a
286
234
}
235
+
236
+ debug.println(s " ${show(a)} - ${show(b)} = ${show(res)}" )
237
+
238
+ res
287
239
}
288
240
}
289
241
@@ -297,19 +249,14 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
297
249
* otherwise approximate extractors to Empty
298
250
*/
299
251
def project (pat : Tree , roundUp : Boolean = true )(implicit ctx : Context ): Space = pat match {
300
- case Literal (c) => Const (c, c.tpe)
301
- case _ : BackquotedIdent => Var (pat.symbol, pat.tpe)
252
+ case Literal (c) =>
253
+ if (c.value.isInstanceOf [Symbol ])
254
+ Typ (c.value.asInstanceOf [Symbol ].termRef, false )
255
+ else
256
+ Typ (ConstantType (c), false )
257
+ case _ : BackquotedIdent => Typ (pat.tpe, false )
302
258
case Ident (_) | Select (_, _) =>
303
- pat.tpe.stripAnnots match {
304
- case tp : TermRef =>
305
- if (pat.symbol.is(Enum ))
306
- Const (Constant (pat.symbol), tp)
307
- else if (tp.underlyingIterator.exists(_.classSymbol.is(Module )))
308
- Typ (tp.widenTermRefExpr.stripAnnots, false )
309
- else
310
- Var (pat.symbol, tp)
311
- case tp => Typ (tp, false )
312
- }
259
+ Typ (pat.tpe.stripAnnots, false )
313
260
case Alternative (trees) => Or (trees.map(project(_, roundUp)))
314
261
case Bind (_, pat) => project(pat)
315
262
case UnApply (_, _, pats) =>
@@ -345,7 +292,9 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
345
292
/** Is `tp1` a subtype of `tp2`? */
346
293
def isSubType (tp1 : Type , tp2 : Type ): Boolean = {
347
294
// check SI-9657 and tests/patmat/gadt.scala
348
- erase(tp1) <:< erase(tp2)
295
+ val res = erase(tp1) <:< erase(tp2)
296
+ debug.println(s " ${tp1.show} <:< ${tp2.show} = $res" )
297
+ res
349
298
}
350
299
351
300
def isEqualType (tp1 : Type , tp2 : Type ): Boolean = tp1 =:= tp2
@@ -373,29 +322,37 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
373
322
}
374
323
}
375
324
325
+ debug.println(s " candidates for ${tp.show} : [ ${children.map(_.show).mkString(" , " )}] " )
326
+
376
327
tp match {
377
328
case OrType (tp1, tp2) => List (Typ (tp1, true ), Typ (tp2, true ))
378
329
case _ if tp =:= ctx.definitions.BooleanType =>
379
330
List (
380
- Const ( Constant (true ), ctx.definitions. BooleanType ),
381
- Const ( Constant (false ), ctx.definitions. BooleanType )
331
+ Typ ( ConstantType ( Constant (true )), true ),
332
+ Typ ( ConstantType ( Constant (false )), true )
382
333
)
383
334
case _ if tp.classSymbol.is(Enum ) =>
384
- children.map(sym => Const ( Constant ( sym), tp ))
335
+ children.map(sym => Typ ( sym.termRef, true ))
385
336
case _ =>
386
337
val parts = children.map { sym =>
387
338
if (sym.is(ModuleClass ))
388
- sym.asClass.classInfo.selfType
339
+ refine(tp, sym.sourceModule.termRef)
340
+ else if (sym.isTerm)
341
+ refine(tp, sym.termRef)
389
342
else if (sym.info.typeParams.length > 0 || tp.isInstanceOf [TypeRef ])
390
343
refine(tp, sym.typeRef)
391
344
else
392
345
sym.typeRef
393
346
} filter { tpe =>
394
347
// Child class may not always be subtype of parent:
395
348
// GADT & path-dependent types
396
- tpe <:< expose(tp)
349
+ val res = tpe <:< expose(tp)
350
+ if (! res) debug.println(s " unqualified child ousted: ${tpe.show} !< ${tp.show}" )
351
+ res
397
352
}
398
353
354
+ debug.println(s " ${tp.show} decomposes to [ ${parts.map(_.show).mkString(" , " )}] " )
355
+
399
356
parts.map(Typ (_, true ))
400
357
}
401
358
}
@@ -409,20 +366,26 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
409
366
* `path2`, then return `path1.B`.
410
367
*/
411
368
def refine (tp1 : Type , tp2 : Type ): Type = (tp1, tp2) match {
412
- case (tp1 : RefinedType , _) => tp1.wrapIfMember(refine(tp1.parent, tp2))
369
+ case (tp1 : RefinedType , _ : TypeRef ) => tp1.wrapIfMember(refine(tp1.parent, tp2))
413
370
case (tp1 : HKApply , _) => refine(tp1.superType, tp2)
414
- case (TypeRef (ref1 : TypeProxy , _), tp2 @ TypeRef (ref2 : TypeProxy , name)) =>
415
- if (ref1.underlying <:< ref2.underlying) TypeRef (ref1, name) else tp2
371
+ case (TypeRef (ref1 : TypeProxy , _), tp2 @ TypeRef (ref2 : TypeProxy , _)) =>
372
+ if (ref1.underlying <:< ref2.underlying) tp2.derivedSelect(ref1) else tp2
373
+ case (TypeRef (ref1 : TypeProxy , _), tp2 @ TermRef (ref2 : TypeProxy , _)) =>
374
+ if (ref1.underlying <:< ref2.underlying) tp2.derivedSelect(ref1) else tp2
416
375
case _ => tp2
417
376
}
418
377
419
378
/** Abstract sealed types, or-types, Boolean and Java enums can be decomposed */
420
379
def canDecompose (tp : Type ): Boolean = {
421
- tp.classSymbol.is(allOf(Abstract , Sealed )) ||
380
+ val res = tp.classSymbol.is(allOf(Abstract , Sealed )) ||
422
381
tp.classSymbol.is(allOf(Trait , Sealed )) ||
423
382
tp.isInstanceOf [OrType ] ||
424
383
tp =:= ctx.definitions.BooleanType ||
425
- tp.classSymbol.is(Enum )
384
+ tp.classSymbol.is(allOf(Enum , Sealed )) // Enum value doesn't have Sealed flag
385
+
386
+ debug.println(s " decomposable: ${tp.show} = $res" )
387
+
388
+ res
426
389
}
427
390
428
391
/** Show friendly type name with current scope in mind
@@ -474,14 +437,12 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
474
437
def show (s : Space ): String = {
475
438
def doShow (s : Space , mergeList : Boolean = false ): String = s match {
476
439
case Empty => " "
477
- case Const (v , _) => v .show
478
- case Var (x , _) => x.show
440
+ case Typ ( c : ConstantType , _) => c.value .show
441
+ case Typ ( tp : TermRef , _) => tp.symbol.showName
479
442
case Typ (tp, decomposed) =>
480
443
val sym = tp.widen.classSymbol
481
444
482
- if (sym.is(ModuleClass ))
483
- showType(tp)
484
- else if (ctx.definitions.isTupleType(tp))
445
+ if (ctx.definitions.isTupleType(tp))
485
446
signature(tp).map(_ => " _" ).mkString(" (" , " , " , " )" )
486
447
else if (sym.showFullName == " scala.collection.immutable.::" )
487
448
if (mergeList) " _" else " List(_)"
@@ -523,7 +484,9 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
523
484
}
524
485
525
486
val Match (sel, cases) = tree
526
- isCheckable(sel.tpe.widen.deAnonymize.dealiasKeepAnnots)
487
+ val res = isCheckable(sel.tpe.widen.deAnonymize.dealiasKeepAnnots)
488
+ debug.println(s " checkable: ${sel.show} = $res" )
489
+ res
527
490
}
528
491
529
492
@@ -584,7 +547,11 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
584
547
val selTyp = sel.tpe.widen.deAnonymize.dealias
585
548
586
549
587
- val patternSpace = cases.map(x => project(x.pat)).reduce((a, b) => Or (List (a, b)))
550
+ val patternSpace = cases.map({ x =>
551
+ val space = project(x.pat)
552
+ debug.println(s " ${x.pat.show} projects to ${show(space)}" )
553
+ space
554
+ }).reduce((a, b) => Or (List (a, b)))
588
555
val uncovered = simplify(minus(Typ (selTyp, true ), patternSpace))
589
556
590
557
if (uncovered != Empty )
0 commit comments