Skip to content

Commit

Permalink
Avoid crash when superType does not exist after erasure (#20188)
Browse files Browse the repository at this point in the history
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
already 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.

The root cause of #19929 got fixed by another PR, but I think it's still
good to do the hardening of this commit.
  • Loading branch information
EugeneFlesselle authored Apr 15, 2024
2 parents 54d67e0 + 16d9e3d commit c47138c
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 37 deletions.
8 changes: 4 additions & 4 deletions compiler/src/dotty/tools/dotc/core/TypeErasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/core/TypeErrors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
54 changes: 29 additions & 25 deletions compiler/src/dotty/tools/dotc/transform/TreeChecker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
7 changes: 0 additions & 7 deletions compiler/src/dotty/tools/dotc/typer/ReTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
5 changes: 5 additions & 0 deletions tests/pos/i19929.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
trait A:
private type M

def foo(a: A{type M = Int}) =
val _: a.M = ??? // was crash

0 comments on commit c47138c

Please sign in to comment.