@@ -2765,26 +2765,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
2765
2765
false
2766
2766
} || tycon.derivesFrom(defn.PairClass )
2767
2767
2768
- /** Is `tp` an empty type?
2769
- *
2770
- * `true` implies that we found a proof; uncertainty defaults to `false`.
2771
- */
2772
- def provablyEmpty (tp : Type ): Boolean =
2773
- tp.dealias match {
2774
- case tp if tp.isExactlyNothing => true
2775
- case AndType (tp1, tp2) => provablyDisjoint(tp1, tp2)
2776
- case OrType (tp1, tp2) => provablyEmpty(tp1) && provablyEmpty(tp2)
2777
- case at @ AppliedType (tycon, args) =>
2778
- args.lazyZip(tycon.typeParams).exists { (arg, tparam) =>
2779
- tparam.paramVarianceSign >= 0
2780
- && provablyEmpty(arg)
2781
- && typeparamCorrespondsToField(tycon, tparam)
2782
- }
2783
- case tp : TypeProxy =>
2784
- provablyEmpty(tp.underlying)
2785
- case _ => false
2786
- }
2787
-
2788
2768
/** Are `tp1` and `tp2` provablyDisjoint types?
2789
2769
*
2790
2770
* `true` implies that we found a proof; uncertainty defaults to `false`.
@@ -3228,14 +3208,16 @@ object TrackingTypeComparer:
3228
3208
enum MatchResult extends Showable :
3229
3209
case Reduced (tp : Type )
3230
3210
case Disjoint
3211
+ case ReducedAndDisjoint
3231
3212
case Stuck
3232
3213
case NoInstance (fails : List [(Name , TypeBounds )])
3233
3214
3234
3215
def toText (p : Printer ): Text = this match
3235
- case Reduced (tp) => " Reduced(" ~ p.toText(tp) ~ " )"
3236
- case Disjoint => " Disjoint"
3237
- case Stuck => " Stuck"
3238
- case NoInstance (fails) => " NoInstance(" ~ Text (fails.map(p.toText(_) ~ p.toText(_)), " , " ) ~ " )"
3216
+ case Reduced (tp) => " Reduced(" ~ p.toText(tp) ~ " )"
3217
+ case Disjoint => " Disjoint"
3218
+ case ReducedAndDisjoint => " ReducedAndDisjoint"
3219
+ case Stuck => " Stuck"
3220
+ case NoInstance (fails) => " NoInstance(" ~ Text (fails.map(p.toText(_) ~ p.toText(_)), " , " ) ~ " )"
3239
3221
3240
3222
class TrackingTypeComparer (initctx : Context ) extends TypeComparer (initctx) {
3241
3223
import TrackingTypeComparer .*
@@ -3330,9 +3312,13 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
3330
3312
}
3331
3313
3332
3314
def matchSubTypeTest (spec : MatchTypeCaseSpec .SubTypeTest ): MatchResult =
3315
+ val disjoint = provablyDisjoint(scrut, spec.pattern)
3333
3316
if necessarySubType(scrut, spec.pattern) then
3334
- MatchResult .Reduced (spec.body)
3335
- else if provablyDisjoint(scrut, spec.pattern) then
3317
+ if disjoint then
3318
+ MatchResult .ReducedAndDisjoint
3319
+ else
3320
+ MatchResult .Reduced (spec.body)
3321
+ else if disjoint then
3336
3322
MatchResult .Disjoint
3337
3323
else
3338
3324
MatchResult .Stuck
@@ -3466,9 +3452,12 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
3466
3452
// This might not be needed
3467
3453
val constrainedCaseLambda = constrained(spec.origMatchCase, ast.tpd.EmptyTree )._1.asInstanceOf [HKTypeLambda ]
3468
3454
3469
- def tryDisjoint : MatchResult =
3455
+ val disjoint =
3470
3456
val defn .MatchCase (origPattern, _) = constrainedCaseLambda.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, constrainedCaseLambda)(constrainedCaseLambda.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