@@ -116,6 +116,7 @@ object SpaceEngine {
116
116
def isSubspace (a : Space , b : Space )(using Context ): Boolean = a.isSubspace(b)
117
117
def canDecompose (typ : Typ )(using Context ): Boolean = typ.canDecompose
118
118
def decompose (typ : Typ )(using Context ): List [Typ ] = typ.decompose
119
+ def nullSpace (using Context ): Space = Typ (ConstantType (Constant (null )), decomposed = false )
119
120
120
121
/** Simplify space such that a space equal to `Empty` becomes `Empty` */
121
122
def computeSimplify (space : Space )(using Context ): Space = trace(i " simplify( $space) " )(space match {
@@ -336,6 +337,13 @@ object SpaceEngine {
336
337
case pat : Ident if isBackquoted(pat) =>
337
338
Typ (pat.tpe, decomposed = false )
338
339
340
+ case Ident (nme.WILDCARD ) =>
341
+ val tp = pat.tpe.stripAnnots.widenSkolem
342
+ val isNullable = tp.isInstanceOf [FlexibleType ] || tp.classSymbol.isNullableClass
343
+ val tpSpace = Typ (erase(tp, isValue = true ), decomposed = false )
344
+ if isNullable then Or (tpSpace :: nullSpace :: Nil )
345
+ else tpSpace
346
+
339
347
case Ident (_) | Select (_, _) =>
340
348
Typ (erase(pat.tpe.stripAnnots.widenSkolem, isValue = true ), decomposed = false )
341
349
@@ -667,7 +675,7 @@ object SpaceEngine {
667
675
case tp => (tp, Nil )
668
676
val (tp, typeArgs) = getAppliedClass(tpOriginal)
669
677
// This function is needed to get the arguments of the types that will be applied to the class.
670
- // This is necessary because if the arguments of the types contain Nothing,
678
+ // This is necessary because if the arguments of the types contain Nothing,
671
679
// then this can affect whether the class will be taken into account during the exhaustiveness check
672
680
def getTypeArgs (parent : Symbol , child : Symbol , typeArgs : List [Type ]): List [Type ] =
673
681
val superType = child.typeRef.superType
@@ -923,50 +931,48 @@ object SpaceEngine {
923
931
&& ! sel.tpe.widen.isRef(defn.QuotedExprClass )
924
932
&& ! sel.tpe.widen.isRef(defn.QuotedTypeClass )
925
933
926
- def mayCoverNull (tp : Space )(using Context ): Boolean = tp match
927
- case Empty => false
928
- case Prod (_, _, _) => false
929
- case Typ (tp, decomposed) => tp == ConstantType (Constant (null ))
930
- case Or (ss) => ss.exists(mayCoverNull)
931
-
932
934
def checkReachability (m : Match )(using Context ): Unit = trace(i " checkReachability( $m) " ):
933
935
val selTyp = toUnderlying(m.selector.tpe).dealias
934
936
val isNullable = selTyp.isInstanceOf [FlexibleType ] || selTyp.classSymbol.isNullableClass
935
937
val targetSpace = trace(i " targetSpace( $selTyp) " ):
936
938
if isNullable && ! ctx.mode.is(Mode .SafeNulls )
937
939
then project(OrType (selTyp, ConstantType (Constant (null )), soft = false ))
938
940
else project(selTyp)
939
-
940
- @ tailrec def recur (cases : List [CaseDef ], prevs : List [Space ], deferred : List [Tree ], nullCovered : Boolean ): Unit =
941
+ var hadNullOnly = false
942
+ @ tailrec def recur (cases : List [CaseDef ], prevs : List [Space ], deferred : List [Tree ]): Unit =
941
943
cases match
942
944
case Nil =>
943
- case (c @ CaseDef (pat, guard, _)) :: rest =>
944
- val patNullable = Nullables .matchesNull(c)
945
- val curr = trace(i " project( $pat) " )(
946
- if patNullable
947
- then Or (List (project(pat), Typ (ConstantType (Constant (null )))))
948
- else project(pat))
945
+ case CaseDef (pat, guard, _) :: rest =>
946
+ val curr = trace(i " project( $pat) " )(project(pat))
949
947
val covered = trace(" covered" )(simplify(intersect(curr, targetSpace)))
950
948
val prev = trace(" prev" )(simplify(Or (prevs)))
951
949
if prev == Empty && covered == Empty then // defer until a case is reachable
952
- recur(rest, prevs, pat :: deferred, nullCovered )
950
+ recur(rest, prevs, pat :: deferred)
953
951
else
954
952
for pat <- deferred.reverseIterator
955
953
do report.warning(MatchCaseUnreachable (), pat.srcPos)
956
954
957
955
if pat != EmptyTree // rethrow case of catch uses EmptyTree
958
956
&& ! pat.symbol.isAllOf(SyntheticCase , butNot= Method ) // ExpandSAMs default cases use SyntheticCase
959
- && isSubspace(covered, Or (List (prev, Typ (ConstantType (Constant (null ))))))
960
957
then
961
- val nullOnly = isNullable && isWildcardArg(pat) && ! nullCovered && ! isSubspace(covered, prev) && (! ctx.explicitNulls || selTyp.isInstanceOf [FlexibleType ])
962
- if nullOnly then report.warning(MatchCaseOnlyNullWarning () , pat.srcPos)
963
- else if (isSubspace(covered, prev)) then report.warning(MatchCaseUnreachable (), pat.srcPos)
958
+ if isSubspace(covered, prev) then
959
+ report.warning(MatchCaseUnreachable (), pat.srcPos)
960
+ else if isNullable && ! hadNullOnly && isWildcardArg(pat)
961
+ && isSubspace(covered, Or (prev :: nullSpace :: Nil )) then
962
+ // Issue OnlyNull warning only if:
963
+ // 1. The target space is nullable;
964
+ // 2. OnlyNull warning has not been issued before;
965
+ // 3. The pattern is a wildcard pattern;
966
+ // 4. The pattern is not covered by the previous cases,
967
+ // but covered by the previous cases with null.
968
+ hadNullOnly = true
969
+ report.warning(MatchCaseOnlyNullWarning (), pat.srcPos)
964
970
965
971
// in redundancy check, take guard as false in order to soundly approximate
966
- val newPrev = if ( guard.isEmpty) then covered :: prevs else prevs
967
- recur(rest, newPrev, Nil , nullCovered || (guard.isEmpty && patNullable) )
972
+ val newPrev = if guard.isEmpty then covered :: prevs else prevs
973
+ recur(rest, newPrev, Nil )
968
974
969
- recur(m.cases, Nil , Nil , false )
975
+ recur(m.cases, Nil , Nil )
970
976
end checkReachability
971
977
972
978
def checkMatch (m : Match )(using Context ): Unit =
0 commit comments