@@ -165,7 +165,7 @@ trait SpaceLogic {
165
165
}
166
166
167
167
/** Is `a` a subspace of `b`? Equivalent to `a - b == Empty`, but faster */
168
- def isSubspace (a : Space , b : Space )(using Context ): Boolean = trace(s " ${show(a)} < ${show(b)}" , debug) {
168
+ def isSubspace (a : Space , b : Space )(using Context ): Boolean = trace(s " isSubspace( ${show(a)}, ${show(b)}) " , debug) {
169
169
def tryDecompose1 (tp : Type ) = canDecompose(tp) && isSubspace(Or (decompose(tp)), b)
170
170
def tryDecompose2 (tp : Type ) = canDecompose(tp) && isSubspace(a, Or (decompose(tp)))
171
171
@@ -212,14 +212,14 @@ trait SpaceLogic {
212
212
if (isSubType(tp2, tp1)) b
213
213
else if (canDecompose(tp1)) tryDecompose1(tp1)
214
214
else if (isSubType(tp1, tp2)) a // problematic corner case: inheriting a case class
215
- else Empty
215
+ else intersectUnrelatedAtomicTypes(tp1, tp2)
216
216
case (Prod (tp1, fun, ss), Typ (tp2, _)) =>
217
217
if (isSubType(tp1, tp2)) a
218
218
else if (canDecompose(tp2)) tryDecompose2(tp2)
219
219
else if (isSubType(tp2, tp1)) a // problematic corner case: inheriting a case class
220
- else Empty
220
+ else intersectUnrelatedAtomicTypes(tp1, tp2)
221
221
case (Prod (tp1, fun1, ss1), Prod (tp2, fun2, ss2)) =>
222
- if (! isSameUnapply(fun1, fun2)) Empty
222
+ if (! isSameUnapply(fun1, fun2)) intersectUnrelatedAtomicTypes(tp1, tp2)
223
223
else if (ss1.zip(ss2).exists(p => simplify(intersect(p._1, p._2)) == Empty )) Empty
224
224
else Prod (tp1, fun1, ss1.zip(ss2).map((intersect _).tupled))
225
225
}
@@ -500,14 +500,34 @@ class SpaceEngine(using Context) extends SpaceLogic {
500
500
}
501
501
}
502
502
503
+ /** Numeric literals, while being constant values of unrelated types (e.g. Char and Int),
504
+ * when used in a case may end up matching at runtime, because their equals may returns true.
505
+ * Because these are universally available, general purpose types, it would be good to avoid
506
+ * returning false positive warnings, such as in `(c: Char) match { case 67 => ... }` emitting a
507
+ * reachability warning on the case. So the type `ConstantType(Constant(67, IntTag))` is
508
+ * converted to `ConstantType(Constant(67, CharTag))`. #12805 */
509
+ def convertConstantType (tp : Type , pt : Type ): Type = tp match
510
+ case tp @ ConstantType (const) =>
511
+ val converted = const.convertTo(pt)
512
+ if converted == null then tp else ConstantType (converted)
513
+ case _ => tp
514
+
515
+ /** Adapt types by performing primitive value boxing. #12805 */
516
+ def maybeBox (tp1 : Type , tp2 : Type ): Type =
517
+ if tp1.classSymbol.isPrimitiveValueClass && ! tp2.classSymbol.isPrimitiveValueClass then
518
+ defn.boxedType(tp1).narrow
519
+ else tp1
520
+
503
521
/** Is `tp1` a subtype of `tp2`? */
504
- def isSubType (tp1 : Type , tp2 : Type ): Boolean = {
505
- debug.println(TypeComparer .explained(_.isSubType(tp1, tp2)))
522
+ def isSubType (_tp1 : Type , tp2 : Type ): Boolean = {
523
+ val tp1 = maybeBox(convertConstantType(_tp1, tp2), tp2)
524
+ // debug.println(TypeComparer.explained(_.isSubType(tp1, tp2)))
506
525
val res = if (ctx.explicitNulls) {
507
526
tp1 <:< tp2
508
527
} else {
509
528
(tp1 != constantNullType || tp2 == constantNullType) && tp1 <:< tp2
510
529
}
530
+ debug.println(i " $tp1 <:< $tp2 = $res" )
511
531
res
512
532
}
513
533
@@ -647,7 +667,6 @@ class SpaceEngine(using Context) extends SpaceLogic {
647
667
parts.map(Typ (_, true ))
648
668
}
649
669
650
-
651
670
/** Abstract sealed types, or-types, Boolean and Java enums can be decomposed */
652
671
def canDecompose (tp : Type ): Boolean =
653
672
val res = tp.dealias match
@@ -663,7 +682,7 @@ class SpaceEngine(using Context) extends SpaceLogic {
663
682
|| cls.isAllOf(JavaEnumTrait )
664
683
|| tp.isRef(defn.BooleanClass )
665
684
|| tp.isRef(defn.UnitClass )
666
- debug.println(s " decomposable: ${tp.show} = $res" )
685
+ // debug.println(s"decomposable: ${tp.show} = $res")
667
686
res
668
687
669
688
/** Show friendly type name with current scope in mind
@@ -747,6 +766,7 @@ class SpaceEngine(using Context) extends SpaceLogic {
747
766
}
748
767
749
768
def show (ss : Seq [Space ]): String = ss.map(show).mkString(" , " )
769
+
750
770
/** Display spaces */
751
771
def show (s : Space ): String = {
752
772
def params (tp : Type ): List [Type ] = tp.classSymbol.primaryConstructor.info.firstParamTypes
@@ -885,49 +905,36 @@ class SpaceEngine(using Context) extends SpaceLogic {
885
905
886
906
if (! redundancyCheckable(sel)) return
887
907
888
- val targetSpace =
889
- if ! selTyp.classSymbol.isNullableClass then
890
- project(selTyp)
891
- else
892
- project(OrType (selTyp, constantNullType, soft = false ))
893
-
894
- // in redundancy check, take guard as false in order to soundly approximate
895
- val spaces = cases.map { x =>
896
- val res =
897
- if (x.guard.isEmpty) project(x.pat)
898
- else Empty
908
+ val isNullable = selTyp.classSymbol.isNullableClass
909
+ val targetSpace = if isNullable
910
+ then project(OrType (selTyp, constantNullType, soft = false ))
911
+ else project(selTyp)
912
+ debug.println(s " targetSpace: ${show(targetSpace)}" )
899
913
900
- debug.println(s " ${x.pat.show} ====> ${res}" )
901
- res
902
- }
903
-
904
- (1 until cases.length).foreach { i =>
905
- val pat = cases(i).pat
914
+ cases.iterator.zipWithIndex.foldLeft(Nil : List [Space ]) { case (prevs, (CaseDef (pat, guard, _), i)) =>
915
+ debug.println(i " case pattern: $pat" )
906
916
907
- if (pat != EmptyTree ) { // rethrow case of catch uses EmptyTree
908
- val prevs = Or (spaces.take(i))
909
- val curr = project(pat)
917
+ val curr = project(pat)
918
+ debug.println(i " reachable? ${show(curr)}" )
910
919
911
- debug.println( s " ---------------reachable? ${show(curr)} " )
912
- debug.println(s " prev: ${show(prevs )}" )
920
+ val prev = simplify( Or (prevs) )
921
+ debug.println(s " prev: ${show(prev )}" )
913
922
914
- var covered = simplify(intersect(curr, targetSpace))
915
- debug.println(s " covered: $covered" )
923
+ val covered = simplify(intersect(curr, targetSpace))
924
+ debug.println(s " covered: ${show( covered)} " )
916
925
917
- // `covered == Empty` may happen for primitive types with auto-conversion
918
- // see tests/patmat/reader.scala tests/patmat/byte.scala
919
- if (covered == Empty && ! isNullLit(pat)) covered = curr
920
-
921
- if (isSubspace(covered, prevs)) {
922
- if i == cases.length - 1
923
- && isWildcardArg(pat)
924
- && pat.tpe.classSymbol.isNullableClass
925
- then
926
- report.warning(MatchCaseOnlyNullWarning (), pat.srcPos)
927
- else
928
- report.warning(MatchCaseUnreachable (), pat.srcPos)
929
- }
926
+ if pat != EmptyTree // rethrow case of catch uses EmptyTree
927
+ && prev != Empty // avoid isSubspace(Empty, Empty) - one of the previous cases much be reachable
928
+ && isSubspace(covered, prev)
929
+ then {
930
+ if isNullable && i == cases.length - 1 && isWildcardArg(pat) then
931
+ report.warning(MatchCaseOnlyNullWarning (), pat.srcPos)
932
+ else
933
+ report.warning(MatchCaseUnreachable (), pat.srcPos)
930
934
}
935
+
936
+ // in redundancy check, take guard as false in order to soundly approximate
937
+ (if guard.isEmpty then covered else Empty ) :: prevs
931
938
}
932
939
}
933
940
}
0 commit comments