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