From 822e792894753ded1d6ce06a94592c3ba48c7eb6 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 13 Mar 2024 16:54:17 +0100 Subject: [PATCH 1/3] Avoid crash when superType does not exist after erasure Fixes #19929 Two main changes: - In TypeErasure, throw a TypeError instead of a FatalError if a supertype of an applied type does not exist. That way, we get a proper error with a position. - Move some catch-and-rethrow logic from ReTyper to TreeChecker. ReTyper alreayd had special exceptions that disabled the logic for all uses of ReTyper except TreeChecker. Unfortunately the ReTyper override also disabled the special TypeError handling in Typer. --- .../dotty/tools/dotc/core/TypeErasure.scala | 8 +-- .../dotty/tools/dotc/core/TypeErrors.scala | 3 +- .../tools/dotc/transform/TreeChecker.scala | 54 ++++++++++--------- .../src/dotty/tools/dotc/typer/ReTyper.scala | 7 --- tests/neg/i19929.check | 5 ++ tests/neg/i19929.scala | 5 ++ 6 files changed, 45 insertions(+), 37 deletions(-) create mode 100644 tests/neg/i19929.check create mode 100644 tests/neg/i19929.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 0474aff4087a..48fb1bab2da1 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -751,12 +751,12 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst private def checkedSuperType(tp: TypeProxy)(using Context): Type = val tp1 = tp.translucentSuperType if !tp1.exists then - val msg = tp.typeConstructor match + val typeErr = tp.typeConstructor match case tycon: TypeRef => - MissingType(tycon.prefix, tycon.name).toMessage.message + MissingType(tycon.prefix, tycon.name) case _ => - i"Cannot resolve reference to $tp" - throw FatalError(msg) + TypeError(em"Cannot resolve reference to $tp") + throw typeErr tp1 /** Widen term ref, skipping any `()` parameter of an eventual getter. Used to erase a TermRef. diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala index 240bc4eebd84..13fe02b712bc 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -46,7 +46,8 @@ abstract class TypeError(using creationContext: Context) extends Exception(""): def toMessage(using Context): Message /** Uses creationContext to produce the message */ - override def getMessage: String = toMessage.message + override def getMessage: String = + try toMessage.message catch case ex: Throwable => "TypeError" object TypeError: def apply(msg: Message)(using Context) = new TypeError: diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index f1f703fb07ee..0515a6978a47 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -418,31 +418,35 @@ object TreeChecker { } override def typedUnadapted(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = { - val res = tree match { - case _: untpd.TypedSplice | _: untpd.Thicket | _: EmptyValDef[?] => - super.typedUnadapted(tree, pt, locked) - case _ if tree.isType => - promote(tree) - case _ => - val tree1 = super.typedUnadapted(tree, pt, locked) - def isSubType(tp1: Type, tp2: Type) = - (tp1 eq tp2) || // accept NoType / NoType - (tp1 <:< tp2) - def divergenceMsg(tp1: Type, tp2: Type) = - s"""Types differ - |Original type : ${tree.typeOpt.show} - |After checking: ${tree1.tpe.show} - |Original tree : ${tree.show} - |After checking: ${tree1.show} - |Why different : - """.stripMargin + core.TypeComparer.explained(_.isSubType(tp1, tp2)) - if (tree.hasType) // it might not be typed because Typer sometimes constructs new untyped trees and resubmits them to typedUnadapted - assert(isSubType(tree1.tpe, tree.typeOpt), divergenceMsg(tree1.tpe, tree.typeOpt)) - tree1 - } - checkNoOrphans(res.tpe) - phasesToCheck.foreach(_.checkPostCondition(res)) - res + try + val res = tree match + case _: untpd.TypedSplice | _: untpd.Thicket | _: EmptyValDef[?] => + super.typedUnadapted(tree, pt, locked) + case _ if tree.isType => + promote(tree) + case _ => + val tree1 = super.typedUnadapted(tree, pt, locked) + def isSubType(tp1: Type, tp2: Type) = + (tp1 eq tp2) || // accept NoType / NoType + (tp1 <:< tp2) + def divergenceMsg(tp1: Type, tp2: Type) = + s"""Types differ + |Original type : ${tree.typeOpt.show} + |After checking: ${tree1.tpe.show} + |Original tree : ${tree.show} + |After checking: ${tree1.show} + |Why different : + """.stripMargin + core.TypeComparer.explained(_.isSubType(tp1, tp2)) + if (tree.hasType) // it might not be typed because Typer sometimes constructs new untyped trees and resubmits them to typedUnadapted + assert(isSubType(tree1.tpe, tree.typeOpt), divergenceMsg(tree1.tpe, tree.typeOpt)) + tree1 + checkNoOrphans(res.tpe) + phasesToCheck.foreach(_.checkPostCondition(res)) + res + catch case NonFatal(ex) if !ctx.run.enrichedErrorMessage => + val treeStr = tree.show(using ctx.withPhase(ctx.phase.prev.megaPhase)) + println(ctx.run.enrichErrorMessage(s"exception while retyping $treeStr of class ${tree.className} # ${tree.uniqueId}")) + throw ex } def checkNotRepeated(tree: Tree)(using Context): tree.type = { diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index e152b5e6b9c7..9741a366da89 100644 --- a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -170,13 +170,6 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking override def addCanThrowCapabilities(expr: untpd.Tree, cases: List[CaseDef])(using Context): untpd.Tree = expr - override def typedUnadapted(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = - try super.typedUnadapted(tree, pt, locked) - catch case NonFatal(ex) if ctx.phase != Phases.typerPhase && ctx.phase != Phases.inliningPhase && !ctx.run.enrichedErrorMessage => - val treeStr = tree.show(using ctx.withPhase(ctx.phase.prev.megaPhase)) - println(ctx.run.enrichErrorMessage(s"exception while retyping $treeStr of class ${tree.className} # ${tree.uniqueId}")) - throw ex - override def inlineExpansion(mdef: DefDef)(using Context): List[Tree] = mdef :: Nil override def inferView(from: Tree, to: Type)(using Context): Implicits.SearchResult = diff --git a/tests/neg/i19929.check b/tests/neg/i19929.check new file mode 100644 index 000000000000..6782cd2133ef --- /dev/null +++ b/tests/neg/i19929.check @@ -0,0 +1,5 @@ +-- Error: tests/neg/i19929.scala:5:6 ----------------------------------------------------------------------------------- +5 | val _: a.M = ??? // error was crash + | ^ + | cannot resolve reference to type (a : A).M + | the classfile defining the type might be missing from the classpath diff --git a/tests/neg/i19929.scala b/tests/neg/i19929.scala new file mode 100644 index 000000000000..af88d950dc4f --- /dev/null +++ b/tests/neg/i19929.scala @@ -0,0 +1,5 @@ +trait A: + private type M + +def foo(a: A{type M = Int}) = + val _: a.M = ??? // error was crash From 4920146f554d418808da97ec537a356ac2562f35 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 15 Apr 2024 13:45:37 +0200 Subject: [PATCH 2/3] Reclassify test --- tests/{neg => pos}/i19929.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/{neg => pos}/i19929.scala (60%) diff --git a/tests/neg/i19929.scala b/tests/pos/i19929.scala similarity index 60% rename from tests/neg/i19929.scala rename to tests/pos/i19929.scala index af88d950dc4f..2e1c691af8f5 100644 --- a/tests/neg/i19929.scala +++ b/tests/pos/i19929.scala @@ -2,4 +2,4 @@ trait A: private type M def foo(a: A{type M = Int}) = - val _: a.M = ??? // error was crash + val _: a.M = ??? // was crash From 16d9e3d3fada48983933d08da472debb7b7fa9e4 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Mon, 15 Apr 2024 17:00:57 +0200 Subject: [PATCH 3/3] Amend reclassify test --- tests/neg/i19929.check | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 tests/neg/i19929.check diff --git a/tests/neg/i19929.check b/tests/neg/i19929.check deleted file mode 100644 index 6782cd2133ef..000000000000 --- a/tests/neg/i19929.check +++ /dev/null @@ -1,5 +0,0 @@ --- Error: tests/neg/i19929.scala:5:6 ----------------------------------------------------------------------------------- -5 | val _: a.M = ??? // error was crash - | ^ - | cannot resolve reference to type (a : A).M - | the classfile defining the type might be missing from the classpath