@@ -2771,26 +2771,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
2771
2771
false
2772
2772
} || tycon.derivesFrom(defn.PairClass )
2773
2773
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
-
2794
2774
/** Are `tp1` and `tp2` provablyDisjoint types?
2795
2775
*
2796
2776
* `true` implies that we found a proof; uncertainty defaults to `false`.
@@ -3234,14 +3214,16 @@ object TrackingTypeComparer:
3234
3214
enum MatchResult extends Showable :
3235
3215
case Reduced (tp : Type )
3236
3216
case Disjoint
3217
+ case ReducedAndDisjoint
3237
3218
case Stuck
3238
3219
case NoInstance (fails : List [(Name , TypeBounds )])
3239
3220
3240
3221
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(_)), " , " ) ~ " )"
3245
3227
3246
3228
class TrackingTypeComparer (initctx : Context ) extends TypeComparer (initctx) {
3247
3229
import TrackingTypeComparer .*
@@ -3336,9 +3318,13 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
3336
3318
}
3337
3319
3338
3320
def matchSubTypeTest (spec : MatchTypeCaseSpec .SubTypeTest ): MatchResult =
3321
+ val disjoint = provablyDisjoint(scrut, spec.pattern)
3339
3322
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
3342
3328
MatchResult .Disjoint
3343
3329
else
3344
3330
MatchResult .Stuck
@@ -3472,9 +3458,12 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
3472
3458
// This might not be needed
3473
3459
val constrainedCaseLambda = constrained(spec.origMatchCase, ast.tpd.EmptyTree )._1.asInstanceOf [HKTypeLambda ]
3474
3460
3475
- def tryDisjoint : MatchResult =
3461
+ val disjoint =
3476
3462
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
3478
3467
MatchResult .Disjoint
3479
3468
else
3480
3469
MatchResult .Stuck
@@ -3490,7 +3479,10 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
3490
3479
val defn .MatchCase (instantiatedPat, reduced) =
3491
3480
instantiateParamsSpec(instances, constrainedCaseLambda)(constrainedCaseLambda.resultType): @ unchecked
3492
3481
if scrut <:< instantiatedPat then
3493
- MatchResult .Reduced (reduced)
3482
+ if disjoint then
3483
+ MatchResult .ReducedAndDisjoint
3484
+ else
3485
+ MatchResult .Reduced (reduced)
3494
3486
else
3495
3487
tryDisjoint
3496
3488
else
@@ -3514,6 +3506,8 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
3514
3506
this .poisoned = savedPoisoned
3515
3507
this .canWidenAbstract = saved
3516
3508
3509
+ val disjoint = provablyDisjoint(scrut, pat)
3510
+
3517
3511
def redux (canApprox : Boolean ): MatchResult =
3518
3512
val instances = paramInstances(canApprox)(Array .fill(caseLambda.paramNames.length)(NoType ), pat)
3519
3513
instantiateParams(instances)(body) match
@@ -3524,13 +3518,16 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
3524
3518
}
3525
3519
}
3526
3520
case redux =>
3527
- MatchResult .Reduced (redux)
3521
+ if disjoint then
3522
+ MatchResult .ReducedAndDisjoint
3523
+ else
3524
+ MatchResult .Reduced (redux)
3528
3525
3529
3526
if matches(canWidenAbstract = false ) then
3530
3527
redux(canApprox = true )
3531
3528
else if matches(canWidenAbstract = true ) then
3532
3529
redux(canApprox = false )
3533
- else if (provablyDisjoint(scrut, pat) )
3530
+ else if (disjoint )
3534
3531
// We found a proof that `scrut` and `pat` are incompatible.
3535
3532
// The search continues.
3536
3533
MatchResult .Disjoint
@@ -3557,28 +3554,22 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
3557
3554
NoType
3558
3555
case MatchResult .Reduced (tp) =>
3559
3556
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
3560
3567
case Nil =>
3561
3568
val casesText = MatchTypeTrace .noMatchesText(scrut, cases)
3562
3569
ErrorType (reporting.MatchTypeNoCases (casesText))
3563
3570
3564
3571
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
3582
3573
// if the scrutinee is an error type
3583
3574
// then just return that as the result
3584
3575
// not doing so will result in the first type case matching
0 commit comments