@@ -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,19 +2137,52 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
21362137 /** Returns last check's debug mode, if explicitly enabled. */
21372138 def lastTrace (): String = " "
21382139
2139- /** Are `tp1` and `tp2` disjoint types?
2140+ private def typeparamCorrespondsToField (tycon : Type , tparam : TypeParamInfo ): Boolean =
2141+ productSelectorTypes(tycon, null ).exists {
2142+ case tp : TypeRef =>
2143+ tp.designator.eq(tparam) // Bingo!
2144+ case _ =>
2145+ false
2146+ }
2147+
2148+ /** Is `tp` an empty type?
21402149 *
2141- * `true` implies that we found a proof; uncertainty default to `false`.
2150+ * `true` implies that we found a proof; uncertainty defaults 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) => provablyDisjoint(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+
2169+ /** Are `tp1` and `tp2` provablyDisjoint types?
2170+ *
2171+ * `true` implies that we found a proof; uncertainty defaults to `false`.
21422172 *
21432173 * Proofs rely on the following properties of Scala types:
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, the same match type expression
2182+ * is either stuck or reduces to the same case.
21492183 */
2150- def disjoint (tp1 : Type , tp2 : Type )(implicit ctx : Context ): Boolean = {
2151- // println(s"disjoint (${tp1.show}, ${tp2.show})")
2184+ def provablyDisjoint (tp1 : Type , tp2 : Type )(implicit ctx : Context ): Boolean = {
2185+ // println(s"provablyDisjoint (${tp1.show}, ${tp2.show})")
21522186 /** Can we enumerate all instantiations of this type? */
21532187 def isClosedSum (tp : Symbol ): Boolean =
21542188 tp.is(Sealed ) && tp.isOneOf(AbstractOrTrait ) && ! tp.hasAnonymousChild
@@ -2181,33 +2215,19 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
21812215 // of classes.
21822216 true
21832217 else if (isClosedSum(cls1))
2184- decompose(cls1, tp1).forall(x => disjoint (x, tp2))
2218+ decompose(cls1, tp1).forall(x => provablyDisjoint (x, tp2))
21852219 else if (isClosedSum(cls2))
2186- decompose(cls2, tp2).forall(x => disjoint (x, tp1))
2220+ decompose(cls2, tp2).forall(x => provablyDisjoint (x, tp1))
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 disjoint 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+ provablyDisjoint(tp1, tp2) && typeparamCorrespondsToField(tycon1, tparam)
22112231
22122232 args1.lazyZip(args2).lazyZip(tycon1.typeParams).exists {
22132233 (arg1, arg2, tparam) =>
@@ -2236,29 +2256,29 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
22362256 }
22372257 }
22382258 case (tp1 : HKLambda , tp2 : HKLambda ) =>
2239- disjoint (tp1.resType, tp2.resType)
2259+ provablyDisjoint (tp1.resType, tp2.resType)
22402260 case (_ : HKLambda , _) =>
2241- // The intersection of these two types would be ill kinded, they are therefore disjoint .
2261+ // The intersection of these two types would be ill kinded, they are therefore provablyDisjoint .
22422262 true
22432263 case (_, _ : HKLambda ) =>
22442264 true
22452265 case (tp1 : OrType , _) =>
2246- disjoint (tp1.tp1, tp2) && disjoint (tp1.tp2, tp2)
2266+ provablyDisjoint (tp1.tp1, tp2) && provablyDisjoint (tp1.tp2, tp2)
22472267 case (_, tp2 : OrType ) =>
2248- disjoint (tp1, tp2.tp1) && disjoint (tp1, tp2.tp2)
2268+ provablyDisjoint (tp1, tp2.tp1) && provablyDisjoint (tp1, tp2.tp2)
22492269 case (tp1 : AndType , tp2 : AndType ) =>
2250- (disjoint (tp1.tp1, tp2.tp1) || disjoint (tp1.tp2, tp2.tp2)) &&
2251- (disjoint (tp1.tp1, tp2.tp2) || disjoint (tp1.tp2, tp2.tp1))
2270+ (provablyDisjoint (tp1.tp1, tp2.tp1) || provablyDisjoint (tp1.tp2, tp2.tp2)) &&
2271+ (provablyDisjoint (tp1.tp1, tp2.tp2) || provablyDisjoint (tp1.tp2, tp2.tp1))
22522272 case (tp1 : AndType , _) =>
2253- disjoint (tp1.tp2, tp2) || disjoint (tp1.tp1, tp2)
2273+ provablyDisjoint (tp1.tp2, tp2) || provablyDisjoint (tp1.tp1, tp2)
22542274 case (_, tp2 : AndType ) =>
2255- disjoint (tp1, tp2.tp2) || disjoint (tp1, tp2.tp1)
2275+ provablyDisjoint (tp1, tp2.tp2) || provablyDisjoint (tp1, tp2.tp1)
22562276 case (tp1 : TypeProxy , tp2 : TypeProxy ) =>
2257- disjoint (tp1.underlying, tp2) || disjoint (tp1, tp2.underlying)
2277+ provablyDisjoint (tp1.underlying, tp2) || provablyDisjoint (tp1, tp2.underlying)
22582278 case (tp1 : TypeProxy , _) =>
2259- disjoint (tp1.underlying, tp2)
2279+ provablyDisjoint (tp1.underlying, tp2)
22602280 case (_, tp2 : TypeProxy ) =>
2261- disjoint (tp1, tp2.underlying)
2281+ provablyDisjoint (tp1, tp2.underlying)
22622282 case _ =>
22632283 false
22642284 }
@@ -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 {
@@ -2432,8 +2453,8 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
24322453 }
24332454 else if (isSubType(widenAbstractTypes(scrut), widenAbstractTypes(pat)))
24342455 Some (NoType )
2435- else if (disjoint (scrut, pat))
2436- // We found a proof that `scrut` and `pat` are incompatible.
2456+ else if (provablyDisjoint (scrut, pat))
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 `provablyDisjoint`.
2483+ if (provablyEmpty(scrut))
2484+ NoType
2485+ else
2486+ recur(cases)
2487+ }
24492488 }
24502489}
24512490
0 commit comments