Skip to content

Commit 561a663

Browse files
authored
Support cleanup actions in class completers (#23515)
Needed to break the loop between completion of class and companion object. If we try to complete the class first, and completion needs the companion object (for instance for processing an import) then the companion object completion would consult the companion class info for constructor that need a constructor proxy in the object. This can lead to a cyclic reference. We now break the cycle by delaying adding constructor proxies in this case to be the last completion action of the companion class.
2 parents 1f057d2 + 9704b0c commit 561a663

File tree

5 files changed

+44
-4
lines changed

5 files changed

+44
-4
lines changed

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,25 @@ import util.Spans.Span
1414
/** Operations that are shared between Namer and TreeUnpickler */
1515
object NamerOps:
1616

17+
/** A completer supporting cleanup actions.
18+
* Needed to break the loop between completion of class and companion object.
19+
* If we try to complete the class first, and completion needs the companion
20+
* object (for instance for processing an import) then the companion object
21+
* completion would consult the companion class info for constructor that
22+
* need a constructor proxy in the object. This can lead to a cyclic reference.
23+
* We break the cycle by delaying adding constructor proxies to be a cleanuo
24+
* action instead.
25+
*/
26+
trait CompleterWithCleanup extends LazyType:
27+
private var cleanupActions: List[() => Unit] = Nil
28+
def addCleanupAction(op: () => Unit): Unit =
29+
cleanupActions = op :: cleanupActions
30+
def cleanup(): Unit =
31+
if cleanupActions.nonEmpty then
32+
cleanupActions.reverse.foreach(_())
33+
cleanupActions = Nil
34+
end CompleterWithCleanup
35+
1736
/** The type of the constructed instance is returned
1837
*
1938
* @param ctor the constructor
@@ -164,8 +183,14 @@ object NamerOps:
164183
ApplyProxyCompleter(constr),
165184
cls.privateWithin,
166185
constr.coord)
167-
for dcl <- cls.info.decls do
186+
def doAdd() = for dcl <- cls.info.decls do
168187
if dcl.isConstructor then scope.enter(proxy(dcl))
188+
cls.infoOrCompleter match
189+
case completer: CompleterWithCleanup if cls.is(Touched) =>
190+
// Taking the info would lead to a cyclic reference here - delay instead until cleanup of `cls`
191+
completer.addCleanupAction(doAdd)
192+
case _ =>
193+
doAdd()
169194
scope
170195
end addConstructorApplies
171196

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ class TreeUnpickler(reader: TastyReader,
148148
}
149149
}
150150

151-
class Completer(reader: TastyReader)(using @constructorOnly _ctx: Context) extends LazyType {
151+
class Completer(reader: TastyReader)(using @constructorOnly _ctx: Context)
152+
extends LazyType, CompleterWithCleanup {
152153
import reader.*
153154
val owner = ctx.owner
154155
val mode = ctx.mode
@@ -168,6 +169,8 @@ class TreeUnpickler(reader: TastyReader,
168169
case ex: CyclicReference => throw ex
169170
case ex: AssertionError => fail(ex)
170171
case ex: Exception => fail(ex)
172+
finally
173+
cleanup()
171174
}
172175

173176
class TreeReader(val reader: TastyReader) {
@@ -668,7 +671,7 @@ class TreeUnpickler(reader: TastyReader,
668671
val annotOwner =
669672
if sym.owner.isClass then newLocalDummy(sym.owner) else sym.owner
670673
var annots = annotFns.map(_(annotOwner))
671-
if annots.exists(_.symbol == defn.SilentIntoAnnot) then
674+
if annots.exists(_.hasSymbol(defn.SilentIntoAnnot)) then
672675
// Temporary measure until we can change TastyFormat to include an INTO tag
673676
sym.setFlag(Into)
674677
annots = annots.filterNot(_.symbol == defn.SilentIntoAnnot)

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1141,7 +1141,8 @@ class Namer { typer: Typer =>
11411141
end typeSig
11421142
}
11431143

1144-
class ClassCompleter(cls: ClassSymbol, original: TypeDef)(ictx: Context) extends Completer(original)(ictx) {
1144+
class ClassCompleter(cls: ClassSymbol, original: TypeDef)(ictx: Context)
1145+
extends Completer(original)(ictx), CompleterWithCleanup {
11451146
withDecls(newScope(using ictx))
11461147

11471148
protected given completerCtx: Context = localContext(cls)
@@ -1764,6 +1765,7 @@ class Namer { typer: Typer =>
17641765
processExports(using localCtx)
17651766
defn.patchStdLibClass(cls)
17661767
addConstructorProxies(cls)
1768+
cleanup()
17671769
}
17681770
}
17691771

tests/pos/i22436/atest.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object Case1 {
2+
def myProps(transport: ProtocolTransport): Unit = ???
3+
}

tests/pos/i22436/defs.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
object ProtocolTransport
2+
3+
import ProtocolTransport.*
4+
5+
@annotation.nowarn()
6+
class ProtocolTransport()
7+

0 commit comments

Comments
 (0)