@@ -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" 
950948          val  prev  =  trace(" prev" 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