Skip to content

Commit

Permalink
Extend compiletime.testing.typechecks with certain transform phases
Browse files Browse the repository at this point in the history
  • Loading branch information
jchyb committed Nov 25, 2024
1 parent 64411b6 commit 08f407d
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 6 deletions.
8 changes: 8 additions & 0 deletions compiler/src/dotty/tools/dotc/CompilationUnit.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Phases.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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])
Expand Down
78 changes: 72 additions & 6 deletions compiler/src/dotty/tools/dotc/inlines/Inlines.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@ 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

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:
Expand Down Expand Up @@ -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, _))
Expand All @@ -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, _))
}
Expand Down
1 change: 1 addition & 0 deletions compiler/test/dotc/run-test-pickling.blacklist
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,5 @@ named-tuples-strawman-2.scala

# typecheckErrors method unpickling
typeCheckErrors.scala
i18150.scala

2 changes: 2 additions & 0 deletions tests/run/i18150.check
Original file line number Diff line number Diff line change
@@ -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))
31 changes: 31 additions & 0 deletions tests/run/i18150.scala
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit 08f407d

Please sign in to comment.