@@ -2764,26 +2764,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
2764
2764
false
2765
2765
} || tycon.derivesFrom(defn.PairClass )
2766
2766
2767
- /** Is `tp` an empty type?
2768
- *
2769
- * `true` implies that we found a proof; uncertainty defaults to `false`.
2770
- */
2771
- def provablyEmpty (tp : Type ): Boolean =
2772
- tp.dealias match {
2773
- case tp if tp.isExactlyNothing => true
2774
- case AndType (tp1, tp2) => provablyDisjoint(tp1, tp2)
2775
- case OrType (tp1, tp2) => provablyEmpty(tp1) && provablyEmpty(tp2)
2776
- case at @ AppliedType (tycon, args) =>
2777
- args.lazyZip(tycon.typeParams).exists { (arg, tparam) =>
2778
- tparam.paramVarianceSign >= 0
2779
- && provablyEmpty(arg)
2780
- && typeparamCorrespondsToField(tycon, tparam)
2781
- }
2782
- case tp : TypeProxy =>
2783
- provablyEmpty(tp.underlying)
2784
- case _ => false
2785
- }
2786
-
2787
2767
/** Are `tp1` and `tp2` provablyDisjoint types?
2788
2768
*
2789
2769
* `true` implies that we found a proof; uncertainty defaults to `false`.
@@ -3224,14 +3204,16 @@ object TrackingTypeComparer:
3224
3204
enum MatchResult extends Showable :
3225
3205
case Reduced (tp : Type )
3226
3206
case Disjoint
3207
+ case ReducedAndDisjoint
3227
3208
case Stuck
3228
3209
case NoInstance (fails : List [(Name , TypeBounds )])
3229
3210
3230
3211
def toText (p : Printer ): Text = this match
3231
- case Reduced (tp) => " Reduced(" ~ p.toText(tp) ~ " )"
3232
- case Disjoint => " Disjoint"
3233
- case Stuck => " Stuck"
3234
- case NoInstance (fails) => " NoInstance(" ~ Text (fails.map(p.toText(_) ~ p.toText(_)), " , " ) ~ " )"
3212
+ case Reduced (tp) => " Reduced(" ~ p.toText(tp) ~ " )"
3213
+ case Disjoint => " Disjoint"
3214
+ case ReducedAndDisjoint => " ReducedAndDisjoint"
3215
+ case Stuck => " Stuck"
3216
+ case NoInstance (fails) => " NoInstance(" ~ Text (fails.map(p.toText(_) ~ p.toText(_)), " , " ) ~ " )"
3235
3217
3236
3218
class TrackingTypeComparer (initctx : Context ) extends TypeComparer (initctx) {
3237
3219
import TrackingTypeComparer .*
@@ -3326,9 +3308,13 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
3326
3308
}
3327
3309
3328
3310
def matchSubTypeTest (spec : MatchTypeCaseSpec .SubTypeTest ): MatchResult =
3311
+ val disjoint = provablyDisjoint(scrut, spec.pattern)
3329
3312
if necessarySubType(scrut, spec.pattern) then
3330
- MatchResult .Reduced (spec.body)
3331
- else if provablyDisjoint(scrut, spec.pattern) then
3313
+ if disjoint then
3314
+ MatchResult .ReducedAndDisjoint
3315
+ else
3316
+ MatchResult .Reduced (spec.body)
3317
+ else if disjoint then
3332
3318
MatchResult .Disjoint
3333
3319
else
3334
3320
MatchResult .Stuck
@@ -3469,9 +3455,12 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
3469
3455
// This might not be needed
3470
3456
val contrainedCaseLambda = constrained(spec.origMatchCase).asInstanceOf [HKTypeLambda ]
3471
3457
3472
- def tryDisjoint : MatchResult =
3458
+ val disjoint =
3473
3459
val defn .MatchCase (origPattern, _) = contrainedCaseLambda.resultType: @ unchecked
3474
- if provablyDisjoint(scrut, origPattern) then
3460
+ provablyDisjoint(scrut, origPattern)
3461
+
3462
+ def tryDisjoint : MatchResult =
3463
+ if disjoint then
3475
3464
MatchResult .Disjoint
3476
3465
else
3477
3466
MatchResult .Stuck
@@ -3487,7 +3476,10 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
3487
3476
val defn .MatchCase (instantiatedPat, reduced) =
3488
3477
instantiateParamsSpec(instances, contrainedCaseLambda)(contrainedCaseLambda.resultType): @ unchecked
3489
3478
if scrut <:< instantiatedPat then
3490
- MatchResult .Reduced (reduced)
3479
+ if disjoint then
3480
+ MatchResult .ReducedAndDisjoint
3481
+ else
3482
+ MatchResult .Reduced (reduced)
3491
3483
else
3492
3484
tryDisjoint
3493
3485
else
@@ -3511,6 +3503,8 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
3511
3503
this .poisoned = savedPoisoned
3512
3504
this .canWidenAbstract = saved
3513
3505
3506
+ val disjoint = provablyDisjoint(scrut, pat)
3507
+
3514
3508
def redux (canApprox : Boolean ): MatchResult =
3515
3509
val instances = paramInstances(canApprox)(Array .fill(caseLambda.paramNames.length)(NoType ), pat)
3516
3510
instantiateParams(instances)(body) match
@@ -3521,13 +3515,16 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
3521
3515
}
3522
3516
}
3523
3517
case redux =>
3524
- MatchResult .Reduced (redux)
3518
+ if disjoint then
3519
+ MatchResult .ReducedAndDisjoint
3520
+ else
3521
+ MatchResult .Reduced (redux)
3525
3522
3526
3523
if matches(canWidenAbstract = false ) then
3527
3524
redux(canApprox = true )
3528
3525
else if matches(canWidenAbstract = true ) then
3529
3526
redux(canApprox = false )
3530
- else if (provablyDisjoint(scrut, pat) )
3527
+ else if (disjoint )
3531
3528
// We found a proof that `scrut` and `pat` are incompatible.
3532
3529
// The search continues.
3533
3530
MatchResult .Disjoint
@@ -3554,28 +3551,22 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
3554
3551
NoType
3555
3552
case MatchResult .Reduced (tp) =>
3556
3553
tp.simplified
3554
+ case MatchResult .ReducedAndDisjoint =>
3555
+ // Empty types break the basic assumption that if a scrutinee and a
3556
+ // pattern are disjoint it's OK to reduce passed that pattern. Indeed,
3557
+ // empty types viewed as a set of value is always a subset of any other
3558
+ // types. As a result, if a scrutinee both matches a pattern and is
3559
+ // probably disjoint from it, we prevent reduction.
3560
+ // See `tests/neg/6570.scala` and `6570-1.scala` for examples that
3561
+ // exploit emptiness to break match type soundness.
3562
+ MatchTypeTrace .emptyScrutinee(scrut)
3563
+ NoType
3557
3564
case Nil =>
3558
3565
val casesText = MatchTypeTrace .noMatchesText(scrut, cases)
3559
3566
ErrorType (reporting.MatchTypeNoCases (casesText))
3560
3567
3561
3568
inFrozenConstraint {
3562
- // Empty types break the basic assumption that if a scrutinee and a
3563
- // pattern are disjoint it's OK to reduce passed that pattern. Indeed,
3564
- // empty types viewed as a set of value is always a subset of any other
3565
- // types. As a result, we first check that the scrutinee isn't empty
3566
- // before proceeding with reduction. See `tests/neg/6570.scala` and
3567
- // `6570-1.scala` for examples that exploit emptiness to break match
3568
- // type soundness.
3569
-
3570
- // If we revered the uncertainty case of this empty check, that is,
3571
- // `!provablyNonEmpty` instead of `provablyEmpty`, that would be
3572
- // obviously sound, but quite restrictive. With the current formulation,
3573
- // we need to be careful that `provablyEmpty` covers all the conditions
3574
- // used to conclude disjointness in `provablyDisjoint`.
3575
- if (provablyEmpty(scrut))
3576
- MatchTypeTrace .emptyScrutinee(scrut)
3577
- NoType
3578
- else if scrut.isError then
3569
+ if scrut.isError then
3579
3570
// if the scrutinee is an error type
3580
3571
// then just return that as the result
3581
3572
// not doing so will result in the first type case matching
0 commit comments