Skip to content

Commit 13fc1ff

Browse files
committed
Revise alias rules in type comparisons.
The fix solves two cases where we had a deep subtype before.
1 parent 7be7795 commit 13fc1ff

File tree

4 files changed

+36
-42
lines changed

4 files changed

+36
-42
lines changed

src/dotty/tools/dotc/core/TypeComparer.scala

Lines changed: 36 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -144,47 +144,41 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
144144
def compareNamed(tp1: Type, tp2: NamedType): Boolean = {
145145
implicit val ctx: Context = this.ctx
146146
tp2.info match {
147-
case info2: TypeAlias if tp2.prefix.isStable =>
148-
// If prefix is not stable (i.e. is not a path), then we have a true
149-
// projection `T # A` which is treated as the existential type
150-
// `ex(x: T)x.A`. We need to deal with the existential first before
151-
// following the alias. If we did follow the alias we could be
152-
// unsound as well as incomplete. An example of this was discovered in Iter2.scala.
153-
// It failed to validate the subtype test
154-
//
155-
// (([+X] -> Seq[X]) & C)[SA] <: C[SA]
156-
//
157-
// Both sides are projections of $Apply. The left $Apply does have an
158-
// aliased info, namely, Seq[SA]. But that is not a subtype of C[SA].
159-
// The problem is that, with the prefix not being a path, an aliased info
160-
// does not necessarily give all of the information of the original projection.
161-
// So we can't follow the alias without a backup strategy. If the alias
162-
// would appear on the right then I believe this can be turned into a case
163-
// of unsoundness.
164-
isSubType(tp1, info2.alias)
147+
case info2: TypeAlias => isSubType(tp1, info2.alias)
165148
case _ => tp1 match {
166149
case tp1: NamedType =>
167150
tp1.info match {
168-
case info1: TypeAlias if tp1.prefix.isStable =>
169-
isSubType(info1.alias, tp2)
151+
case info1: TypeAlias =>
152+
if (isSubType(info1.alias, tp2)) return true
153+
if (tp1.prefix.isStable) return false
154+
// If tp1.prefix is stable, the alias does contain all information about the original ref, so
155+
// there's no need to try something else. (This is important for performance).
156+
// To see why we cannot in general stop here, consider:
157+
//
158+
// trait C { type A }
159+
// trait D { type A = String }
160+
// (C & D)#A <: C#A
161+
//
162+
// Following the alias leads to the judgment `String <: C#A` which is false.
163+
// However the original judgment should be true.
170164
case _ =>
171-
val sym1 = tp1.symbol
172-
if ((sym1 ne NoSymbol) && (sym1 eq tp2.symbol))
173-
ctx.erasedTypes ||
174-
sym1.isStaticOwner ||
175-
isSubType(tp1.prefix, tp2.prefix) ||
176-
thirdTryNamed(tp1, tp2)
177-
else
178-
( (tp1.name eq tp2.name)
179-
&& isSubType(tp1.prefix, tp2.prefix)
180-
&& tp1.signature == tp2.signature
181-
&& !tp1.isInstanceOf[WithFixedSym]
182-
&& !tp2.isInstanceOf[WithFixedSym]
183-
) ||
184-
compareHK(tp1, tp2, inOrder = true) ||
185-
compareHK(tp2, tp1, inOrder = false) ||
186-
thirdTryNamed(tp1, tp2)
187165
}
166+
val sym1 = tp1.symbol
167+
if ((sym1 ne NoSymbol) && (sym1 eq tp2.symbol))
168+
ctx.erasedTypes ||
169+
sym1.isStaticOwner ||
170+
isSubType(tp1.prefix, tp2.prefix) ||
171+
thirdTryNamed(tp1, tp2)
172+
else
173+
( (tp1.name eq tp2.name)
174+
&& isSubType(tp1.prefix, tp2.prefix)
175+
&& tp1.signature == tp2.signature
176+
&& !tp1.isInstanceOf[WithFixedSym]
177+
&& !tp2.isInstanceOf[WithFixedSym]
178+
) ||
179+
compareHK(tp1, tp2, inOrder = true) ||
180+
compareHK(tp2, tp1, inOrder = false) ||
181+
thirdTryNamed(tp1, tp2)
188182
case _ =>
189183
compareHK(tp2, tp1, inOrder = false) ||
190184
secondTry(tp1, tp2)
@@ -258,11 +252,13 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
258252
private def secondTry(tp1: Type, tp2: Type): Boolean = tp1 match {
259253
case tp1: NamedType =>
260254
tp1.info match {
261-
case info1: TypeAlias => isSubType(info1.alias, tp2)
255+
case info1: TypeAlias =>
256+
if (isSubType(info1.alias, tp2)) return true
257+
if (tp1.prefix.isStable) return false
262258
case _ =>
263-
compareHK(tp1, tp2, inOrder = true) ||
264-
thirdTry(tp1, tp2)
265259
}
260+
compareHK(tp1, tp2, inOrder = true) ||
261+
thirdTry(tp1, tp2)
266262
case tp1: PolyParam =>
267263
def flagNothingBound = {
268264
if (!frozenConstraint && tp2.isRef(defn.NothingClass) && state.isGlobalCommittable) {
@@ -1106,7 +1102,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
11061102
NoType
11071103
}
11081104

1109-
/** Try to distribute `|` inside type, detect and handle conflicts.
1105+
/** Try to distribute `|` inside type, detect and handle conflicts
11101106
* Note that, unlike for `&`, a disjunction cannot be pushed into
11111107
* a refined or applied type. Example:
11121108
*

test/dotc/tests.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,6 @@ class tests extends CompilerTest {
100100

101101
@Test def pos_all = compileFiles(posDir) // twice omitted to make tests run faster
102102

103-
@Test def pos_sets = compileFile(posSpecialDir, "sets")(allowDeepSubtypes)
104-
@Test def pos_t2613 = compileFile(posSpecialDir, "t2613")(allowDeepSubtypes)
105103
@Test def pos_i871 = compileFile(posSpecialDir, "i871", scala2mode)
106104
@Test def pos_variancesConstr = compileFile(posSpecialDir, "variances-constr", scala2mode)
107105

File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)