@@ -16,6 +16,7 @@ import transform.TypeUtils._
16
16
import transform .SymUtils ._
17
17
import scala .util .control .NonFatal
18
18
import typer .ProtoTypes .constrained
19
+ import typer .Applications .productSelectorTypes
19
20
import reporting .trace
20
21
21
22
final class AbsentContext
@@ -2136,6 +2137,32 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
2136
2137
/** Returns last check's debug mode, if explicitly enabled. */
2137
2138
def lastTrace (): String = " "
2138
2139
2140
+ /** Is `tp` an empty type?
2141
+ *
2142
+ * `true` implies that we found a proof; uncertainty default to `false`.
2143
+ */
2144
+ def provablyEmpty (tp : Type ): Boolean =
2145
+ tp.dealias match {
2146
+ case tp if tp.isBottomType => true
2147
+ case AndType (tp1, tp2) => disjoint(tp1, tp2)
2148
+ case OrType (tp1, tp2) => provablyEmpty(tp1) && provablyEmpty(tp2)
2149
+ case at @ AppliedType (tycon, args) =>
2150
+ args.lazyZip(tycon.typeParams).exists { (arg, tparam) =>
2151
+ tparam.paramVariance >= 0 &&
2152
+ provablyEmpty(arg) &&
2153
+ productSelectorTypes(tycon, null ).exists {
2154
+ case tp : TypeRef =>
2155
+ (tp.designator: Any ) == tparam
2156
+ case _ =>
2157
+ false
2158
+ }
2159
+ }
2160
+ case tp : TypeProxy =>
2161
+ provablyEmpty(tp.underlying)
2162
+ case _ => false
2163
+ }
2164
+
2165
+
2139
2166
/** Are `tp1` and `tp2` disjoint types?
2140
2167
*
2141
2168
* `true` implies that we found a proof; uncertainty default to `false`.
@@ -2144,8 +2171,12 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
2144
2171
*
2145
2172
* 1. Single inheritance of classes
2146
2173
* 2. Final classes cannot be extended
2147
- * 3. ConstantTypes with distinc values are non intersecting
2174
+ * 3. ConstantTypes with distinct values are non intersecting
2148
2175
* 4. There is no value of type Nothing
2176
+ *
2177
+ * Note on soundness: the correctness of match types relies on on the
2178
+ * property that in all possible contexts, a same match type expression
2179
+ * is either stuck or reduces to the same case.
2149
2180
*/
2150
2181
def disjoint (tp1 : Type , tp2 : Type )(implicit ctx : Context ): Boolean = {
2151
2182
// println(s"disjoint(${tp1.show}, ${tp2.show})")
@@ -2187,26 +2218,17 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
2187
2218
else
2188
2219
false
2189
2220
case (AppliedType (tycon1, args1), AppliedType (tycon2, args2)) if tycon1 == tycon2 =>
2221
+ // It is possible to conclude that two types appiles are disjoints by
2222
+ // looking at covariant type parameters if The said type parameters
2223
+ // are disjoin and correspond to fields.
2224
+ // (Type parameter disjointness is not enought by itself as it could
2225
+ // lead to incorrect conclusions for phantom type parameters).
2190
2226
def covariantDisjoint (tp1 : Type , tp2 : Type , tparam : TypeParamInfo ): Boolean =
2191
- disjoint(tp1, tp2) && {
2192
- // We still need to proof that `Nothing` is not a valid
2193
- // instantiation of this type parameter. We have two ways
2194
- // to get to that conclusion:
2195
- // 1. `Nothing` does not conform to the type parameter's lb
2196
- // 2. `tycon1` has a field typed with this type parameter.
2197
- //
2198
- // Because of separate compilation, the use of 2. is
2199
- // limited to case classes.
2200
- import dotty .tools .dotc .typer .Applications .productSelectorTypes
2201
- val lowerBoundedByNothing = tparam.paramInfo.bounds.lo eq NothingType
2202
- val typeUsedAsField =
2203
- productSelectorTypes(tycon1, null ).exists {
2204
- case tp : TypeRef =>
2205
- (tp.designator: Any ) == tparam // Bingo!
2206
- case _ =>
2207
- false
2208
- }
2209
- ! lowerBoundedByNothing || typeUsedAsField
2227
+ disjoint(tp1, tp2) && productSelectorTypes(tycon1, null ).exists {
2228
+ case tp : TypeRef =>
2229
+ (tp.designator: Any ) == tparam // Bingo!
2230
+ case _ =>
2231
+ false
2210
2232
}
2211
2233
2212
2234
args1.lazyZip(args2).lazyZip(tycon1.typeParams).exists {
@@ -2419,6 +2441,7 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
2419
2441
}.apply(tp)
2420
2442
2421
2443
val defn .MatchCase (pat, body) = cas1
2444
+
2422
2445
if (isSubType(scrut, pat))
2423
2446
// `scrut` is a subtype of `pat`: *It's a Match!*
2424
2447
Some {
@@ -2433,7 +2456,7 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
2433
2456
else if (isSubType(widenAbstractTypes(scrut), widenAbstractTypes(pat)))
2434
2457
Some (NoType )
2435
2458
else if (disjoint(scrut, pat))
2436
- // We found a proof that `scrut` and `pat` are incompatible.
2459
+ // We found a proof that `scrut` and `pat` are incompatible.
2437
2460
// The search continues.
2438
2461
None
2439
2462
else
@@ -2445,7 +2468,25 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
2445
2468
case Nil => NoType
2446
2469
}
2447
2470
2448
- inFrozenConstraint(recur(cases))
2471
+ inFrozenConstraint {
2472
+ // Empty types break the basic assumption that if a scrutinee and a
2473
+ // pattern are disjoint it's OK to reduce passed that pattern. Indeed,
2474
+ // empty types viewed as a set of value is always a subset of any other
2475
+ // types. As a result, we first check that the scrutinee isn't empty
2476
+ // before proceeding with reduction. See `tests/neg/6570.scala` and
2477
+ // `6570-1.scala` for examples that exploit emptiness to break match
2478
+ // type soundness.
2479
+
2480
+ // If we revered the uncertainty case of this empty check, that is,
2481
+ // `!provablyNonEmpty` instead of `provablyEmpty`, that would be
2482
+ // obviously sound, but quite restrictive. With the current formulation,
2483
+ // we need to be careful that `provablyEmpty` covers all the conditions
2484
+ // used to conclude disjointness in `disjoint`.
2485
+ if (provablyEmpty(scrut))
2486
+ NoType
2487
+ else
2488
+ recur(cases)
2489
+ }
2449
2490
}
2450
2491
}
2451
2492
0 commit comments