@@ -16,6 +16,7 @@ import transform.TypeUtils._
1616import transform .SymUtils ._
1717import scala .util .control .NonFatal
1818import typer .ProtoTypes .constrained
19+ import typer .Applications .productSelectorTypes
1920import reporting .trace
2021
2122final class AbsentContext
@@ -2136,6 +2137,35 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
21362137 /** Returns last check's debug mode, if explicitly enabled. */
21372138 def lastTrace (): String = " "
21382139
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+
21392169 /** Are `tp1` and `tp2` disjoint types?
21402170 *
21412171 * `true` implies that we found a proof; uncertainty default to `false`.
@@ -2144,8 +2174,12 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
21442174 *
21452175 * 1. Single inheritance of classes
21462176 * 2. Final classes cannot be extended
2147- * 3. ConstantTypes with distinc values are non intersecting
2177+ * 3. ConstantTypes with distinct values are non intersecting
21482178 * 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.
21492183 */
21502184 def disjoint (tp1 : Type , tp2 : Type )(implicit ctx : Context ): Boolean = {
21512185 // println(s"disjoint(${tp1.show}, ${tp2.show})")
@@ -2187,27 +2221,13 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
21872221 else
21882222 false
21892223 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).
21902229 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)
22112231
22122232 args1.lazyZip(args2).lazyZip(tycon1.typeParams).exists {
22132233 (arg1, arg2, tparam) =>
@@ -2419,6 +2439,7 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
24192439 }.apply(tp)
24202440
24212441 val defn .MatchCase (pat, body) = cas1
2442+
24222443 if (isSubType(scrut, pat))
24232444 // `scrut` is a subtype of `pat`: *It's a Match!*
24242445 Some {
@@ -2433,7 +2454,7 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
24332454 else if (isSubType(widenAbstractTypes(scrut), widenAbstractTypes(pat)))
24342455 Some (NoType )
24352456 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.
24372458 // The search continues.
24382459 None
24392460 else
@@ -2445,7 +2466,25 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
24452466 case Nil => NoType
24462467 }
24472468
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+ }
24492488 }
24502489}
24512490
0 commit comments