@@ -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`.
@@ -3221,14 +3201,16 @@ object TrackingTypeComparer:
3221
3201
enum MatchResult extends Showable :
3222
3202
case Reduced (tp : Type )
3223
3203
case Disjoint
3204
+ case ReducedAndDisjoint
3224
3205
case Stuck
3225
3206
case NoInstance (fails : List [(Name , TypeBounds )])
3226
3207
3227
3208
def toText (p : Printer ): Text = this match
3228
- case Reduced (tp) => " Reduced(" ~ p.toText(tp) ~ " )"
3229
- case Disjoint => " Disjoint"
3230
- case Stuck => " Stuck"
3231
- case NoInstance (fails) => " NoInstance(" ~ Text (fails.map(p.toText(_) ~ p.toText(_)), " , " ) ~ " )"
3209
+ case Reduced (tp) => " Reduced(" ~ p.toText(tp) ~ " )"
3210
+ case Disjoint => " Disjoint"
3211
+ case ReducedAndDisjoint => " ReducedAndDisjoint"
3212
+ case Stuck => " Stuck"
3213
+ case NoInstance (fails) => " NoInstance(" ~ Text (fails.map(p.toText(_) ~ p.toText(_)), " , " ) ~ " )"
3232
3214
3233
3215
class TrackingTypeComparer (initctx : Context ) extends TypeComparer (initctx) {
3234
3216
import TrackingTypeComparer .*
@@ -3323,9 +3305,13 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
3323
3305
}
3324
3306
3325
3307
def matchSubTypeTest (spec : MatchTypeCaseSpec .SubTypeTest ): MatchResult =
3308
+ val disjoint = provablyDisjoint(scrut, spec.pattern)
3326
3309
if necessarySubType(scrut, spec.pattern) then
3327
- MatchResult .Reduced (spec.body)
3328
- else if provablyDisjoint(scrut, spec.pattern) then
3310
+ if disjoint then
3311
+ MatchResult .ReducedAndDisjoint
3312
+ else
3313
+ MatchResult .Reduced (spec.body)
3314
+ else if disjoint then
3329
3315
MatchResult .Disjoint
3330
3316
else
3331
3317
MatchResult .Stuck
@@ -3466,9 +3452,12 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
3466
3452
// This might not be needed
3467
3453
val contrainedCaseLambda = constrained(spec.origMatchCase).asInstanceOf [HKTypeLambda ]
3468
3454
3469
- def tryDisjoint : MatchResult =
3455
+ val disjoint =
3470
3456
val defn .MatchCase (origPattern, _) = contrainedCaseLambda.resultType: @ unchecked
3471
- if provablyDisjoint(scrut, origPattern) then
3457
+ provablyDisjoint(scrut, origPattern)
3458
+
3459
+ def tryDisjoint : MatchResult =
3460
+ if disjoint then
3472
3461
MatchResult .Disjoint
3473
3462
else
3474
3463
MatchResult .Stuck
@@ -3484,7 +3473,10 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
3484
3473
val defn .MatchCase (instantiatedPat, reduced) =
3485
3474
instantiateParamsSpec(instances, contrainedCaseLambda)(contrainedCaseLambda.resultType): @ unchecked
3486
3475
if scrut <:< instantiatedPat then
3487
- MatchResult .Reduced (reduced)
3476
+ if disjoint then
3477
+ MatchResult .ReducedAndDisjoint
3478
+ else
3479
+ MatchResult .Reduced (reduced)
3488
3480
else
3489
3481
tryDisjoint
3490
3482
else
@@ -3508,6 +3500,8 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
3508
3500
this .poisoned = savedPoisoned
3509
3501
this .canWidenAbstract = saved
3510
3502
3503
+ val disjoint = provablyDisjoint(scrut, pat)
3504
+
3511
3505
def redux (canApprox : Boolean ): MatchResult =
3512
3506
val instances = paramInstances(canApprox)(Array .fill(caseLambda.paramNames.length)(NoType ), pat)
3513
3507
instantiateParams(instances)(body) match
@@ -3518,13 +3512,16 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
3518
3512
}
3519
3513
}
3520
3514
case redux =>
3521
- MatchResult .Reduced (redux)
3515
+ if disjoint then
3516
+ MatchResult .ReducedAndDisjoint
3517
+ else
3518
+ MatchResult .Reduced (redux)
3522
3519
3523
3520
if matches(canWidenAbstract = false ) then
3524
3521
redux(canApprox = true )
3525
3522
else if matches(canWidenAbstract = true ) then
3526
3523
redux(canApprox = false )
3527
- else if (provablyDisjoint(scrut, pat) )
3524
+ else if (disjoint )
3528
3525
// We found a proof that `scrut` and `pat` are incompatible.
3529
3526
// The search continues.
3530
3527
MatchResult .Disjoint
@@ -3551,28 +3548,22 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
3551
3548
NoType
3552
3549
case MatchResult .Reduced (tp) =>
3553
3550
tp.simplified
3551
+ case MatchResult .ReducedAndDisjoint =>
3552
+ // Empty types break the basic assumption that if a scrutinee and a
3553
+ // pattern are disjoint it's OK to reduce passed that pattern. Indeed,
3554
+ // empty types viewed as a set of value is always a subset of any other
3555
+ // types. As a result, if a scrutinee both matches a pattern and is
3556
+ // probably disjoint from it, we prevent reduction.
3557
+ // See `tests/neg/6570.scala` and `6570-1.scala` for examples that
3558
+ // exploit emptiness to break match type soundness.
3559
+ MatchTypeTrace .emptyScrutinee(scrut)
3560
+ NoType
3554
3561
case Nil =>
3555
3562
val casesText = MatchTypeTrace .noMatchesText(scrut, cases)
3556
3563
ErrorType (reporting.MatchTypeNoCases (casesText))
3557
3564
3558
3565
inFrozenConstraint {
3559
- // Empty types break the basic assumption that if a scrutinee and a
3560
- // pattern are disjoint it's OK to reduce passed that pattern. Indeed,
3561
- // empty types viewed as a set of value is always a subset of any other
3562
- // types. As a result, we first check that the scrutinee isn't empty
3563
- // before proceeding with reduction. See `tests/neg/6570.scala` and
3564
- // `6570-1.scala` for examples that exploit emptiness to break match
3565
- // type soundness.
3566
-
3567
- // If we revered the uncertainty case of this empty check, that is,
3568
- // `!provablyNonEmpty` instead of `provablyEmpty`, that would be
3569
- // obviously sound, but quite restrictive. With the current formulation,
3570
- // we need to be careful that `provablyEmpty` covers all the conditions
3571
- // used to conclude disjointness in `provablyDisjoint`.
3572
- if (provablyEmpty(scrut))
3573
- MatchTypeTrace .emptyScrutinee(scrut)
3574
- NoType
3575
- else if scrut.isError then
3566
+ if scrut.isError then
3576
3567
// if the scrutinee is an error type
3577
3568
// then just return that as the result
3578
3569
// not doing so will result in the first type case matching
0 commit comments