1
1
package dotty .tools .dotc
2
2
package transform
3
+ package patmat
3
4
4
5
import core .Types ._
5
6
import core .Contexts ._
@@ -12,8 +13,26 @@ import core.Symbols._
12
13
import core .NameOps ._
13
14
import core .Constants ._
14
15
15
- /** Space logic for checking exhaustivity and unreachability of
16
- * pattern matching.
16
+ /** Space logic for checking exhaustivity and unreachability of pattern matching.
17
+ *
18
+ * The core idea of the algorithm is that patterns and types are value
19
+ * spaces, which is recursively defined as follows:
20
+ *
21
+ * 1. `Empty` is a space
22
+ * 2. For a type T, `Typ(T)` is a space
23
+ * 3. A union of spaces `S1 | S2 | ...` is a space
24
+ * 4. For a case class Kon(x1: T1, x2: T2, .., xn: Tn), if S1, S2, ..., Sn
25
+ * are spaces, then `Kon(S1, S2, ..., Sn)` is a space.
26
+ * 5. A constant `Const(value, T)` is a point in space
27
+ * 6. A stable identifier `Var(sym, T)` is a space
28
+ *
29
+ * For the problem of exhaustivity check, its formulation in terms of space is as follows:
30
+ *
31
+ * Is the space Typ(T) a subspace of the union of space covered by all the patterns?
32
+ *
33
+ * The problem of unreachable patterns can be formulated as follows:
34
+ *
35
+ * Is the space covered by a pattern a subspace of the space covered by previous patterns?
17
36
*/
18
37
19
38
@@ -41,7 +60,7 @@ trait SpaceLogic {
41
60
/** Is the type `tp` decomposable? i.e. all values of the type can be covered
42
61
* by its decomposed types.
43
62
*
44
- * Abstract sealed class, OrType and Boolean can be decomposed.
63
+ * Abstract sealed class, OrType, Boolean and Java enums can be decomposed.
45
64
*/
46
65
def canDecompose (tp : Type ): Boolean
47
66
@@ -197,7 +216,7 @@ trait SpaceLogic {
197
216
case (Const (_, tp1), Typ (tp2, _)) =>
198
217
if (isSubType(tp1, tp2)) Empty else a
199
218
case (Const (_, _), _) => a
200
- case (Typ (tp1, _), Const (_, tp2)) => // Boolean
219
+ case (Typ (tp1, _), Const (_, tp2)) => // Boolean & Java enum
201
220
if (isSubType(tp2, tp1) && canDecompose(tp1))
202
221
minus(Or (partitions(tp1)), b)
203
222
else a
@@ -215,10 +234,6 @@ trait SpaceLogic {
215
234
class SpaceEngine (implicit ctx : Context ) extends SpaceLogic {
216
235
import tpd ._
217
236
218
- def debug (s : String ): Unit = {
219
- if (ctx.debug) println(s)
220
- }
221
-
222
237
/** Return the space that represents the pattern `pat`
223
238
*
224
239
* If roundUp is true, approximate extractors to its type,
@@ -227,10 +242,13 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
227
242
def project (pat : Tree , roundUp : Boolean = true )(implicit ctx : Context ): Space = pat match {
228
243
case Literal (c) => Const (c, c.tpe)
229
244
case _ : BackquotedIdent => Var (pat.symbol, pat.tpe)
230
- case Ident (_) => Typ (pat.tpe.stripAnnots, false )
231
- case Select (_, _) =>
245
+ case Ident (_) =>
246
+ Typ (pat.tpe.stripAnnots, false )
247
+ case Select (_, _) =>
232
248
if (pat.symbol.is(Module ))
233
249
Typ (pat.tpe.stripAnnots, false )
250
+ else if (pat.symbol.is(Enum ))
251
+ Const (Constant (pat.symbol), pat.tpe)
234
252
else
235
253
Var (pat.symbol, pat.tpe)
236
254
case Alternative (trees) => Or (trees.map(project(_, roundUp)))
@@ -243,7 +261,6 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
243
261
case Typed (pat @ UnApply (_, _, _), _) => project(pat)
244
262
case Typed (expr, _) => Typ (expr.tpe.stripAnnots, true )
245
263
case _ =>
246
- debug(s " ========unkown tree: $pat======== " )
247
264
Empty
248
265
}
249
266
@@ -255,71 +272,61 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
255
272
case (tp1 : RefinedType , tp2 : RefinedType ) => isSubType(tp1.parent, tp2.parent)
256
273
case (tp1 : RefinedType , _) => isSubType(tp1.parent, tp2)
257
274
case (_, tp2 : RefinedType ) => isSubType(tp1, tp2.parent)
258
- case (_, _) =>
259
- val res = tp1 <:< tp2
260
- debug(s " $tp1 <: $tp2 ? $res" )
261
- res
275
+ case (_, _) => tp1 <:< tp2
262
276
}
263
277
264
- def isEqualType (tp1 : Type , tp2 : Type ): Boolean = {
265
- val res = tp1 =:= tp2
266
- debug(s " $tp1 == $tp2 ? $res" )
267
- res
268
- }
278
+ def isEqualType (tp1 : Type , tp2 : Type ): Boolean = tp1 =:= tp2
269
279
270
280
def signature (tp : Type ): List [Type ] = {
271
281
val ktor = tp.classSymbol.primaryConstructor.info
272
282
273
- debug(s " =======ktor: $ktor" )
274
-
275
283
val meth =
276
284
if (ktor.isInstanceOf [MethodType ]) ktor
277
285
else
278
286
tp match {
279
287
case AppliedType (_, params) =>
280
- debug(s " =======params: $params" )
281
288
val refined = params.map {
282
289
// TypeBounds would generate an exception
283
290
case tp : TypeBounds => tp.underlying
284
291
case tp => tp
285
292
}
286
- debug(s " =======refined params: $refined" )
287
293
ktor.appliedTo(refined)
288
294
case _ =>
289
295
ktor
290
296
}
291
297
292
- val sign = meth.firstParamTypes.map(_.stripTypeVar).map(paramTp => refine(tp, paramTp))
293
-
294
- debug(s " ====signature of $tp: $sign" )
295
- sign
298
+ meth.firstParamTypes.map(_.stripTypeVar).map(refine(tp, _))
296
299
}
297
300
298
- def partitions (tp : Type ): List [Space ] = tp match {
299
- case OrType (tp1, tp2) => List (Typ (tp1, true ), Typ (tp2, true ))
300
- case _ if tp =:= ctx.definitions.BooleanType =>
301
- List (
302
- Const (Constant (true ), ctx.definitions.BooleanType ),
303
- Const (Constant (false ), ctx.definitions.BooleanType )
304
- )
305
- case _ =>
306
- val children = tp.classSymbol.annotations.filter(_.symbol == ctx.definitions.ChildAnnot ).map { annot =>
307
- // refer to definition of Annotation.makeChild
308
- val sym = annot.tree match {
309
- case Apply (TypeApply (_, List (tpTree)), _) => tpTree.symbol.asClass
310
- }
311
-
312
- if (sym.is(ModuleClass ))
313
- sym.classInfo.selfType
314
- else if (sym.info.typeParams.length > 0 || tp.isInstanceOf [TypeRef ])
315
- refine(tp, sym.classInfo.symbolicTypeRef)
316
- else
317
- sym.info
318
- } filter(_ <:< tp) // child may not always be subtype of parent: SI-4020
319
-
320
- debug(s " =========child of ${tp.show}: ${children.map(_.show).mkString(" , " )}" )
301
+ def partitions (tp : Type ): List [Space ] = {
302
+ val children = tp.classSymbol.annotations.filter(_.symbol == ctx.definitions.ChildAnnot ).map { annot =>
303
+ // refer to definition of Annotation.makeChild
304
+ annot.tree match {
305
+ case Apply (TypeApply (_, List (tpTree)), _) => tpTree.symbol
306
+ }
307
+ }
321
308
322
- children.map(tp => Typ (tp, true ))
309
+ tp match {
310
+ case OrType (tp1, tp2) => List (Typ (tp1, true ), Typ (tp2, true ))
311
+ case _ if tp =:= ctx.definitions.BooleanType =>
312
+ List (
313
+ Const (Constant (true ), ctx.definitions.BooleanType ),
314
+ Const (Constant (false ), ctx.definitions.BooleanType )
315
+ )
316
+ case _ if tp.classSymbol.is(Enum ) =>
317
+ children.map(sym => Const (Constant (sym), tp))
318
+ case _ =>
319
+ val parts = children.map { sym =>
320
+ if (sym.is(ModuleClass ))
321
+ sym.asClass.classInfo.selfType
322
+ else if (sym.info.typeParams.length > 0 || tp.isInstanceOf [TypeRef ])
323
+ refine(tp, sym.asClass.classInfo.symbolicTypeRef)
324
+ else
325
+ sym.info
326
+ } filter(_ <:< tp) // child may not always be subtype of parent: SI-4020
327
+
328
+ parts.map(tp => Typ (tp, true ))
329
+ }
323
330
}
324
331
325
332
/** Refine tp2 based on tp1
@@ -337,13 +344,13 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
337
344
case _ => tp2
338
345
}
339
346
340
- /** Abstract sealed types, or-types and Boolean can be decomposed */
341
- def canDecompose (tp : Type ): Boolean = {
347
+ /** Abstract sealed types, or-types, Boolean and Java enums can be decomposed */
348
+ def canDecompose (tp : Type ): Boolean =
342
349
tp.typeSymbol.is(allOf(Abstract , Sealed )) ||
343
350
tp.typeSymbol.is(allOf(Trait , Sealed )) ||
344
351
tp.isInstanceOf [OrType ] ||
345
- tp =:= ctx.definitions.BooleanType
346
- }
352
+ tp =:= ctx.definitions.BooleanType ||
353
+ tp.typeSymbol.is( Enum )
347
354
348
355
def isCaseClass (tp : Type ): Boolean = tp.classSymbol.isClass && tp.classSymbol.is(CaseClass )
349
356
@@ -428,27 +435,24 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
428
435
flatten(s).map(doShow(_, false )).distinct.mkString(" , " )
429
436
}
430
437
431
- /** Does the type t have @unchecked annotation? */
432
- def skipCheck (tp : Type ) : Boolean = tp match {
433
- case AnnotatedType (tp, annot) => ctx.definitions.UncheckedAnnot == annot.symbol
434
- case _ => false
435
- }
436
-
437
- /** Widen a type and eliminate anonymous classes */
438
- private def widen ( tp : Type ) : Type = tp.widen match {
439
- case tp : TypeRef if tp.symbol.isAnonymousClass =>
440
- tp.symbol.asClass.typeRef.asSeenFrom(tp.prefix, tp.symbol.owner)
441
- case tp => tp
438
+ def checkable ( tp : Type ) : Boolean = tp match {
439
+ case AnnotatedType (tp, annot) =>
440
+ ( ctx.definitions.UncheckedAnnot != annot.symbol) && checkable(tp)
441
+ case _ => true // actually everything is checkable unless @unchecked
442
+
443
+ // tp.classSymbol.is(Sealed) ||
444
+ // tp.isInstanceOf[OrType] ||
445
+ // tp.classSymbol.is(Enum) ||
446
+ // Boolean
447
+ // Int
448
+ // ...
442
449
}
443
450
444
- def exhaustivity (_match : Match ): Unit = {
451
+ def checkExhaustivity (_match : Match ): Unit = {
445
452
val Match (sel, cases) = _match
446
- val selTyp = widen( sel.tpe)
453
+ val selTyp = sel.tpe.widen.elimAnonymousClass
447
454
448
- debug(s " ====patterns: \n ${cases.map(_.pat).mkString(" \n " )}" )
449
455
val patternSpace = cases.map(x => project(x.pat)).reduce((a, b) => Or (List (a, b)))
450
- debug(s " ====selector: \n " + selTyp)
451
- debug(" ====pattern space:\n " + show(patternSpace))
452
456
val uncovered = simplify(minus(Typ (selTyp, true ), patternSpace))
453
457
454
458
if (uncovered != Empty ) {
@@ -460,9 +464,9 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
460
464
}
461
465
}
462
466
463
- def redundancy (_match : Match ): Unit = {
467
+ def checkRedundancy (_match : Match ): Unit = {
464
468
val Match (sel, cases) = _match
465
- val selTyp = widen( sel.tpe)
469
+ val selTyp = sel.tpe.widen.elimAnonymousClass
466
470
467
471
// starts from the second, the first can't be redundant
468
472
(1 until cases.length).foreach { i =>
0 commit comments