@@ -7,7 +7,7 @@ import core.*
77import Constants .* , Contexts .* , Decorators .* , Flags .* , NullOpsDecorator .* , Symbols .* , Types .*
88import Names .* , NameOps .* , StdNames .*
99import ast .* , tpd .*
10- import config .Printers .*
10+ import config .Printers .exhaustivity
1111import printing .{ Printer , * }, Texts .*
1212import reporting .*
1313import typer .* , Applications .* , Inferencing .* , ProtoTypes .*
@@ -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
@@ -525,14 +533,25 @@ object SpaceEngine {
525533 val mt : MethodType = unapp.widen match {
526534 case mt : MethodType => mt
527535 case pt : PolyType =>
536+ scrutineeTp match
537+ case AppliedType (tycon, targs)
538+ if unappSym.is(Synthetic )
539+ && (pt.resultType.asInstanceOf [MethodType ].paramInfos.head.typeConstructor eq tycon) =>
540+ // Special case synthetic unapply/unapplySeq's
541+ // Provided the shapes of the types match:
542+ // the scrutinee type being unapplied and
543+ // the unapply parameter type
544+ pt.instantiate(targs).asInstanceOf [MethodType ]
545+ case _ =>
528546 val locked = ctx.typerState.ownedVars
529547 val tvars = constrained(pt)
530548 val mt = pt.instantiate(tvars).asInstanceOf [MethodType ]
531- scrutineeTp <:< mt.paramInfos(0 )
549+ val unapplyArgType = mt.paramInfos.head
550+ scrutineeTp <:< unapplyArgType
532551 // force type inference to infer a narrower type: could be singleton
533552 // see tests/patmat/i4227.scala
534- mt.paramInfos( 0 ) <:< scrutineeTp
535- maximizeType(mt.paramInfos( 0 ) , Spans .NoSpan )
553+ unapplyArgType <:< scrutineeTp
554+ maximizeType(unapplyArgType , Spans .NoSpan )
536555 if ! (ctx.typerState.ownedVars -- locked).isEmpty then
537556 // constraining can create type vars out of wildcard types
538557 // (in legalBound, by using a LevelAvoidMap)
@@ -544,7 +563,7 @@ object SpaceEngine {
544563 // but I'd rather have an unassigned new-new type var, than an infinite loop.
545564 // After all, there's nothing strictly "wrong" with unassigned type vars,
546565 // it just fails TreeChecker's linting.
547- maximizeType(mt.paramInfos( 0 ) , Spans .NoSpan )
566+ maximizeType(unapplyArgType , Spans .NoSpan )
548567 mt
549568 }
550569
@@ -656,7 +675,7 @@ object SpaceEngine {
656675 case tp => (tp, Nil )
657676 val (tp, typeArgs) = getAppliedClass(tpOriginal)
658677 // This function is needed to get the arguments of the types that will be applied to the class.
659- // 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,
660679 // then this can affect whether the class will be taken into account during the exhaustiveness check
661680 def getTypeArgs (parent : Symbol , child : Symbol , typeArgs : List [Type ]): List [Type ] =
662681 val superType = child.typeRef.superType
@@ -912,50 +931,48 @@ object SpaceEngine {
912931 && ! sel.tpe.widen.isRef(defn.QuotedExprClass )
913932 && ! sel.tpe.widen.isRef(defn.QuotedTypeClass )
914933
915- def mayCoverNull (tp : Space )(using Context ): Boolean = tp match
916- case Empty => false
917- case Prod (_, _, _) => false
918- case Typ (tp, decomposed) => tp == ConstantType (Constant (null ))
919- case Or (ss) => ss.exists(mayCoverNull)
920-
921934 def checkReachability (m : Match )(using Context ): Unit = trace(i " checkReachability( $m) " ):
922935 val selTyp = toUnderlying(m.selector.tpe).dealias
923936 val isNullable = selTyp.isInstanceOf [FlexibleType ] || selTyp.classSymbol.isNullableClass
924937 val targetSpace = trace(i " targetSpace( $selTyp) " ):
925938 if isNullable && ! ctx.mode.is(Mode .SafeNulls )
926939 then project(OrType (selTyp, ConstantType (Constant (null )), soft = false ))
927940 else project(selTyp)
928-
929- @ 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 =
930943 cases match
931944 case Nil =>
932- case (c @ CaseDef (pat, guard, _)) :: rest =>
933- val patNullable = Nullables .matchesNull(c)
934- val curr = trace(i " project( $pat) " )(
935- if patNullable
936- then Or (List (project(pat), Typ (ConstantType (Constant (null )))))
937- else project(pat))
945+ case CaseDef (pat, guard, _) :: rest =>
946+ val curr = trace(i " project( $pat) " )(project(pat))
938947 val covered = trace(" covered" )(simplify(intersect(curr, targetSpace)))
939948 val prev = trace(" prev" )(simplify(Or (prevs)))
940949 if prev == Empty && covered == Empty then // defer until a case is reachable
941- recur(rest, prevs, pat :: deferred, nullCovered )
950+ recur(rest, prevs, pat :: deferred)
942951 else
943952 for pat <- deferred.reverseIterator
944953 do report.warning(MatchCaseUnreachable (), pat.srcPos)
945954
946955 if pat != EmptyTree // rethrow case of catch uses EmptyTree
947956 && ! pat.symbol.isAllOf(SyntheticCase , butNot= Method ) // ExpandSAMs default cases use SyntheticCase
948- && isSubspace(covered, Or (List (prev, Typ (ConstantType (Constant (null ))))))
949957 then
950- val nullOnly = isNullable && isWildcardArg(pat) && ! nullCovered && ! isSubspace(covered, prev) && (! ctx.explicitNulls || selTyp.isInstanceOf [FlexibleType ])
951- if nullOnly then report.warning(MatchCaseOnlyNullWarning () , pat.srcPos)
952- 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)
953970
954971 // in redundancy check, take guard as false in order to soundly approximate
955- val newPrev = if ( guard.isEmpty) then covered :: prevs else prevs
956- recur(rest, newPrev, Nil , nullCovered || (guard.isEmpty && patNullable) )
972+ val newPrev = if guard.isEmpty then covered :: prevs else prevs
973+ recur(rest, newPrev, Nil )
957974
958- recur(m.cases, Nil , Nil , false )
975+ recur(m.cases, Nil , Nil )
959976 end checkReachability
960977
961978 def checkMatch (m : Match )(using Context ): Unit =
0 commit comments