diff --git a/compiler/src/dotty/tools/dotc/core/NamerOps.scala b/compiler/src/dotty/tools/dotc/core/NamerOps.scala index b29c5633fb8f..b44942d4e7ff 100644 --- a/compiler/src/dotty/tools/dotc/core/NamerOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NamerOps.scala @@ -14,6 +14,25 @@ import util.Spans.Span /** 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 @@ -164,8 +183,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 a7b02a287f6d..ad9d485b5ee5 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -148,7 +148,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 @@ -168,6 +169,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) { @@ -668,7 +671,7 @@ class TreeUnpickler(reader: TastyReader, val annotOwner = if sym.owner.isClass then newLocalDummy(sym.owner) else sym.owner var annots = annotFns.map(_(annotOwner)) - if annots.exists(_.symbol == defn.SilentIntoAnnot) then + if annots.exists(_.hasSymbol(defn.SilentIntoAnnot)) then // Temporary measure until we can change TastyFormat to include an INTO tag sym.setFlag(Into) annots = annots.filterNot(_.symbol == defn.SilentIntoAnnot) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index e4d237072041..c86705bbff06 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1141,7 +1141,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 given completerCtx: Context = localContext(cls) @@ -1764,6 +1765,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() +