@@ -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,35 @@ 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
+ private def typeparamCorrespondsToField (tycon : Type , tparam : TypeParamInfo ): Boolean =
2141
+ productSelectorTypes(tycon, null ).exists {
2142
+ case tp : TypeRef =>
2143
+ (tp.designator: Any ) == tparam // Bingo!
2144
+ case _ =>
2145
+ false
2146
+ }
2147
+
2148
+ /** Is `tp` an empty type?
2149
+ *
2150
+ * `true` implies that we found a proof; uncertainty default to `false`.
2151
+ */
2152
+ def provablyEmpty (tp : Type ): Boolean =
2153
+ tp.dealias match {
2154
+ case tp if tp.isBottomType => true
2155
+ case AndType (tp1, tp2) => disjoint(tp1, tp2)
2156
+ case OrType (tp1, tp2) => provablyEmpty(tp1) && provablyEmpty(tp2)
2157
+ case at @ AppliedType (tycon, args) =>
2158
+ args.lazyZip(tycon.typeParams).exists { (arg, tparam) =>
2159
+ tparam.paramVariance >= 0 &&
2160
+ provablyEmpty(arg) &&
2161
+ typeparamCorrespondsToField(tycon, tparam)
2162
+ }
2163
+ case tp : TypeProxy =>
2164
+ provablyEmpty(tp.underlying)
2165
+ case _ => false
2166
+ }
2167
+
2168
+
2139
2169
/** Are `tp1` and `tp2` disjoint types?
2140
2170
*
2141
2171
* `true` implies that we found a proof; uncertainty default to `false`.
@@ -2144,8 +2174,12 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
2144
2174
*
2145
2175
* 1. Single inheritance of classes
2146
2176
* 2. Final classes cannot be extended
2147
- * 3. ConstantTypes with distinc values are non intersecting
2177
+ * 3. ConstantTypes with distinct values are non intersecting
2148
2178
* 4. There is no value of type Nothing
2179
+ *
2180
+ * Note on soundness: the correctness of match types relies on on the
2181
+ * property that in all possible contexts, a same match type expression
2182
+ * is either stuck or reduces to the same case.
2149
2183
*/
2150
2184
def disjoint (tp1 : Type , tp2 : Type )(implicit ctx : Context ): Boolean = {
2151
2185
// println(s"disjoint(${tp1.show}, ${tp2.show})")
@@ -2187,27 +2221,13 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
2187
2221
else
2188
2222
false
2189
2223
case (AppliedType (tycon1, args1), AppliedType (tycon2, args2)) if tycon1 == tycon2 =>
2224
+ // It is possible to conclude that two types applies are disjoints by
2225
+ // looking at covariant type parameters if The said type parameters
2226
+ // are disjoin and correspond to fields.
2227
+ // (Type parameter disjointness is not enough by itself as it could
2228
+ // lead to incorrect conclusions for phantom type parameters).
2190
2229
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
2210
- }
2230
+ disjoint(tp1, tp2) && typeparamCorrespondsToField(tycon1, tparam)
2211
2231
2212
2232
args1.lazyZip(args2).lazyZip(tycon1.typeParams).exists {
2213
2233
(arg1, arg2, tparam) =>
@@ -2419,6 +2439,7 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
2419
2439
}.apply(tp)
2420
2440
2421
2441
val defn .MatchCase (pat, body) = cas1
2442
+
2422
2443
if (isSubType(scrut, pat))
2423
2444
// `scrut` is a subtype of `pat`: *It's a Match!*
2424
2445
Some {
@@ -2433,7 +2454,7 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
2433
2454
else if (isSubType(widenAbstractTypes(scrut), widenAbstractTypes(pat)))
2434
2455
Some (NoType )
2435
2456
else if (disjoint(scrut, pat))
2436
- // We found a proof that `scrut` and `pat` are incompatible.
2457
+ // We found a proof that `scrut` and `pat` are incompatible.
2437
2458
// The search continues.
2438
2459
None
2439
2460
else
@@ -2445,7 +2466,25 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
2445
2466
case Nil => NoType
2446
2467
}
2447
2468
2448
- inFrozenConstraint(recur(cases))
2469
+ inFrozenConstraint {
2470
+ // Empty types break the basic assumption that if a scrutinee and a
2471
+ // pattern are disjoint it's OK to reduce passed that pattern. Indeed,
2472
+ // empty types viewed as a set of value is always a subset of any other
2473
+ // types. As a result, we first check that the scrutinee isn't empty
2474
+ // before proceeding with reduction. See `tests/neg/6570.scala` and
2475
+ // `6570-1.scala` for examples that exploit emptiness to break match
2476
+ // type soundness.
2477
+
2478
+ // If we revered the uncertainty case of this empty check, that is,
2479
+ // `!provablyNonEmpty` instead of `provablyEmpty`, that would be
2480
+ // obviously sound, but quite restrictive. With the current formulation,
2481
+ // we need to be careful that `provablyEmpty` covers all the conditions
2482
+ // used to conclude disjointness in `disjoint`.
2483
+ if (provablyEmpty(scrut))
2484
+ NoType
2485
+ else
2486
+ recur(cases)
2487
+ }
2449
2488
}
2450
2489
}
2451
2490
0 commit comments