Skip to content

Commit 4a85e27

Browse files
authored
Add error-checking when fetching rhs of trees from TASTy (#22565)
This PR would fix the error raised in #22442. The error arises when reading incompatible TASTy trees. This PR checks if an error occurs when fetching the rhs of ValOrDefDef trees and gives a warning.
2 parents bef520f + e808881 commit 4a85e27

File tree

6 files changed

+71
-14
lines changed

6 files changed

+71
-14
lines changed

Diff for: compiler/src/dotty/tools/dotc/transform/init/Semantic.scala

+37-14
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ object Semantic:
378378
// ----- Checker State -----------------------------------
379379

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

383383
// ----- Error Handling -----------------------------------
384384

@@ -443,6 +443,29 @@ object Semantic:
443443

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

446+
// ----- Cache for Trees -----------------------------
447+
448+
object TreeCache:
449+
class CacheData:
450+
private val emptyTrees = mutable.Set[ValOrDefDef]()
451+
452+
extension (tree: ValOrDefDef)
453+
def getRhs(using Context): Tree =
454+
def getTree: Tree =
455+
val errorCount = ctx.reporter.errorCount
456+
val rhs = tree.rhs
457+
458+
if (ctx.reporter.errorCount > errorCount)
459+
emptyTrees.add(tree)
460+
report.warning("Ignoring analyses of " + tree.name + " due to error in reading TASTy.")
461+
EmptyTree
462+
else
463+
rhs
464+
465+
if (emptyTrees.contains(tree)) EmptyTree
466+
else getTree
467+
end TreeCache
468+
446469
// ----- Operations on domains -----------------------------
447470
extension (a: Value)
448471
def join(b: Value): Value =
@@ -576,7 +599,7 @@ object Semantic:
576599
case ref: Ref =>
577600
val target = if needResolve then resolve(ref.klass, field) else field
578601
if target.is(Flags.Lazy) then
579-
val rhs = target.defTree.asInstanceOf[ValDef].rhs
602+
val rhs = target.defTree.asInstanceOf[ValDef].getRhs
580603
eval(rhs, ref, target.owner.asClass, cacheResult = true)
581604
else if target.exists then
582605
val obj = ref.objekt
@@ -591,7 +614,7 @@ object Semantic:
591614
// return `Hot` here, errors are reported in checking `ThisRef`
592615
Hot
593616
else if target.hasSource then
594-
val rhs = target.defTree.asInstanceOf[ValOrDefDef].rhs
617+
val rhs = target.defTree.asInstanceOf[ValOrDefDef].getRhs
595618
eval(rhs, ref, target.owner.asClass, cacheResult = true)
596619
else
597620
val error = CallUnknown(field)(trace)
@@ -715,7 +738,7 @@ object Semantic:
715738
else
716739
reporter.reportAll(tryReporter.errors)
717740
extendTrace(ddef) {
718-
eval(ddef.rhs, ref, cls, cacheResult = true)
741+
eval(ddef.getRhs, ref, cls, cacheResult = true)
719742
}
720743
else if ref.canIgnoreMethodCall(target) then
721744
Hot
@@ -777,7 +800,7 @@ object Semantic:
777800
val tpl = cls.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template]
778801
extendTrace(cls.defTree) { init(tpl, ref, cls) }
779802
else
780-
val initCall = ddef.rhs match
803+
val initCall = ddef.getRhs match
781804
case Block(call :: _, _) => call
782805
case call => call
783806
extendTrace(ddef) { eval(initCall, ref, cls) }
@@ -796,7 +819,7 @@ object Semantic:
796819
extendTrace(cls.defTree) { eval(tpl, ref, cls, cacheResult = true) }
797820
ref
798821
else
799-
extendTrace(ddef) { eval(ddef.rhs, ref, cls, cacheResult = true) }
822+
extendTrace(ddef) { eval(ddef.getRhs, ref, cls, cacheResult = true) }
800823
else if ref.canIgnoreMethodCall(ctor) then
801824
Hot
802825
else
@@ -906,8 +929,7 @@ object Semantic:
906929

907930
case Cold => Cold
908931

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

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

@@ -1320,10 +1343,10 @@ object Semantic:
13201343
}
13211344

13221345
case closureDef(ddef) =>
1323-
Fun(ddef.rhs, thisV, klass)
1346+
Fun(ddef.getRhs, thisV, klass)
13241347

13251348
case PolyFun(ddef) =>
1326-
Fun(ddef.rhs, thisV, klass)
1349+
Fun(ddef.getRhs, thisV, klass)
13271350

13281351
case Block(stats, expr) =>
13291352
eval(stats, thisV, klass)
@@ -1375,7 +1398,7 @@ object Semantic:
13751398

13761399
case vdef : ValDef =>
13771400
// local val definition
1378-
eval(vdef.rhs, thisV, klass)
1401+
eval(vdef.getRhs, thisV, klass)
13791402

13801403
case ddef : DefDef =>
13811404
// local method
@@ -1593,8 +1616,8 @@ object Semantic:
15931616

15941617
// class body
15951618
if thisV.isThisRef || !thisV.asInstanceOf[Warm].isPopulatingParams then tpl.body.foreach {
1596-
case vdef : ValDef if !vdef.symbol.is(Flags.Lazy) && !vdef.rhs.isEmpty =>
1597-
val res = eval(vdef.rhs, thisV, klass)
1619+
case vdef : ValDef if !vdef.symbol.is(Flags.Lazy) && !vdef.getRhs.isEmpty =>
1620+
val res = eval(vdef.getRhs, thisV, klass)
15981621
// TODO: Improve promotion to avoid handling enum initialization specially
15991622
//
16001623
// The failing case is tests/init/pos/i12544.scala due to promotion failure.

Diff for: compiler/test/dotty/tools/dotc/CompilationTests.scala

+23
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,29 @@ class CompilationTests {
263263

264264
tests.foreach(_.delete())
265265
}
266+
267+
/* This tests for errors in the program's TASTy trees.
268+
* The test consists of three files: (a) v1/A, (b) v1/B, and (c) v0/A. (a) and (b) are
269+
* compatible, but (b) and (c) are not. If (b) and (c) are compiled together, there should be
270+
* an error when reading the files' TASTy trees. */
271+
locally {
272+
val tastyErrorGroup = TestGroup("checkInit/tasty-error")
273+
val tastyErrorOptions = options.without("-Xfatal-warnings")
274+
275+
val a0Dir = defaultOutputDir + tastyErrorGroup + "/A/v0/A"
276+
val a1Dir = defaultOutputDir + tastyErrorGroup + "/A/v1/A"
277+
val b1Dir = defaultOutputDir + tastyErrorGroup + "/B/v1/B"
278+
279+
val tests = List(
280+
compileFile("tests/init/tasty-error/v1/A.scala", tastyErrorOptions)(tastyErrorGroup),
281+
compileFile("tests/init/tasty-error/v1/B.scala", tastyErrorOptions.withClasspath(a1Dir))(tastyErrorGroup),
282+
compileFile("tests/init/tasty-error/v0/A.scala", tastyErrorOptions)(tastyErrorGroup),
283+
).map(_.keepOutput.checkCompile())
284+
285+
compileFile("tests/init/tasty-error/Main.scala", tastyErrorOptions.withClasspath(a0Dir).withClasspath(b1Dir))(tastyErrorGroup).checkExpectedErrors()
286+
287+
tests.foreach(_.delete())
288+
}
266289
}
267290

268291
// parallel backend tests

Diff for: tests/init/tasty-error/Main.scala

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
class Main extends B{} // anypos-error

Diff for: tests/init/tasty-error/v0/A.scala

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class A {
2+
def fail(a: Int, b: Int): Int = a
3+
}

Diff for: tests/init/tasty-error/v1/A.scala

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class A {
2+
def fail(a: Int): Int = a
3+
}

Diff for: tests/init/tasty-error/v1/B.scala

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class B {
2+
val a: A = new A
3+
val x = a.fail(0)
4+
}

0 commit comments

Comments
 (0)