@@ -165,7 +165,7 @@ trait SpaceLogic {
165165 }
166166
167167 /** 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) {
169169 def tryDecompose1 (tp : Type ) = canDecompose(tp) && isSubspace(Or (decompose(tp)), b)
170170 def tryDecompose2 (tp : Type ) = canDecompose(tp) && isSubspace(a, Or (decompose(tp)))
171171
@@ -212,14 +212,14 @@ trait SpaceLogic {
212212 if (isSubType(tp2, tp1)) b
213213 else if (canDecompose(tp1)) tryDecompose1(tp1)
214214 else if (isSubType(tp1, tp2)) a // problematic corner case: inheriting a case class
215- else Empty
215+ else intersectUnrelatedAtomicTypes(tp1, tp2)
216216 case (Prod (tp1, fun, ss), Typ (tp2, _)) =>
217217 if (isSubType(tp1, tp2)) a
218218 else if (canDecompose(tp2)) tryDecompose2(tp2)
219219 else if (isSubType(tp2, tp1)) a // problematic corner case: inheriting a case class
220- else Empty
220+ else intersectUnrelatedAtomicTypes(tp1, tp2)
221221 case (Prod (tp1, fun1, ss1), Prod (tp2, fun2, ss2)) =>
222- if (! isSameUnapply(fun1, fun2)) Empty
222+ if (! isSameUnapply(fun1, fun2)) intersectUnrelatedAtomicTypes(tp1, tp2)
223223 else if (ss1.zip(ss2).exists(p => simplify(intersect(p._1, p._2)) == Empty )) Empty
224224 else Prod (tp1, fun1, ss1.zip(ss2).map((intersect _).tupled))
225225 }
@@ -500,14 +500,34 @@ class SpaceEngine(using Context) extends SpaceLogic {
500500 }
501501 }
502502
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+
503521 /** 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)))
506525 val res = if (ctx.explicitNulls) {
507526 tp1 <:< tp2
508527 } else {
509528 (tp1 != constantNullType || tp2 == constantNullType) && tp1 <:< tp2
510529 }
530+ debug.println(i " $tp1 <:< $tp2 = $res" )
511531 res
512532 }
513533
@@ -647,7 +667,6 @@ class SpaceEngine(using Context) extends SpaceLogic {
647667 parts.map(Typ (_, true ))
648668 }
649669
650-
651670 /** Abstract sealed types, or-types, Boolean and Java enums can be decomposed */
652671 def canDecompose (tp : Type ): Boolean =
653672 val res = tp.dealias match
@@ -663,7 +682,7 @@ class SpaceEngine(using Context) extends SpaceLogic {
663682 || cls.isAllOf(JavaEnumTrait )
664683 || tp.isRef(defn.BooleanClass )
665684 || tp.isRef(defn.UnitClass )
666- debug.println(s " decomposable: ${tp.show} = $res" )
685+ // debug.println(s"decomposable: ${tp.show} = $res")
667686 res
668687
669688 /** Show friendly type name with current scope in mind
@@ -747,6 +766,7 @@ class SpaceEngine(using Context) extends SpaceLogic {
747766 }
748767
749768 def show (ss : Seq [Space ]): String = ss.map(show).mkString(" , " )
769+
750770 /** Display spaces */
751771 def show (s : Space ): String = {
752772 def params (tp : Type ): List [Type ] = tp.classSymbol.primaryConstructor.info.firstParamTypes
@@ -885,49 +905,36 @@ class SpaceEngine(using Context) extends SpaceLogic {
885905
886906 if (! redundancyCheckable(sel)) return
887907
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)}" )
899913
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" )
906916
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)}" )
910919
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 )}" )
913922
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)} " )
916925
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)
930934 }
935+
936+ // in redundancy check, take guard as false in order to soundly approximate
937+ (if guard.isEmpty then covered else Empty ) :: prevs
931938 }
932939 }
933940}
0 commit comments