diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 3ea5ef1b0bc8..b9dc7d69265e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -488,14 +488,22 @@ class Namer { typer: Typer => */ final def addChild(cls: Symbol, child: Symbol)(using Context): Unit = { val childStart = if (child.span.exists) child.span.start else -1 + // A Child annotation that is currently being forced cannot be safely + // inspected here (forcing it again would trigger an assertion in + // `LazyAnnotation.tree`). This can happen when adding a child causes + // completion of another class that itself needs to be added as a child of + // the same parent (see tests/pos-special/i24719). In that case, fall back + // to just prepending the new Child annotation. + def isReady(ann: Annotation): Boolean = + ann.symbol == defn.ChildAnnot && !ann.isEvaluating def insertInto(annots: List[Annotation]): List[Annotation] = - annots.find(_.symbol == defn.ChildAnnot) match { + annots.find(isReady) match { case Some(Annotation.Child(other)) if other.span.exists && childStart <= other.span.start => if (child == other) annots // can happen if a class has several inaccessible children else { assert(childStart != other.span.start || child.source != other.source, i"duplicate child annotation $child / $other") - val (prefix, otherAnnot :: rest) = annots.span(_.symbol != defn.ChildAnnot): @unchecked + val (prefix, otherAnnot :: rest) = annots.span(ann => !isReady(ann)): @unchecked prefix ::: otherAnnot :: insertInto(rest) } case _ => diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 8cb629e7aa06..da281168c1f4 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -35,6 +35,7 @@ class CompilationTests { compileFile("tests/pos-special/utf16encoded.scala", defaultOptions.and("-encoding", "UTF16")), compileDir("tests/pos-special/i18589", defaultOptions.and("-Wsafe-init").without("-Ycheck:all")), compileDir("tests/pos-special/i24547", defaultOptions.without("-Ycheck:all")), + compileDir("tests/pos-special/i24719", defaultOptions.without("-Ycheck:all")), // Run tests for legacy lazy vals compileFilesInDir("tests/pos", defaultOptions.and("-Wsafe-init", "-Ylegacy-lazy-vals", "-Ycheck-constraint-deps"), FileFilter.include(TestSources.posLazyValsAllowlist)), compileDir("tests/pos-special/java-param-names", defaultOptions.withJavacOnlyOptions("-parameters")), diff --git a/tests/pos-special/i24719/Tuple1.scala b/tests/pos-special/i24719/Tuple1.scala new file mode 100644 index 000000000000..22c4b8e866fa --- /dev/null +++ b/tests/pos-special/i24719/Tuple1.scala @@ -0,0 +1,2 @@ +package scala +final case class Tuple1[+T1](_1: T1) extends Product1[T1] diff --git a/tests/pos-special/i24719/Tuple22.scala b/tests/pos-special/i24719/Tuple22.scala new file mode 100644 index 000000000000..720739146e79 --- /dev/null +++ b/tests/pos-special/i24719/Tuple22.scala @@ -0,0 +1,5 @@ +package scala +final case class Tuple22[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11, +T12, +T13, +T14, +T15, +T16, +T17, +T18, +T19, +T20, +T21, +T22]( + _1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, + _12: T12, _13: T13, _14: T14, _15: T15, _16: T16, _17: T17, _18: T18, _19: T19, _20: T20, _21: T21, _22: T22) + extends Product22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22]