@@ -2771,26 +2771,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
27712771 false
27722772 } || tycon.derivesFrom(defn.PairClass )
27732773
2774- /** Is `tp` an empty type?
2775- *
2776- * `true` implies that we found a proof; uncertainty defaults to `false`.
2777- */
2778- def provablyEmpty (tp : Type ): Boolean =
2779- tp.dealias match {
2780- case tp if tp.isExactlyNothing => true
2781- case AndType (tp1, tp2) => provablyDisjoint(tp1, tp2)
2782- case OrType (tp1, tp2) => provablyEmpty(tp1) && provablyEmpty(tp2)
2783- case at @ AppliedType (tycon, args) =>
2784- args.lazyZip(tycon.typeParams).exists { (arg, tparam) =>
2785- tparam.paramVarianceSign >= 0
2786- && provablyEmpty(arg)
2787- && typeparamCorrespondsToField(tycon, tparam)
2788- }
2789- case tp : TypeProxy =>
2790- provablyEmpty(tp.underlying)
2791- case _ => false
2792- }
2793-
27942774 /** Are `tp1` and `tp2` provablyDisjoint types?
27952775 *
27962776 * `true` implies that we found a proof; uncertainty defaults to `false`.
@@ -3234,14 +3214,16 @@ object TrackingTypeComparer:
32343214 enum MatchResult extends Showable :
32353215 case Reduced (tp : Type )
32363216 case Disjoint
3217+ case ReducedAndDisjoint
32373218 case Stuck
32383219 case NoInstance (fails : List [(Name , TypeBounds )])
32393220
32403221 def toText (p : Printer ): Text = this match
3241- case Reduced (tp) => " Reduced(" ~ p.toText(tp) ~ " )"
3242- case Disjoint => " Disjoint"
3243- case Stuck => " Stuck"
3244- case NoInstance (fails) => " NoInstance(" ~ Text (fails.map(p.toText(_) ~ p.toText(_)), " , " ) ~ " )"
3222+ case Reduced (tp) => " Reduced(" ~ p.toText(tp) ~ " )"
3223+ case Disjoint => " Disjoint"
3224+ case ReducedAndDisjoint => " ReducedAndDisjoint"
3225+ case Stuck => " Stuck"
3226+ case NoInstance (fails) => " NoInstance(" ~ Text (fails.map(p.toText(_) ~ p.toText(_)), " , " ) ~ " )"
32453227
32463228class TrackingTypeComparer (initctx : Context ) extends TypeComparer (initctx) {
32473229 import TrackingTypeComparer .*
@@ -3336,9 +3318,13 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
33363318 }
33373319
33383320 def matchSubTypeTest (spec : MatchTypeCaseSpec .SubTypeTest ): MatchResult =
3321+ val disjoint = provablyDisjoint(scrut, spec.pattern)
33393322 if necessarySubType(scrut, spec.pattern) then
3340- MatchResult .Reduced (spec.body)
3341- else if provablyDisjoint(scrut, spec.pattern) then
3323+ if disjoint then
3324+ MatchResult .ReducedAndDisjoint
3325+ else
3326+ MatchResult .Reduced (spec.body)
3327+ else if disjoint then
33423328 MatchResult .Disjoint
33433329 else
33443330 MatchResult .Stuck
@@ -3472,9 +3458,12 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
34723458 // This might not be needed
34733459 val constrainedCaseLambda = constrained(spec.origMatchCase, ast.tpd.EmptyTree )._1.asInstanceOf [HKTypeLambda ]
34743460
3475- def tryDisjoint : MatchResult =
3461+ val disjoint =
34763462 val defn .MatchCase (origPattern, _) = constrainedCaseLambda.resultType: @ unchecked
3477- if provablyDisjoint(scrut, origPattern) then
3463+ provablyDisjoint(scrut, origPattern)
3464+
3465+ def tryDisjoint : MatchResult =
3466+ if disjoint then
34783467 MatchResult .Disjoint
34793468 else
34803469 MatchResult .Stuck
@@ -3490,7 +3479,10 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
34903479 val defn .MatchCase (instantiatedPat, reduced) =
34913480 instantiateParamsSpec(instances, constrainedCaseLambda)(constrainedCaseLambda.resultType): @ unchecked
34923481 if scrut <:< instantiatedPat then
3493- MatchResult .Reduced (reduced)
3482+ if disjoint then
3483+ MatchResult .ReducedAndDisjoint
3484+ else
3485+ MatchResult .Reduced (reduced)
34943486 else
34953487 tryDisjoint
34963488 else
@@ -3514,6 +3506,8 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
35143506 this .poisoned = savedPoisoned
35153507 this .canWidenAbstract = saved
35163508
3509+ val disjoint = provablyDisjoint(scrut, pat)
3510+
35173511 def redux (canApprox : Boolean ): MatchResult =
35183512 val instances = paramInstances(canApprox)(Array .fill(caseLambda.paramNames.length)(NoType ), pat)
35193513 instantiateParams(instances)(body) match
@@ -3524,13 +3518,16 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
35243518 }
35253519 }
35263520 case redux =>
3527- MatchResult .Reduced (redux)
3521+ if disjoint then
3522+ MatchResult .ReducedAndDisjoint
3523+ else
3524+ MatchResult .Reduced (redux)
35283525
35293526 if matches(canWidenAbstract = false ) then
35303527 redux(canApprox = true )
35313528 else if matches(canWidenAbstract = true ) then
35323529 redux(canApprox = false )
3533- else if (provablyDisjoint(scrut, pat) )
3530+ else if (disjoint )
35343531 // We found a proof that `scrut` and `pat` are incompatible.
35353532 // The search continues.
35363533 MatchResult .Disjoint
@@ -3557,28 +3554,22 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
35573554 NoType
35583555 case MatchResult .Reduced (tp) =>
35593556 tp.simplified
3557+ case MatchResult .ReducedAndDisjoint =>
3558+ // Empty types break the basic assumption that if a scrutinee and a
3559+ // pattern are disjoint it's OK to reduce passed that pattern. Indeed,
3560+ // empty types viewed as a set of value is always a subset of any other
3561+ // types. As a result, if a scrutinee both matches a pattern and is
3562+ // probably disjoint from it, we prevent reduction.
3563+ // See `tests/neg/6570.scala` and `6570-1.scala` for examples that
3564+ // exploit emptiness to break match type soundness.
3565+ MatchTypeTrace .emptyScrutinee(scrut)
3566+ NoType
35603567 case Nil =>
35613568 val casesText = MatchTypeTrace .noMatchesText(scrut, cases)
35623569 ErrorType (reporting.MatchTypeNoCases (casesText))
35633570
35643571 inFrozenConstraint {
3565- // Empty types break the basic assumption that if a scrutinee and a
3566- // pattern are disjoint it's OK to reduce passed that pattern. Indeed,
3567- // empty types viewed as a set of value is always a subset of any other
3568- // types. As a result, we first check that the scrutinee isn't empty
3569- // before proceeding with reduction. See `tests/neg/6570.scala` and
3570- // `6570-1.scala` for examples that exploit emptiness to break match
3571- // type soundness.
3572-
3573- // If we revered the uncertainty case of this empty check, that is,
3574- // `!provablyNonEmpty` instead of `provablyEmpty`, that would be
3575- // obviously sound, but quite restrictive. With the current formulation,
3576- // we need to be careful that `provablyEmpty` covers all the conditions
3577- // used to conclude disjointness in `provablyDisjoint`.
3578- if (provablyEmpty(scrut))
3579- MatchTypeTrace .emptyScrutinee(scrut)
3580- NoType
3581- else if scrut.isError then
3572+ if scrut.isError then
35823573 // if the scrutinee is an error type
35833574 // then just return that as the result
35843575 // not doing so will result in the first type case matching
0 commit comments