diff --git a/compiler/src/dotty/tools/dotc/core/NamerOps.scala b/compiler/src/dotty/tools/dotc/core/NamerOps.scala index 82ebb03b9662..91a5dfde1c47 100644 --- a/compiler/src/dotty/tools/dotc/core/NamerOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NamerOps.scala @@ -9,6 +9,25 @@ import TypeApplications.EtaExpansion /** Operations that are shared between Namer and TreeUnpickler */ object NamerOps: + /** A completer supporting cleanup actions. + * 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 break the cycle by delaying adding constructor proxies to be a cleanuo + * action instead. + */ + trait CompleterWithCleanup extends LazyType: + private var cleanupActions: List[() => Unit] = Nil + def addCleanupAction(op: () => Unit): Unit = + cleanupActions = op :: cleanupActions + def cleanup(): Unit = + if cleanupActions.nonEmpty then + cleanupActions.reverse.foreach(_()) + cleanupActions = Nil + end CompleterWithCleanup + /** The type of the constructed instance is returned * * @param ctor the constructor @@ -118,8 +137,14 @@ object NamerOps: ApplyProxyCompleter(constr), cls.privateWithin, constr.coord) - for dcl <- cls.info.decls do + def doAdd() = for dcl <- cls.info.decls do if dcl.isConstructor then scope.enter(proxy(dcl)) + cls.infoOrCompleter match + case completer: CompleterWithCleanup if cls.is(Touched) => + // Taking the info would lead to a cyclic reference here - delay instead until cleanup of `cls` + completer.addCleanupAction(doAdd) + case _ => + doAdd() scope end addConstructorApplies diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 6cc1d3c33c17..4edbb278504e 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -127,7 +127,8 @@ class TreeUnpickler(reader: TastyReader, } } - class Completer(reader: TastyReader)(using @constructorOnly _ctx: Context) extends LazyType { + class Completer(reader: TastyReader)(using @constructorOnly _ctx: Context) + extends LazyType, CompleterWithCleanup { import reader.* val owner = ctx.owner val mode = ctx.mode @@ -147,6 +148,8 @@ class TreeUnpickler(reader: TastyReader, case ex: CyclicReference => throw ex case ex: AssertionError => fail(ex) case ex: Exception => fail(ex) + finally + cleanup() } class TreeReader(val reader: TastyReader) { diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 75f738abd00e..2269a07ec77c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1089,7 +1089,8 @@ class Namer { typer: Typer => end typeSig } - class ClassCompleter(cls: ClassSymbol, original: TypeDef)(ictx: Context) extends Completer(original)(ictx) { + class ClassCompleter(cls: ClassSymbol, original: TypeDef)(ictx: Context) + extends Completer(original)(ictx), CompleterWithCleanup { withDecls(newScope(using ictx)) protected implicit val completerCtx: Context = localContext(cls) @@ -1691,6 +1692,7 @@ class Namer { typer: Typer => processExports(using localCtx) defn.patchStdLibClass(cls) addConstructorProxies(cls) + cleanup() } } diff --git a/tests/pos/i22436/atest.scala b/tests/pos/i22436/atest.scala new file mode 100644 index 000000000000..7f77a5562385 --- /dev/null +++ b/tests/pos/i22436/atest.scala @@ -0,0 +1,3 @@ +object Case1 { + def myProps(transport: ProtocolTransport): Unit = ??? +} diff --git a/tests/pos/i22436/defs.scala b/tests/pos/i22436/defs.scala new file mode 100644 index 000000000000..c4bc3d74df25 --- /dev/null +++ b/tests/pos/i22436/defs.scala @@ -0,0 +1,7 @@ +object ProtocolTransport + +import ProtocolTransport.* + +@annotation.nowarn() +class ProtocolTransport() +