diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index 0975c94e916a..0d755797d026 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -155,6 +155,14 @@ object CompilationUnit { unit1 } + /** Create a compilation unit corresponding to an in-memory String. + * Used for `compiletime.testing.typeChecks`. + */ + def apply(name: String, source: String)(using Context): CompilationUnit = { + val src = SourceFile.virtual(name = name, content = source, maybeIncomplete = false) + new CompilationUnit(src, null) + } + /** Create a compilation unit corresponding to `source`. * If `mustExist` is true, this will fail if `source` does not exist. */ diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 015cf6fc0f2c..e3351628e43e 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -222,6 +222,7 @@ object Phases { private var mySbtExtractDependenciesPhase: Phase = uninitialized private var mySbtExtractAPIPhase: Phase = uninitialized private var myPicklerPhase: Phase = uninitialized + private var mySetRootTreePhase: Phase = uninitialized private var myInliningPhase: Phase = uninitialized private var myStagingPhase: Phase = uninitialized private var mySplicingPhase: Phase = uninitialized @@ -249,6 +250,7 @@ object Phases { final def sbtExtractDependenciesPhase: Phase = mySbtExtractDependenciesPhase final def sbtExtractAPIPhase: Phase = mySbtExtractAPIPhase final def picklerPhase: Phase = myPicklerPhase + final def setRootTreePhase: Phase = mySetRootTreePhase final def inliningPhase: Phase = myInliningPhase final def stagingPhase: Phase = myStagingPhase final def splicingPhase: Phase = mySplicingPhase @@ -278,6 +280,7 @@ object Phases { myPostTyperPhase = phaseOfClass(classOf[PostTyper]) mySbtExtractDependenciesPhase = phaseOfClass(classOf[sbt.ExtractDependencies]) mySbtExtractAPIPhase = phaseOfClass(classOf[sbt.ExtractAPI]) + mySetRootTreePhase = phaseOfClass(classOf[SetRootTree]) myPicklerPhase = phaseOfClass(classOf[Pickler]) myInliningPhase = phaseOfClass(classOf[Inlining]) myStagingPhase = phaseOfClass(classOf[Staging]) diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index aeecd9c376e3..1eca206d1455 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -12,6 +12,9 @@ import SymDenotations.SymDenotation import config.Printers.inlining import ErrorReporting.errorTree import dotty.tools.dotc.util.{SourceFile, SourcePosition, SrcPos} +import dotty.tools.dotc.transform.* +import dotty.tools.dotc.transform.MegaPhase +import dotty.tools.dotc.transform.MegaPhase.MiniPhase import parsing.Parsers.Parser import transform.{PostTyper, Inlining, CrossVersionChecks} import staging.StagingLevel @@ -19,6 +22,7 @@ import staging.StagingLevel import collection.mutable import reporting.{NotConstant, trace} import util.Spans.Span +import dotty.tools.dotc.core.Periods.PhaseId /** Support for querying inlineable methods and for inlining calls to such methods */ object Inlines: @@ -345,10 +349,58 @@ object Inlines: // We should not be rewriting tested strings val noRewriteSettings = ctx.settings.rewrite.updateIn(ctx.settingsState.reinitializedCopy(), None) + class MegaPhaseWithCustomPhaseId(miniPhases: Array[MiniPhase], startId: PhaseId, endId: PhaseId) + extends MegaPhase(miniPhases) { + override def start: Int = startId + override def end: Int = endId + } + ConstFold(underlyingCodeArg).tpe.widenTermRefExpr match { case ConstantType(Constant(code: String)) => - val source2 = SourceFile.virtual("tasty-reflect", code) - inContext(ctx.fresh.setSettings(noRewriteSettings).setNewTyperState().setTyper(new Typer(ctx.nestingLevel + 1)).setSource(source2)) { + val unitName = "tasty-reflect" + val source2 = SourceFile.virtual(unitName, code) + // We need a dummy owner, as the actual one does not have a computed denotation yet, + // but might be inspected in a transform phase, leading to cyclic errors + val dummyOwner = newSymbol(ctx.owner, "$dummySymbol$".toTermName, Private, defn.AnyType, NoSymbol) + val newContext = + ctx.fresh + .setSettings(noRewriteSettings) + .setNewTyperState() + .setTyper(new Typer(ctx.nestingLevel + 1)) + .setSource(source2) + .withOwner(dummyOwner) + + inContext(newContext) { + // Let's reconstruct necessary transform MegaPhases, without anything + // that could cause problems here (like `CrossVersionChecks`). + // The individiual lists here should line up with Compiler.scala, i.e + // separate chunks there should also be kept separate here. + // For now we create a single MegaPhase, since there does not seem to + // be any important checks later (e.g. ForwardDepChecks could be applicable here, + // but the equivalent is also not run in the scala 2's `ctx.typechecks`, + // so let's leave it out for now). + val transformPhases: List[List[(Class[?], () => MiniPhase)]] = List( + List( + (classOf[InlineVals], () => new InlineVals), + (classOf[ElimRepeated], () => new ElimRepeated), + (classOf[RefChecks], () => new RefChecks), + ), + ) + + val mergedTransformPhases = + transformPhases.flatMap( (megaPhaseList: List[(Class[?], () => MiniPhase)]) => + val (newMegaPhasePhases, phaseIds) = + megaPhaseList + .flatMap { filteredPhase => + ctx.base.phases.find(phase => filteredPhase._1.isInstance(phase)).map { a => + (filteredPhase._2(), a.id) + } + } + .unzip + if newMegaPhasePhases.isEmpty then None + else Some(MegaPhaseWithCustomPhaseId(newMegaPhasePhases.toArray, phaseIds.head, phaseIds.last)) + ) + val tree2 = new Parser(source2).block() if ctx.reporter.allErrors.nonEmpty then ctx.reporter.allErrors.map((ErrorKind.Parser, _)) @@ -357,10 +409,24 @@ object Inlines: ctx.base.postTyperPhase match case postTyper: PostTyper if ctx.reporter.allErrors.isEmpty => val tree4 = atPhase(postTyper) { postTyper.newTransformer.transform(tree3) } - ctx.base.inliningPhase match - case inlining: Inlining if ctx.reporter.allErrors.isEmpty => - atPhase(inlining) { inlining.newTransformer.transform(tree4) } - case _ => + ctx.base.setRootTreePhase match + case setRootTree => + val tree5 = + val compilationUnit = CompilationUnit(unitName, code) + compilationUnit.tpdTree = tree4 + compilationUnit.untpdTree = tree2 + var units = List(compilationUnit) + atPhase(setRootTree)(setRootTree.runOn(units).head.tpdTree) + + ctx.base.inliningPhase match + case inlining: Inlining if ctx.reporter.allErrors.isEmpty => + val tree6 = atPhase(inlining) { inlining.newTransformer.transform(tree5) } + if mergedTransformPhases.nonEmpty then + var transformTree = tree6 + for (phase <- mergedTransformPhases if ctx.reporter.allErrors.isEmpty) { + transformTree = atPhase(phase.end + 1)(phase.transformUnit(transformTree)) + } + case _ => case _ => ctx.reporter.allErrors.map((ErrorKind.Typer, _)) } diff --git a/compiler/test/dotc/run-test-pickling.blacklist b/compiler/test/dotc/run-test-pickling.blacklist index 31304e061bc7..c880a4b78f23 100644 --- a/compiler/test/dotc/run-test-pickling.blacklist +++ b/compiler/test/dotc/run-test-pickling.blacklist @@ -48,4 +48,5 @@ named-tuples-strawman-2.scala # typecheckErrors method unpickling typeCheckErrors.scala +i18150.scala diff --git a/tests/run/i18150.check b/tests/run/i18150.check new file mode 100644 index 000000000000..ea0b653719cd --- /dev/null +++ b/tests/run/i18150.check @@ -0,0 +1,2 @@ +List(Error(illegal inheritance: self type Banana of class Banana does not conform to self type Apple +of parent trait RecursiveSelfTypeEntity,class Banana extends RecursiveSelfTypeEntity[Apple]:,6,Typer)) diff --git a/tests/run/i18150.scala b/tests/run/i18150.scala new file mode 100644 index 000000000000..2a94f7d29549 --- /dev/null +++ b/tests/run/i18150.scala @@ -0,0 +1,31 @@ +object Test: + def main(args: Array[String]): Unit = + val result = + scala.compiletime.testing.typeCheckErrors( + "trait RecursiveSelfTypeEntity[E <: RecursiveSelfTypeEntity[E]]: \n" + + " self: E => \n" + + " def create(): E \n" + + " def read(id: Long): Option[E] \n" + + " def update(f: E => E): E \n" + + " def delete(id: Long): Unit \n" + + "\n" + + "class Apple extends RecursiveSelfTypeEntity[Apple]: \n" + + " override def create(): Apple = ??? \n" + + " override def read(id: Long): Option[Apple] = ??? \n" + + " override def update(f: Apple => Apple): Apple = ??? \n" + + " override def delete(id: Long): Unit = ??? \n" + + " \n" + + "class Orange extends RecursiveSelfTypeEntity[Orange]: \n" + + " override def create(): Orange = ??? \n" + + " override def read(id: Long): Option[Orange] = ??? \n" + + " override def update(f: Orange => Orange): Orange = ??? \n" + + " override def delete(id: Long): Unit = ??? \n" + + " \n" + + "class Banana extends RecursiveSelfTypeEntity[Apple]: \n" + + " override def create(): Apple = ??? \n" + + " override def read(id: Long): Option[Apple] = ??? \n" + + " override def update(f: Apple => Apple): Apple = ??? \n" + + " override def delete(id: Long): Unit = ???\n" + ) + assert(!result.isEmpty, "Should fail type check, but it didn't.") + println(result)