@@ -29,6 +29,7 @@ import config.Printers.{ exhaustivity => debug }
29
29
* 3. A union of spaces `S1 | S2 | ...` is a space
30
30
* 4. For a case class Kon(x1: T1, x2: T2, .., xn: Tn), if S1, S2, ..., Sn
31
31
* are spaces, then `Kon(S1, S2, ..., Sn)` is a space.
32
+ * 5. `Fun(S1, S2, ..., Sn)` is an extractor space.
32
33
*
33
34
* For the problem of exhaustivity check, its formulation in terms of space is as follows:
34
35
*
@@ -63,6 +64,9 @@ case class Typ(tp: Type, decomposed: Boolean) extends Space
63
64
/** Space representing a constructor pattern */
64
65
case class Kon (tp : Type , params : List [Space ]) extends Space
65
66
67
+ /** Space representing an extractor pattern */
68
+ case class Fun (tp : Type , fun : Type , params : List [Space ]) extends Space
69
+
66
70
/** Union of spaces */
67
71
case class Or (spaces : List [Space ]) extends Space
68
72
@@ -98,19 +102,31 @@ trait SpaceLogic {
98
102
def show (sp : Space ): String
99
103
100
104
/** Simplify space using the laws, there's no nested union after simplify */
101
- def simplify (space : Space ): Space = space match {
105
+ def simplify (space : Space , aggressive : Boolean = false ): Space = space match {
102
106
case Kon (tp, spaces) =>
103
- val sp = Kon (tp, spaces.map(simplify _))
107
+ val sp = Kon (tp, spaces.map(simplify(_)))
108
+ if (sp.params.contains(Empty )) Empty
109
+ else sp
110
+ case Fun (tp, fun, spaces) =>
111
+ val sp = Fun (tp, fun, spaces.map(simplify(_)))
104
112
if (sp.params.contains(Empty )) Empty
105
113
else sp
106
114
case Or (spaces) =>
107
- val set = spaces.map(simplify _ ).flatMap {
115
+ val set = spaces.map(simplify(_) ).flatMap {
108
116
case Or (ss) => ss
109
117
case s => Seq (s)
110
118
} filter (_ != Empty )
111
119
112
120
if (set.isEmpty) Empty
113
121
else if (set.size == 1 ) set.toList(0 )
122
+ else if (aggressive && spaces.size < 5 ) {
123
+ val res = set.map(sp => (sp, set.filter(_ ne sp))).find {
124
+ case (sp, sps) =>
125
+ isSubspace(sp, Or (sps))
126
+ }
127
+ if (res.isEmpty) Or (set)
128
+ else simplify(Or (res.get._2), aggressive)
129
+ }
114
130
else Or (set)
115
131
case Typ (tp, _) =>
116
132
if (canDecompose(tp) && decompose(tp).isEmpty) Empty
@@ -137,26 +153,33 @@ trait SpaceLogic {
137
153
def tryDecompose1 (tp : Type ) = canDecompose(tp) && isSubspace(Or (decompose(tp)), b)
138
154
def tryDecompose2 (tp : Type ) = canDecompose(tp) && isSubspace(a, Or (decompose(tp)))
139
155
140
- val res = (a , b) match {
156
+ val res = (simplify(a) , b) match {
141
157
case (Empty , _) => true
142
158
case (_, Empty ) => false
143
- case (Or (ss), _) => ss.forall(isSubspace(_, b))
159
+ case (Or (ss), _) =>
160
+ ss.forall(isSubspace(_, b))
144
161
case (Typ (tp1, _), Typ (tp2, _)) =>
145
- isSubType(tp1, tp2) || tryDecompose1(tp1) || tryDecompose2(tp2)
146
- case (Typ (tp1, _), Or (ss)) =>
162
+ isSubType(tp1, tp2)
163
+ case (Typ (tp1, _), Or (ss)) => // optimization
147
164
ss.exists(isSubspace(a, _)) || tryDecompose1(tp1)
165
+ case (_, Or (_)) =>
166
+ simplify(minus(a, b)) == Empty
148
167
case (Typ (tp1, _), Kon (tp2, ss)) =>
149
- isSubType(tp1, tp2) && isSubspace(Kon (tp2, signature(tp2).map(Typ (_, false ))), b) ||
150
- tryDecompose1(tp1)
168
+ isSubType(tp1, tp2) && isSubspace(Kon (tp2, signature(tp2).map(Typ (_, false ))), b)
151
169
case (Kon (tp1, ss), Typ (tp2, _)) =>
152
- isSubType(tp1, tp2) ||
153
- simplify(a) == Empty ||
154
- (isSubType(tp2, tp1) && tryDecompose1(tp1)) ||
155
- tryDecompose2(tp2)
156
- case (Kon (_, _), Or (_)) =>
157
- simplify(minus(a, b)) == Empty
170
+ isSubType(tp1, tp2)
158
171
case (Kon (tp1, ss1), Kon (tp2, ss2)) =>
159
172
isEqualType(tp1, tp2) && ss1.zip(ss2).forall((isSubspace _).tupled)
173
+ case (Fun (tp1, fun, ss), Typ (tp2, _)) =>
174
+ isSubType(tp1, tp2)
175
+ case (Typ (tp2, _), Fun (tp1, fun, ss)) =>
176
+ false // approximation: assume a type can never be fully matched by an extractor
177
+ case (Kon (_, _), Fun (_, _, _)) =>
178
+ false // approximation
179
+ case (Fun (_, _, _), Kon (_, _)) =>
180
+ false // approximation
181
+ case (Fun (_, fun1, ss1), Fun (_, fun2, ss2)) =>
182
+ isEqualType(fun1, fun2) && ss1.zip(ss2).forall((isSubspace _).tupled)
160
183
}
161
184
162
185
debug.println(s " ${show(a)} < ${show(b)} = $res" )
@@ -169,7 +192,7 @@ trait SpaceLogic {
169
192
def tryDecompose1 (tp : Type ) = intersect(Or (decompose(tp)), b)
170
193
def tryDecompose2 (tp : Type ) = intersect(a, Or (decompose(tp)))
171
194
172
- val res = (a, b) match {
195
+ val res : Space = (a, b) match {
173
196
case (Empty , _) | (_, Empty ) => Empty
174
197
case (_, Or (ss)) => Or (ss.map(intersect(a, _)).filterConserve(_ ne Empty ))
175
198
case (Or (ss), _) => Or (ss.map(intersect(_, b)).filterConserve(_ ne Empty ))
@@ -193,6 +216,24 @@ trait SpaceLogic {
193
216
if (! isEqualType(tp1, tp2)) Empty
194
217
else if (ss1.zip(ss2).exists(p => simplify(intersect(p._1, p._2)) == Empty )) Empty
195
218
else Kon (tp1, ss1.zip(ss2).map((intersect _).tupled))
219
+ case (Typ (tp1, _), Fun (tp2, _, _)) =>
220
+ if (isSubType(tp1, tp2) || isSubType(tp2, tp1)) b // prefer extractor space for better approximation
221
+ else if (canDecompose(tp1)) tryDecompose1(tp1)
222
+ else Empty
223
+ case (Fun (tp1, _, _), Typ (tp2, _)) =>
224
+ if (isSubType(tp1, tp2) || isSubType(tp2, tp1)) a
225
+ else if (canDecompose(tp2)) tryDecompose2(tp2)
226
+ else Empty
227
+ case (Fun (tp1, _, _), Kon (tp2, _)) =>
228
+ if (isSubType(tp1, tp2) || isSubType(tp2, tp1)) a
229
+ else Empty
230
+ case (Kon (tp1, _), Fun (tp2, _, _)) =>
231
+ if (isSubType(tp1, tp2) || isSubType(tp2, tp1)) b
232
+ else Empty
233
+ case (Fun (tp1, fun1, ss1), Fun (tp2, fun2, ss2)) =>
234
+ if (! isEqualType(fun1, fun2)) Empty
235
+ else if (ss1.zip(ss2).exists(p => simplify(intersect(p._1, p._2)) == Empty )) Empty
236
+ else Fun (tp1, fun1, ss1.zip(ss2).map((intersect _).tupled))
196
237
}
197
238
198
239
debug.println(s " ${show(a)} & ${show(b)} = ${show(res)}" )
@@ -238,6 +279,25 @@ trait SpaceLogic {
238
279
Or (ss1.zip(ss2).map((minus _).tupled).zip(0 to ss2.length - 1 ).map {
239
280
case (ri, i) => Kon (tp1, ss1.updated(i, ri))
240
281
})
282
+ case (Fun (tp1, _, _), Typ (tp2, _)) =>
283
+ if (isSubType(tp1, tp2)) Empty
284
+ else a
285
+ case (Typ (tp1, _), Fun (tp2, _, _)) =>
286
+ a // approximation
287
+ case (Fun (_, _, _), Kon (_, _)) =>
288
+ a
289
+ case (Kon (_, _), Fun (_, _, _)) =>
290
+ a
291
+ case (Fun (tp1, fun1, ss1), Fun (tp2, fun2, ss2)) =>
292
+ if (! isEqualType(fun1, fun2)) a
293
+ else if (ss1.zip(ss2).exists(p => simplify(intersect(p._1, p._2)) == Empty )) a
294
+ else if (ss1.zip(ss2).forall((isSubspace _).tupled)) Empty
295
+ else
296
+ // `(_, _, _) - (Some, None, _)` becomes `(None, _, _) | (_, Some, _) | (_, _, Empty)`
297
+ Or (ss1.zip(ss2).map((minus _).tupled).zip(0 to ss2.length - 1 ).map {
298
+ case (ri, i) => Fun (tp1, fun1, ss1.updated(i, ri))
299
+ })
300
+
241
301
}
242
302
243
303
debug.println(s " ${show(a)} - ${show(b)} = ${show(res)}" )
@@ -263,6 +323,12 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
263
323
import SpaceEngine ._
264
324
import tpd ._
265
325
326
+ private val scalaSomeClass = ctx.requiredClassRef(" scala.Some" .toTermName).symbol.asClass
327
+ private val scalaSeqFactoryClass = ctx.requiredClass(" scala.collection.generic.SeqFactory" .toTypeName)
328
+ private val scalaListType = ctx.requiredClassRef(" scala.collection.immutable.List" .toTypeName)
329
+ private val scalaNilType = ctx.requiredModuleRef(" scala.collection.immutable.Nil" .toTermName)
330
+ private val scalaConType = ctx.requiredClassRef(" scala.collection.immutable.::" .toTypeName)
331
+
266
332
/** Checks if it's possible to create a trait/class which is a subtype of `tp`.
267
333
*
268
334
* - doesn't handle member collisions (will not declare a type unimplementable because of one)
@@ -337,11 +403,8 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
337
403
}
338
404
339
405
/** Return the space that represents the pattern `pat`
340
- *
341
- * If roundUp is true, approximate extractors to its type,
342
- * otherwise approximate extractors to Empty
343
406
*/
344
- def project (pat : Tree , roundUp : Boolean = true )( implicit ctx : Context ): Space = pat match {
407
+ def project (pat : Tree ): Space = pat match {
345
408
case Literal (c) =>
346
409
if (c.value.isInstanceOf [Symbol ])
347
410
Typ (c.value.asInstanceOf [Symbol ].termRef, false )
@@ -350,20 +413,41 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
350
413
case _ : BackquotedIdent => Typ (pat.tpe, false )
351
414
case Ident (_) | Select (_, _) =>
352
415
Typ (pat.tpe.stripAnnots, false )
353
- case Alternative (trees) => Or (trees.map(project(_, roundUp )))
416
+ case Alternative (trees) => Or (trees.map(project(_)))
354
417
case Bind (_, pat) => project(pat)
355
- case UnApply (_ , _, pats) =>
418
+ case UnApply (fun , _, pats) =>
356
419
if (pat.tpe.classSymbol.is(CaseClass ))
357
420
// FIXME: why dealias is needed here?
358
- Kon (pat.tpe.stripAnnots.dealias, pats.map(pat => project(pat, roundUp)))
359
- else if (roundUp) Typ (pat.tpe.stripAnnots, false )
360
- else Empty
421
+ Kon (pat.tpe.stripAnnots.dealias, pats.map(pat => project(pat)))
422
+ else if (fun.symbol.owner == scalaSeqFactoryClass && fun.symbol.name == nme.unapplySeq)
423
+ projectList(pats)
424
+ else if (fun.symbol.info.resultType.isRef(scalaSomeClass))
425
+ Kon (pat.tpe.stripAnnots.dealias, pats.map(pat => project(pat)))
426
+ else
427
+ Fun (pat.tpe.stripAnnots.dealias, fun.tpe, pats.map(pat => project(pat)))
361
428
case Typed (pat @ UnApply (_, _, _), _) => project(pat)
362
429
case Typed (expr, _) => Typ (expr.tpe.stripAnnots, true )
363
430
case _ =>
364
431
Empty
365
432
}
366
433
434
+
435
+ /** Space of the pattern: List(a, b, c: _*)
436
+ */
437
+ def projectList (pats : List [Tree ]): Space = {
438
+ if (pats.isEmpty) return Typ (scalaNilType, false )
439
+
440
+ val (items, zero) = if (pats.last.tpe.isRepeatedParam)
441
+ (pats.init, Typ (scalaListType.appliedTo(pats.head.tpe.widen), false ))
442
+ else
443
+ (pats, Typ (scalaNilType, false ))
444
+
445
+ items.foldRight[Space ](zero) { (pat, acc) =>
446
+ Kon (scalaConType.appliedTo(pats.head.tpe.widen), project(pat) :: acc :: Nil )
447
+ }
448
+ }
449
+
450
+
367
451
/* Erase a type binding according to erasure semantics in pattern matching */
368
452
def erase (tp : Type ): Type = {
369
453
def doErase (tp : Type ): Type = tp match {
@@ -549,6 +633,8 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
549
633
550
634
if (ctx.definitions.isTupleType(tp))
551
635
signature(tp).map(_ => " _" ).mkString(" (" , " , " , " )" )
636
+ else if (sym.showFullName == " scala.collection.immutable.List" )
637
+ if (mergeList) " _*" else " _: List"
552
638
else if (sym.showFullName == " scala.collection.immutable.::" )
553
639
if (mergeList) " _" else " List(_)"
554
640
else if (tp.classSymbol.is(CaseClass ))
@@ -566,6 +652,8 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
566
652
else params.map(doShow(_, true )).filter(_ != " Nil" ).mkString(" List(" , " , " , " )" )
567
653
else
568
654
showType(tp) + params.map(doShow(_)).mkString(" (" , " , " , " )" )
655
+ case Fun (tp, fun, params) =>
656
+ showType(fun) + params.map(doShow(_)).mkString(" (" , " , " , " )" )
569
657
case Or (_) =>
570
658
throw new Exception (" incorrect flatten result " + s)
571
659
}
@@ -664,31 +752,35 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
664
752
665
753
val patternSpace = cases.map({ x =>
666
754
val space = project(x.pat)
667
- debug.println(s " ${x.pat.show} projects to ${show(space)}" )
755
+ debug.println(s " ${x.pat.show} ====> ${show(space)}" )
668
756
space
669
757
}).reduce((a, b) => Or (List (a, b)))
670
- val uncovered = simplify(minus(Typ (selTyp, true ), patternSpace))
758
+ val uncovered = simplify(minus(Typ (selTyp, true ), patternSpace), aggressive = true )
671
759
672
760
if (uncovered != Empty )
673
- ctx.warning(PatternMatchExhaustivity (show(uncovered)), _match .pos)
761
+ ctx.warning(PatternMatchExhaustivity (show(uncovered)), sel .pos)
674
762
}
675
763
676
764
def checkRedundancy (_match : Match ): Unit = {
677
765
val Match (sel, cases) = _match
678
766
// ignore selector type for now
679
767
// val selTyp = sel.tpe.widen.deAnonymize.dealias
680
768
769
+ if (cases.length == 1 ) return
770
+
681
771
// starts from the second, the first can't be redundant
682
772
(1 until cases.length).foreach { i =>
683
- // in redundancy check, take guard as false, take extractor as match
684
- // nothing in order to soundly approximate
773
+ // in redundancy check, take guard as false in order to soundly approximate
685
774
val prevs = cases.take(i).map { x =>
686
- if (x.guard.isEmpty) project(x.pat, false )
775
+ if (x.guard.isEmpty) project(x.pat)
687
776
else Empty
688
777
}.reduce((a, b) => Or (List (a, b)))
689
778
690
779
val curr = project(cases(i).pat)
691
780
781
+ debug.println(s " ---------------reachable? ${show(curr)}" )
782
+ debug.println(s " prev: ${show(prevs)}" )
783
+
692
784
if (isSubspace(curr, prevs)) {
693
785
ctx.warning(MatchCaseUnreachable (), cases(i).body.pos)
694
786
}
0 commit comments