Skip to content

Commit b876445

Browse files
committed
Fix #1401: Make sure all refs are forwarded
Faced with recursive dependencies through self types, we might have to apply `normalizeToClassRefs` to a class P with a parent that is not yet initialized (witnessed by P's parents being Nil). In that case we should still execute forwardRefs on P, but we have to wait in a suspension until P is initialized. This avoids the problem raised in #1401. I am still not quite sure why forwardRefs is needed, but it seems that asSeenFrom alone is not enough to track the dependencies in this case.
1 parent 435fca4 commit b876445

File tree

6 files changed

+59
-9
lines changed

6 files changed

+59
-9
lines changed

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

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -361,13 +361,23 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
361361
* to the current scope, provided (1) variances of both aliases are the same, and
362362
* (2) X is not yet defined in current scope. This "short-circuiting" prevents
363363
* long chains of aliases which would have to be traversed in type comparers.
364+
*
365+
* Note: Test i1401.scala shows that `forwardRefs` is also necessary
366+
* for typechecking in the case where self types refer to type parameters
367+
* that are upper-bounded by subclass instances.
364368
*/
365369
def forwardRefs(from: Symbol, to: Type, prefs: List[TypeRef]) = to match {
366370
case to @ TypeBounds(lo1, hi1) if lo1 eq hi1 =>
367-
for (pref <- prefs)
368-
for (argSym <- pref.decls)
369-
if (argSym is BaseTypeArg)
370-
forwardRef(argSym, from, to, cls, decls)
371+
for (pref <- prefs) {
372+
def forward(): Unit =
373+
for (argSym <- pref.decls)
374+
if (argSym is BaseTypeArg)
375+
forwardRef(argSym, from, to, cls, decls)
376+
pref.info match {
377+
case info: TempClassInfo => info.suspensions = (() => forward()) :: info.suspensions // !!! dotty deviation `forward` alone does not eta expand
378+
case _ => forward()
379+
}
380+
}
371381
case _ =>
372382
}
373383

@@ -419,9 +429,9 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
419429
s"redefinition of ${decls.lookup(name).debugString} in ${cls.showLocated}")
420430
enterArgBinding(formals(name), refinedInfo, cls, decls)
421431
}
422-
// Forward definitions in super classes that have one of the refined paramters
432+
// Forward definitions in super classes that have one of the refined parameters
423433
// as aliases directly to the refined info.
424-
// Note that this cannot be fused bwith the previous loop because we now
434+
// Note that this cannot be fused with the previous loop because we now
425435
// assume that all arguments have been entered in `decls`.
426436
refinements foreachBinding { (name, refinedInfo) =>
427437
forwardRefs(formals(name), refinedInfo, parentRefs)

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3043,9 +3043,20 @@ object Types {
30433043
override def toString = s"ClassInfo($prefix, $cls)"
30443044
}
30453045

3046-
final class CachedClassInfo(prefix: Type, cls: ClassSymbol, classParents: List[TypeRef], decls: Scope, selfInfo: DotClass)
3046+
class CachedClassInfo(prefix: Type, cls: ClassSymbol, classParents: List[TypeRef], decls: Scope, selfInfo: DotClass)
30473047
extends ClassInfo(prefix, cls, classParents, decls, selfInfo)
30483048

3049+
/** A class for temporary class infos where `parents` are not yet known. */
3050+
final class TempClassInfo(prefix: Type, cls: ClassSymbol, decls: Scope, selfInfo: DotClass)
3051+
extends CachedClassInfo(prefix, cls, Nil, decls, selfInfo) {
3052+
3053+
/** A list of actions that were because they rely on the class info of `cls` to
3054+
* be no longer temporary. These actions will be performed once `cls` gets a real
3055+
* ClassInfo.
3056+
*/
3057+
var suspensions: List[() => Unit] = Nil
3058+
}
3059+
30493060
object ClassInfo {
30503061
def apply(prefix: Type, cls: ClassSymbol, classParents: List[TypeRef], decls: Scope, selfInfo: DotClass = NoType)(implicit ctx: Context) =
30513062
unique(new CachedClassInfo(prefix, cls, classParents, decls, selfInfo))

src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ object Scala2Unpickler {
106106
// `denot.sourceModule.exists` provision i859.scala crashes in the backend.
107107
denot.owner.thisType select denot.sourceModule
108108
else selfInfo
109-
denot.info = ClassInfo(denot.owner.thisType, denot.classSymbol, Nil, decls, ost) // first rough info to avoid CyclicReferences
109+
denot.info = new TempClassInfo(denot.owner.thisType, denot.classSymbol, decls, ost) // first rough info to avoid CyclicReferences
110110
var parentRefs = ctx.normalizeToClassRefs(parents, cls, decls)
111111
if (parentRefs.isEmpty) parentRefs = defn.ObjectType :: Nil
112112
for (tparam <- tparams) {

src/dotty/tools/dotc/typer/Namer.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,8 @@ class Namer { typer: Typer =>
688688
else createSymbol(self)
689689

690690
// pre-set info, so that parent types can refer to type params
691-
denot.info = ClassInfo(cls.owner.thisType, cls, Nil, decls, selfInfo)
691+
val tempClassInfo = new TempClassInfo(cls.owner.thisType, cls, decls, selfInfo)
692+
denot.info = tempClassInfo
692693

693694
// Ensure constructor is completed so that any parameter accessors
694695
// which have type trees deriving from its parameters can be
@@ -705,6 +706,8 @@ class Namer { typer: Typer =>
705706

706707
index(rest)(inClassContext(selfInfo))
707708
denot.info = ClassInfo(cls.owner.thisType, cls, parentRefs, decls, selfInfo)
709+
tempClassInfo.suspensions.foreach(_())
710+
708711
Checking.checkWellFormed(cls)
709712
if (isDerivedValueClass(cls)) cls.setFlag(Final)
710713
cls.setApplicableFlags(

test/dotc/scala-collections.whitelist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
./scala-scala/src/library/scala/collection/immutable/Seq.scala
8484
./scala-scala/src/library/scala/collection/mutable/IndexedSeq.scala
8585
./scala-scala/src/library/scala/collection/mutable/ListBuffer.scala
86+
./scala-scala/src/library/scala/collection/mutable/BufferLike.scala
8687

8788
./scala-scala/src/library/scala/collection/mutable/ArrayBuilder.scala
8889

tests/pos/i1401.scala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package i1401
2+
3+
trait Subtractable[A, +Repr <: Subtractable[A, Repr]] {
4+
def -(elem: A): Repr
5+
}
6+
7+
trait BufferLike[BA, +This <: BufferLike[BA, This] with Buffer[BA]]
8+
extends Subtractable[BA, This]
9+
{ self : This =>
10+
11+
/* Without fix-#1401:
12+
*
13+
error: overriding method - in trait Subtractable of type (elem: A)This & i1401.Buffer[A];
14+
method - of type (elem: BA)This has incompatible type
15+
def -(elem: BA): This
16+
^
17+
one error found
18+
*/
19+
def -(elem: BA): This
20+
}
21+
22+
trait Buffer[A] extends BufferLike[A, Buffer[A]]
23+
24+
25+

0 commit comments

Comments
 (0)