Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add error-checking when fetching rhs of trees from TASTy #22565

Merged
merged 1 commit into from
Mar 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 37 additions & 14 deletions compiler/src/dotty/tools/dotc/transform/init/Semantic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ object Semantic:
// ----- Checker State -----------------------------------

/** The state that threads through the interpreter */
type Contextual[T] = (Context, Trace, Promoted, Cache.Data, Reporter) ?=> T
type Contextual[T] = (Context, Trace, Promoted, Cache.Data, Reporter, TreeCache.CacheData) ?=> T

// ----- Error Handling -----------------------------------

Expand Down Expand Up @@ -443,6 +443,29 @@ object Semantic:

inline def reporter(using r: Reporter): Reporter = r

// ----- Cache for Trees -----------------------------

object TreeCache:
class CacheData:
private val emptyTrees = mutable.Set[ValOrDefDef]()

extension (tree: ValOrDefDef)
def getRhs(using Context): Tree =
def getTree: Tree =
val errorCount = ctx.reporter.errorCount
val rhs = tree.rhs

if (ctx.reporter.errorCount > errorCount)
emptyTrees.add(tree)
report.warning("Ignoring analyses of " + tree.name + " due to error in reading TASTy.")
EmptyTree
else
rhs

if (emptyTrees.contains(tree)) EmptyTree
else getTree
end TreeCache

// ----- Operations on domains -----------------------------
extension (a: Value)
def join(b: Value): Value =
Expand Down Expand Up @@ -576,7 +599,7 @@ object Semantic:
case ref: Ref =>
val target = if needResolve then resolve(ref.klass, field) else field
if target.is(Flags.Lazy) then
val rhs = target.defTree.asInstanceOf[ValDef].rhs
val rhs = target.defTree.asInstanceOf[ValDef].getRhs
eval(rhs, ref, target.owner.asClass, cacheResult = true)
else if target.exists then
val obj = ref.objekt
Expand All @@ -591,7 +614,7 @@ object Semantic:
// return `Hot` here, errors are reported in checking `ThisRef`
Hot
else if target.hasSource then
val rhs = target.defTree.asInstanceOf[ValOrDefDef].rhs
val rhs = target.defTree.asInstanceOf[ValOrDefDef].getRhs
eval(rhs, ref, target.owner.asClass, cacheResult = true)
else
val error = CallUnknown(field)(trace)
Expand Down Expand Up @@ -715,7 +738,7 @@ object Semantic:
else
reporter.reportAll(tryReporter.errors)
extendTrace(ddef) {
eval(ddef.rhs, ref, cls, cacheResult = true)
eval(ddef.getRhs, ref, cls, cacheResult = true)
}
else if ref.canIgnoreMethodCall(target) then
Hot
Expand Down Expand Up @@ -777,7 +800,7 @@ object Semantic:
val tpl = cls.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template]
extendTrace(cls.defTree) { init(tpl, ref, cls) }
else
val initCall = ddef.rhs match
val initCall = ddef.getRhs match
case Block(call :: _, _) => call
case call => call
extendTrace(ddef) { eval(initCall, ref, cls) }
Expand All @@ -796,7 +819,7 @@ object Semantic:
extendTrace(cls.defTree) { eval(tpl, ref, cls, cacheResult = true) }
ref
else
extendTrace(ddef) { eval(ddef.rhs, ref, cls, cacheResult = true) }
extendTrace(ddef) { eval(ddef.getRhs, ref, cls, cacheResult = true) }
else if ref.canIgnoreMethodCall(ctor) then
Hot
else
Expand Down Expand Up @@ -906,8 +929,7 @@ object Semantic:

case Cold => Cold

case ref: Ref => eval(vdef.rhs, ref, enclosingClass, cacheResult = sym.is(Flags.Lazy))

case ref: Ref => eval(vdef.getRhs, ref, enclosingClass, cacheResult = sym.is(Flags.Lazy))
case _ =>
report.warning("[Internal error] unexpected this value when accessing local variable, sym = " + sym.show + ", thisValue = " + thisValue2.show + Trace.show, Trace.position)
Hot
Expand Down Expand Up @@ -1114,7 +1136,7 @@ object Semantic:
*
* The class to be checked must be an instantiable concrete class.
*/
private def checkClass(classSym: ClassSymbol)(using Cache.Data, Context): Unit =
private def checkClass(classSym: ClassSymbol)(using Cache.Data, Context, TreeCache.CacheData): Unit =
val thisRef = ThisRef(classSym)
val tpl = classSym.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template]

Expand Down Expand Up @@ -1149,6 +1171,7 @@ object Semantic:
*/
def checkClasses(classes: List[ClassSymbol])(using Context): Unit =
given Cache.Data()
given TreeCache.CacheData()
for classSym <- classes if isConcreteClass(classSym) && !classSym.isStaticObject do
checkClass(classSym)

Expand Down Expand Up @@ -1320,10 +1343,10 @@ object Semantic:
}

case closureDef(ddef) =>
Fun(ddef.rhs, thisV, klass)
Fun(ddef.getRhs, thisV, klass)

case PolyFun(ddef) =>
Fun(ddef.rhs, thisV, klass)
Fun(ddef.getRhs, thisV, klass)

case Block(stats, expr) =>
eval(stats, thisV, klass)
Expand Down Expand Up @@ -1375,7 +1398,7 @@ object Semantic:

case vdef : ValDef =>
// local val definition
eval(vdef.rhs, thisV, klass)
eval(vdef.getRhs, thisV, klass)

case ddef : DefDef =>
// local method
Expand Down Expand Up @@ -1593,8 +1616,8 @@ object Semantic:

// class body
if thisV.isThisRef || !thisV.asInstanceOf[Warm].isPopulatingParams then tpl.body.foreach {
case vdef : ValDef if !vdef.symbol.is(Flags.Lazy) && !vdef.rhs.isEmpty =>
val res = eval(vdef.rhs, thisV, klass)
case vdef : ValDef if !vdef.symbol.is(Flags.Lazy) && !vdef.getRhs.isEmpty =>
val res = eval(vdef.getRhs, thisV, klass)
// TODO: Improve promotion to avoid handling enum initialization specially
//
// The failing case is tests/init/pos/i12544.scala due to promotion failure.
Expand Down
23 changes: 23 additions & 0 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,29 @@ class CompilationTests {

tests.foreach(_.delete())
}

/* This tests for errors in the program's TASTy trees.
* The test consists of three files: (a) v1/A, (b) v1/B, and (c) v0/A. (a) and (b) are
* compatible, but (b) and (c) are not. If (b) and (c) are compiled together, there should be
* an error when reading the files' TASTy trees. */
locally {
val tastyErrorGroup = TestGroup("checkInit/tasty-error")
val tastyErrorOptions = options.without("-Xfatal-warnings")

val a0Dir = defaultOutputDir + tastyErrorGroup + "/A/v0/A"
val a1Dir = defaultOutputDir + tastyErrorGroup + "/A/v1/A"
val b1Dir = defaultOutputDir + tastyErrorGroup + "/B/v1/B"

val tests = List(
compileFile("tests/init/tasty-error/v1/A.scala", tastyErrorOptions)(tastyErrorGroup),
compileFile("tests/init/tasty-error/v1/B.scala", tastyErrorOptions.withClasspath(a1Dir))(tastyErrorGroup),
compileFile("tests/init/tasty-error/v0/A.scala", tastyErrorOptions)(tastyErrorGroup),
).map(_.keepOutput.checkCompile())

compileFile("tests/init/tasty-error/Main.scala", tastyErrorOptions.withClasspath(a0Dir).withClasspath(b1Dir))(tastyErrorGroup).checkExpectedErrors()

tests.foreach(_.delete())
}
}

// parallel backend tests
Expand Down
1 change: 1 addition & 0 deletions tests/init/tasty-error/Main.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
class Main extends B{} // anypos-error
3 changes: 3 additions & 0 deletions tests/init/tasty-error/v0/A.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class A {
def fail(a: Int, b: Int): Int = a
}
3 changes: 3 additions & 0 deletions tests/init/tasty-error/v1/A.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class A {
def fail(a: Int): Int = a
}
4 changes: 4 additions & 0 deletions tests/init/tasty-error/v1/B.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class B {
val a: A = new A
val x = a.fail(0)
}
Loading