@@ -116,6 +116,7 @@ object SpaceEngine {
116116 def isSubspace (a : Space , b : Space )(using Context ): Boolean = a.isSubspace(b)
117117 def canDecompose (typ : Typ )(using Context ): Boolean = typ.canDecompose
118118 def decompose (typ : Typ )(using Context ): List [Typ ] = typ.decompose
119+ def nullSpace (using Context ): Space = Typ (ConstantType (Constant (null )), decomposed = false )
119120
120121 /** Simplify space such that a space equal to `Empty` becomes `Empty` */
121122 def computeSimplify (space : Space )(using Context ): Space = trace(i " simplify( $space) " )(space match {
@@ -336,6 +337,13 @@ object SpaceEngine {
336337 case pat : Ident if isBackquoted(pat) =>
337338 Typ (pat.tpe, decomposed = false )
338339
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+
339347 case Ident (_) | Select (_, _) =>
340348 Typ (erase(pat.tpe.stripAnnots.widenSkolem, isValue = true ), decomposed = false )
341349
@@ -667,7 +675,7 @@ object SpaceEngine {
667675 case tp => (tp, Nil )
668676 val (tp, typeArgs) = getAppliedClass(tpOriginal)
669677 // 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,
671679 // then this can affect whether the class will be taken into account during the exhaustiveness check
672680 def getTypeArgs (parent : Symbol , child : Symbol , typeArgs : List [Type ]): List [Type ] =
673681 val superType = child.typeRef.superType
@@ -923,50 +931,48 @@ object SpaceEngine {
923931 && ! sel.tpe.widen.isRef(defn.QuotedExprClass )
924932 && ! sel.tpe.widen.isRef(defn.QuotedTypeClass )
925933
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-
932934 def checkReachability (m : Match )(using Context ): Unit = trace(i " checkReachability( $m) " ):
933935 val selTyp = toUnderlying(m.selector.tpe).dealias
934936 val isNullable = selTyp.isInstanceOf [FlexibleType ] || selTyp.classSymbol.isNullableClass
935937 val targetSpace = trace(i " targetSpace( $selTyp) " ):
936938 if isNullable && ! ctx.mode.is(Mode .SafeNulls )
937939 then project(OrType (selTyp, ConstantType (Constant (null )), soft = false ))
938940 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 =
941943 cases match
942944 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))
949947 val covered = trace(" covered" )(simplify(intersect(curr, targetSpace)))
950948 val prev = trace(" prev" )(simplify(Or (prevs)))
951949 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)
953951 else
954952 for pat <- deferred.reverseIterator
955953 do report.warning(MatchCaseUnreachable (), pat.srcPos)
956954
957955 if pat != EmptyTree // rethrow case of catch uses EmptyTree
958956 && ! pat.symbol.isAllOf(SyntheticCase , butNot= Method ) // ExpandSAMs default cases use SyntheticCase
959- && isSubspace(covered, Or (List (prev, Typ (ConstantType (Constant (null ))))))
960957 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)
964970
965971 // 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 )
968974
969- recur(m.cases, Nil , Nil , false )
975+ recur(m.cases, Nil , Nil )
970976 end checkReachability
971977
972978 def checkMatch (m : Match )(using Context ): Unit =
0 commit comments