Skip to content

Check for tasty error in template trees. #22867

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

Merged
merged 1 commit into from
Apr 14, 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
25 changes: 23 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/init/Semantic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,18 @@ object Semantic:
object TreeCache:
class CacheData:
private val emptyTrees = mutable.Set[ValOrDefDef]()
private val templatesToSkip = mutable.Set[Template]()

def checkTemplateBodyValidity(tpl: Template, className: String)(using Context): Unit =
if (templatesToSkip.contains(tpl))
throw new TastyTreeException(className)

val errorCount = ctx.reporter.errorCount
tpl.forceFields()

if (ctx.reporter.errorCount > errorCount)
templatesToSkip.add(tpl)
throw new TastyTreeException(className)

extension (tree: ValOrDefDef)
def getRhs(using Context): Tree =
Expand All @@ -465,7 +477,9 @@ object Semantic:
if (emptyTrees.contains(tree)) EmptyTree
else getTree
end TreeCache


inline def treeCache(using t: TreeCache.CacheData): TreeCache.CacheData = t

// ----- Operations on domains -----------------------------
extension (a: Value)
def join(b: Value): Value =
Expand Down Expand Up @@ -654,6 +668,8 @@ object Semantic:
val methodType = atPhaseBeforeTransforms { meth.info.stripPoly }
var allArgsHot = true
val allParamTypes = methodType.paramInfoss.flatten.map(_.repeatedToSingle)
if(allParamTypes.size != args.size)
report.warning("[Internal error] Number of parameters do not match number of arguments in " + meth.name)
val errors = allParamTypes.zip(args).flatMap { (info, arg) =>
val tryReporter = Reporter.errorsIn { arg.promote }
allArgsHot = allArgsHot && tryReporter.errors.isEmpty
Expand Down Expand Up @@ -1173,7 +1189,10 @@ object Semantic:
given Cache.Data()
given TreeCache.CacheData()
for classSym <- classes if isConcreteClass(classSym) && !classSym.isStaticObject do
checkClass(classSym)
try
checkClass(classSym)
catch
case TastyTreeException(className) => report.warning("Skipping the analysis of " + classSym.show + " due to an error reading the body of " + className + "'s TASTy.")

// ----- Semantic definition --------------------------------
type ArgInfo = TraceValue[Value]
Expand Down Expand Up @@ -1520,6 +1539,8 @@ object Semantic:
* @param klass The class to which the template belongs.
*/
def init(tpl: Template, thisV: Ref, klass: ClassSymbol): Contextual[Value] = log("init " + klass.show, printer, (_: Value).show) {
treeCache.checkTemplateBodyValidity(tpl, klass.show)

val paramsMap = tpl.constr.termParamss.flatten.map { vdef =>
vdef.name -> thisV.objekt.field(vdef.symbol)
}.toMap
Expand Down
5 changes: 5 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/init/Util.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import config.Printers.init as printer
import Trace.*

object Util:
/** Exception used for errors encountered when reading TASTy. */
case class TastyTreeException(msg: String) extends RuntimeException(msg)

/** Utility definition used for better error-reporting of argument errors */
case class TraceValue[T](value: T, trace: Trace)

Expand Down Expand Up @@ -43,6 +46,8 @@ object Util:
case Apply(fn, args) =>
val argTps = fn.tpe.widen match
case mt: MethodType => mt.paramInfos
if (args.size != argTps.size)
report.warning("[Internal error] Number of arguments do not match number of argument types in " + tree.symbol.name)
val normArgs: List[Arg] = args.zip(argTps).map {
case (arg, _: ExprType) => ByNameArg(arg)
case (arg, _) => arg
Expand Down
41 changes: 33 additions & 8 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -278,20 +278,45 @@ class CompilationTests {
* 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 tastyErrorGroup = TestGroup("checkInit/tasty-error/val-or-defdef")
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 classA0 = defaultOutputDir + tastyErrorGroup + "/A/v0/A"
val classA1 = defaultOutputDir + tastyErrorGroup + "/A/v1/A"
val classB1 = 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),
compileFile("tests/init/tasty-error/val-or-defdef/v1/A.scala", tastyErrorOptions)(tastyErrorGroup),
compileFile("tests/init/tasty-error/val-or-defdef/v1/B.scala", tastyErrorOptions.withClasspath(classA1))(tastyErrorGroup),
compileFile("tests/init/tasty-error/val-or-defdef/v0/A.scala", tastyErrorOptions)(tastyErrorGroup),
).map(_.keepOutput.checkCompile())

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

tests.foreach(_.delete())
}

/* This tests for errors in the program's TASTy trees.
* The test consists of five files: Main, C, v1/A, v1/B, and v0/A. The files v1/A, v1/B, and v0/A all depend on C. v1/A and v1/B are
* compatible, but v1/B and v0/A are not. If v1/B and v0/A are compiled together, there should be
* an error when reading the files' TASTy trees. This fact is demonstrated by the compilation of Main. */
locally {
val tastyErrorGroup = TestGroup("checkInit/tasty-error/typedef")
val tastyErrorOptions = options.without("-Xfatal-warnings").without("-Ycheck:all")

val classC = defaultOutputDir + tastyErrorGroup + "/C/typedef/C"
val classA0 = defaultOutputDir + tastyErrorGroup + "/A/v0/A"
val classA1 = defaultOutputDir + tastyErrorGroup + "/A/v1/A"
val classB1 = defaultOutputDir + tastyErrorGroup + "/B/v1/B"

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

compileFile("tests/init/tasty-error/typedef/Main.scala", tastyErrorOptions.withClasspath(classC).withClasspath(classA0).withClasspath(classB1))(tastyErrorGroup).checkExpectedErrors()

tests.foreach(_.delete())
}
Expand Down
1 change: 1 addition & 0 deletions tests/init/tasty-error/typedef/C.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
class C
2 changes: 2 additions & 0 deletions tests/init/tasty-error/typedef/Main.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class Main extends B{} // anypos-error
class ExtendsB extends B{}
3 changes: 3 additions & 0 deletions tests/init/tasty-error/typedef/v0/A.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class A(c1: C) {
def fail(a: Int, b: Int): Int = a
}
3 changes: 3 additions & 0 deletions tests/init/tasty-error/typedef/v1/A.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class A(c1: C, c2: C) {
def fail(a: Int, b: Int): Int = a
}
4 changes: 4 additions & 0 deletions tests/init/tasty-error/typedef/v1/B.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class B extends C{
new A(this, this).fail(0,0)
val x = 5
}
Loading