From e80888176d5645b3c26075423e93454dc766b8e1 Mon Sep 17 00:00:00 2001
From: Daisy Li
Date: Sun, 9 Feb 2025 11:34:45 -0500
Subject: [PATCH] Add error-checking when fetching rhs of ValOrDefDefs.
---
.../tools/dotc/transform/init/Semantic.scala | 51 ++++++++++++++-----
.../dotty/tools/dotc/CompilationTests.scala | 23 +++++++++
tests/init/tasty-error/Main.scala | 1 +
tests/init/tasty-error/v0/A.scala | 3 ++
tests/init/tasty-error/v1/A.scala | 3 ++
tests/init/tasty-error/v1/B.scala | 4 ++
6 files changed, 71 insertions(+), 14 deletions(-)
create mode 100644 tests/init/tasty-error/Main.scala
create mode 100644 tests/init/tasty-error/v0/A.scala
create mode 100644 tests/init/tasty-error/v1/A.scala
create mode 100644 tests/init/tasty-error/v1/B.scala
diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala
index 85b2764ff0f3..adb2370bb1e0 100644
--- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala
+++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala
@@ -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 -----------------------------------
@@ -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 =
@@ -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
@@ -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)
@@ -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
@@ -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) }
@@ -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
@@ -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
@@ -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]
@@ -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)
@@ -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)
@@ -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
@@ -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.
diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala
index b170d4be77bb..cc1ce5a0145e 100644
--- a/compiler/test/dotty/tools/dotc/CompilationTests.scala
+++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala
@@ -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
diff --git a/tests/init/tasty-error/Main.scala b/tests/init/tasty-error/Main.scala
new file mode 100644
index 000000000000..2b27dd2b0d1f
--- /dev/null
+++ b/tests/init/tasty-error/Main.scala
@@ -0,0 +1 @@
+class Main extends B{} // anypos-error
\ No newline at end of file
diff --git a/tests/init/tasty-error/v0/A.scala b/tests/init/tasty-error/v0/A.scala
new file mode 100644
index 000000000000..be8c7d214378
--- /dev/null
+++ b/tests/init/tasty-error/v0/A.scala
@@ -0,0 +1,3 @@
+class A {
+ def fail(a: Int, b: Int): Int = a
+}
\ No newline at end of file
diff --git a/tests/init/tasty-error/v1/A.scala b/tests/init/tasty-error/v1/A.scala
new file mode 100644
index 000000000000..fa2956d1de7e
--- /dev/null
+++ b/tests/init/tasty-error/v1/A.scala
@@ -0,0 +1,3 @@
+class A {
+ def fail(a: Int): Int = a
+}
\ No newline at end of file
diff --git a/tests/init/tasty-error/v1/B.scala b/tests/init/tasty-error/v1/B.scala
new file mode 100644
index 000000000000..3059f487db64
--- /dev/null
+++ b/tests/init/tasty-error/v1/B.scala
@@ -0,0 +1,4 @@
+class B {
+ val a: A = new A
+ val x = a.fail(0)
+}
\ No newline at end of file