Skip to content

Commit ba624b5

Browse files
committed
Complete owners of type parameters before parameters themselves
Fixes #10967 I minimized #10967 further to: ```scala trait SomeTrait trait CollBase[CC1[A2 <: SomeTrait]] { val companion: CollCompanion[CC1] } trait CollCompanion[CC2[A <: SomeTrait]] ``` What happened was: 1. the compiler tries to typecheck `companion` 2. this means needs to check `CollCompanion[CC1]` 3. this means it needs to complete the type parameter `CC2` of CollCompanion in order to make sure the argument is CC1 is kind correct. 4. this means we complete type parameter `A` of `CC2` 4. checking the type parameter requires checking the owner class `CollCompanion`. 5. And this leads back to `CC2`, hence the cycle. Note that if the definitions of `CollBase` and `CollCompanion` appear in reverse order, no cycle results, since then `CollCompanion` and its type parameters were completed before completing `companion`. To break the cycle, we can complete the owner of a type parameter before starting completion of the parameter. Furthermore, we need to do this only in Namer, which is where the complex checking patterns are. I have not tried to also treat symbols that are not type definitions that way. It's not necessary to fix #10967 but it might help for other cycles (or it might cause them, these things are tricky!). I did try to make the behavior universal for all completers, not just Namers, but that caused crashes immediately.
1 parent 141bf9e commit ba624b5

File tree

4 files changed

+60
-25
lines changed

4 files changed

+60
-25
lines changed

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -141,30 +141,31 @@ object SymDenotations {
141141
}
142142

143143
final def completeFrom(completer: LazyType)(using Context): Unit =
144-
if (Config.showCompletions) {
145-
println(i"${" " * indent}completing ${if (isType) "type" else "val"} $name")
146-
indent += 1
147-
148-
if (myFlags.is(Touched)) throw CyclicReference(this)
149-
myFlags |= Touched
150-
151-
// completions.println(s"completing ${this.debugString}")
152-
try atPhase(validFor.firstPhaseId)(completer.complete(this))
153-
catch {
154-
case ex: CyclicReference =>
155-
println(s"error while completing ${this.debugString}")
156-
throw ex
144+
if completer.needsCompletion(this) then
145+
if (Config.showCompletions) {
146+
println(i"${" " * indent}completing ${if (isType) "type" else "val"} $name")
147+
indent += 1
148+
149+
if (myFlags.is(Touched)) throw CyclicReference(this)
150+
myFlags |= Touched
151+
152+
// completions.println(s"completing ${this.debugString}")
153+
try atPhase(validFor.firstPhaseId)(completer.complete(this))
154+
catch {
155+
case ex: CyclicReference =>
156+
println(s"error while completing ${this.debugString}")
157+
throw ex
158+
}
159+
finally {
160+
indent -= 1
161+
println(i"${" " * indent}completed $name in $owner")
162+
}
157163
}
158-
finally {
159-
indent -= 1
160-
println(i"${" " * indent}completed $name in $owner")
164+
else {
165+
if (myFlags.is(Touched)) throw CyclicReference(this)
166+
myFlags |= Touched
167+
atPhase(validFor.firstPhaseId)(completer.complete(this))
161168
}
162-
}
163-
else {
164-
if (myFlags.is(Touched)) throw CyclicReference(this)
165-
myFlags |= Touched
166-
atPhase(validFor.firstPhaseId)(completer.complete(this))
167-
}
168169

169170
protected[dotc] def info_=(tp: Type): Unit = {
170171
/* // DEBUG
@@ -2517,6 +2518,13 @@ object SymDenotations {
25172518
def withModuleClass(moduleClassFn: Context ?=> Symbol): this.type = { myModuleClassFn = moduleClassFn; this }
25182519

25192520
override def toString: String = getClass.toString
2521+
2522+
/** A hook that is called before trying to complete a symbol with its
2523+
* associated cycle detection via the Touched flag. This is overridden
2524+
* for Type definitions in Namer, where we make sure that owners are
2525+
* completed before nested types.
2526+
*/
2527+
def needsCompletion(symd: SymDenotation)(using Context): Boolean = true
25202528
}
25212529

25222530
object LazyType:

compiler/src/dotty/tools/dotc/core/TypeApplications.scala

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,13 @@ class TypeApplications(val self: Type) extends AnyVal {
211211

212212
/** If `self` is a generic class, its type parameter symbols, otherwise Nil */
213213
final def typeParamSymbols(using Context): List[TypeSymbol] = typeParams match {
214-
case (_: Symbol) :: _ =>
215-
assert(typeParams.forall(_.isInstanceOf[Symbol]))
216-
typeParams.asInstanceOf[List[TypeSymbol]]
214+
case tparams @ (_: Symbol) :: _ =>
215+
assert(tparams.forall(_.isInstanceOf[Symbol]))
216+
tparams.asInstanceOf[List[TypeSymbol]]
217+
// Note: Two successive calls to typeParams can yield different results here because
218+
// of different completion status. I.e. the first call might produce some symbols,
219+
// whereas the second call gives some LambdaParams. This was observed
220+
// for ticket0137.scala
217221
case _ => Nil
218222
}
219223

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,20 @@ class Namer { typer: Typer =>
841841
private var nestedCtx: Context = null
842842
assert(!original.isClassDef)
843843

844+
/** If completion of the owner of the to be completed symbol has not yet started,
845+
* complete the owner first and check again. This prevents cyclic references
846+
* where we need to copmplete a type parameter that has an owner that is not
847+
* yet completed. Test case is pos/i10967.scala.
848+
*/
849+
override def needsCompletion(symd: SymDenotation)(using Context): Boolean =
850+
val owner = symd.owner
851+
!owner.exists
852+
|| owner.is(Touched)
853+
|| {
854+
owner.ensureCompleted()
855+
!symd.isCompleted
856+
}
857+
844858
override def completerTypeParams(sym: Symbol)(using Context): List[TypeSymbol] =
845859
if myTypeParams == null then
846860
//println(i"completing type params of $sym in ${sym.owner}")

tests/pos/i10967.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
trait SomeTrait
2+
3+
trait CollBase[A <: SomeTrait, +CC1[A2 <: SomeTrait]] {
4+
val companion: CollCompanion[CC1]
5+
}
6+
7+
trait Coll[A <: SomeTrait] extends CollBase[A, Coll]
8+
9+
trait CollCompanion[+CC2[A <: SomeTrait]]

0 commit comments

Comments
 (0)