diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index adb2370bb1e0..a8a855b6ae5b 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -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 = @@ -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 = @@ -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 @@ -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] @@ -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 diff --git a/compiler/src/dotty/tools/dotc/transform/init/Util.scala b/compiler/src/dotty/tools/dotc/transform/init/Util.scala index ca30e2d32a4d..3280c289f926 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Util.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Util.scala @@ -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) @@ -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 diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 47ed2aa6564d..f533c02175bd 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -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()) } diff --git a/tests/init/tasty-error/typedef/C.scala b/tests/init/tasty-error/typedef/C.scala new file mode 100644 index 000000000000..9dccdd5e5954 --- /dev/null +++ b/tests/init/tasty-error/typedef/C.scala @@ -0,0 +1 @@ +class C \ No newline at end of file diff --git a/tests/init/tasty-error/typedef/Main.scala b/tests/init/tasty-error/typedef/Main.scala new file mode 100644 index 000000000000..232a7c34c4e1 --- /dev/null +++ b/tests/init/tasty-error/typedef/Main.scala @@ -0,0 +1,2 @@ +class Main extends B{} // anypos-error +class ExtendsB extends B{} \ No newline at end of file diff --git a/tests/init/tasty-error/typedef/v0/A.scala b/tests/init/tasty-error/typedef/v0/A.scala new file mode 100644 index 000000000000..9c86f47e2c77 --- /dev/null +++ b/tests/init/tasty-error/typedef/v0/A.scala @@ -0,0 +1,3 @@ +class A(c1: C) { + def fail(a: Int, b: Int): Int = a +} diff --git a/tests/init/tasty-error/typedef/v1/A.scala b/tests/init/tasty-error/typedef/v1/A.scala new file mode 100644 index 000000000000..a79930e5dd30 --- /dev/null +++ b/tests/init/tasty-error/typedef/v1/A.scala @@ -0,0 +1,3 @@ +class A(c1: C, c2: C) { + def fail(a: Int, b: Int): Int = a +} diff --git a/tests/init/tasty-error/typedef/v1/B.scala b/tests/init/tasty-error/typedef/v1/B.scala new file mode 100644 index 000000000000..801efcb5fdf0 --- /dev/null +++ b/tests/init/tasty-error/typedef/v1/B.scala @@ -0,0 +1,4 @@ +class B extends C{ + new A(this, this).fail(0,0) + val x = 5 +} \ No newline at end of file diff --git a/tests/init/tasty-error/Main.scala b/tests/init/tasty-error/val-or-defdef/Main.scala similarity index 100% rename from tests/init/tasty-error/Main.scala rename to tests/init/tasty-error/val-or-defdef/Main.scala diff --git a/tests/init/tasty-error/v0/A.scala b/tests/init/tasty-error/val-or-defdef/v0/A.scala similarity index 100% rename from tests/init/tasty-error/v0/A.scala rename to tests/init/tasty-error/val-or-defdef/v0/A.scala diff --git a/tests/init/tasty-error/v1/A.scala b/tests/init/tasty-error/val-or-defdef/v1/A.scala similarity index 100% rename from tests/init/tasty-error/v1/A.scala rename to tests/init/tasty-error/val-or-defdef/v1/A.scala diff --git a/tests/init/tasty-error/v1/B.scala b/tests/init/tasty-error/val-or-defdef/v1/B.scala similarity index 100% rename from tests/init/tasty-error/v1/B.scala rename to tests/init/tasty-error/val-or-defdef/v1/B.scala