Skip to content

Commit a10ceda

Browse files
committed
Revamp exception handling, from nsc
1 parent f56089b commit a10ceda

File tree

9 files changed

+112
-36
lines changed

9 files changed

+112
-36
lines changed

compiler/src/dotty/tools/dotc/Driver.scala

+7-6
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,19 @@ class Driver {
3030

3131
protected def doCompile(compiler: Compiler, files: List[AbstractFile])(using Context): Reporter =
3232
if files.nonEmpty then
33+
var run = ctx.run
3334
try
34-
val run = compiler.newRun
35+
run = compiler.newRun
3536
run.compile(files)
3637
finish(compiler, run)
3738
catch
3839
case ex: FatalError =>
39-
report.error(ex.getMessage.nn) // signals that we should fail compilation.
40-
case ex: TypeError =>
41-
println(s"${ex.toMessage} while compiling ${files.map(_.path).mkString(", ")}")
40+
report.error(run.enrichErrorMessage(ex.getMessage.nn)) // signals that we should fail compilation.
41+
case ex: TypeError if !run.enrichedErrorMessage =>
42+
println(run.enrichErrorMessage(s"TypeError while compiling ${files.map(_.path).mkString(", ")}"))
4243
throw ex
43-
case ex: Throwable =>
44-
println(s"$ex while compiling ${files.map(_.path).mkString(", ")}")
44+
case NonFatal(ex) if !run.enrichedErrorMessage =>
45+
println(run.enrichErrorMessage(s"Exception while compiling ${files.map(_.path).mkString(", ")}"))
4546
throw ex
4647
ctx.reporter
4748

compiler/src/dotty/tools/dotc/Run.scala

+15-8
Original file line numberDiff line numberDiff line change
@@ -173,15 +173,22 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
173173
*/
174174
var ccImportEncountered = false
175175

176+
private var myEnrichedErrorMessage = false
177+
178+
def enrichedErrorMessage: Boolean = myEnrichedErrorMessage
179+
180+
def enrichErrorMessage(errorMessage: String)(using Context): String =
181+
if !enrichedErrorMessage then
182+
myEnrichedErrorMessage = true
183+
report.enrichErrorMessage(errorMessage)
184+
else errorMessage
185+
176186
def compile(files: List[AbstractFile]): Unit =
177-
try
178-
val sources = files.map(runContext.getSource(_))
179-
compileSources(sources)
180-
catch
181-
case NonFatal(ex) =>
182-
if units.nonEmpty then report.echo(i"exception occurred while compiling $units%, %")
183-
else report.echo(s"exception occurred while compiling ${files.map(_.name).mkString(", ")}")
184-
throw ex
187+
try compileSources(files.map(runContext.getSource(_)))
188+
catch case NonFatal(ex) if !enrichedErrorMessage =>
189+
val files1 = if units.nonEmpty then units.map(_.source.file) else files
190+
report.echo(enrichErrorMessage(s"exception occurred while compiling ${files1.map(_.path)}"))
191+
throw ex
185192

186193
/** TODO: There's a fundamental design problem here: We assemble phases using `fusePhases`
187194
* when we first build the compiler. But we modify them with -Yskip, -Ystop

compiler/src/dotty/tools/dotc/core/Decorators.scala

+1-4
Original file line numberDiff line numberDiff line change
@@ -280,10 +280,7 @@ object Decorators {
280280
case ex: CyclicReference => "... (caught cyclic reference) ..."
281281
case NonFatal(ex)
282282
if !ctx.mode.is(Mode.PrintShowExceptions) && !ctx.settings.YshowPrintErrors.value =>
283-
val msg = ex match
284-
case te: TypeError => te.toMessage.message
285-
case _ => ex.getMessage
286-
s"[cannot display due to $msg, raw string = $x]"
283+
s"... (caught ${ex.className}) ..."
287284
case _ => String.valueOf(x).nn
288285

289286
/** Returns the simple class name of `x`. */

compiler/src/dotty/tools/dotc/core/Phases.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -322,8 +322,8 @@ object Phases {
322322
units.map { unit =>
323323
val unitCtx = ctx.fresh.setPhase(this.start).setCompilationUnit(unit).withRootImports
324324
try run(using unitCtx)
325-
catch case ex: Throwable =>
326-
println(s"$ex while running $phaseName on $unit")
325+
catch case NonFatal(ex) if !ctx.run.enrichedErrorMessage =>
326+
println(ctx.run.enrichErrorMessage(s"unhandled exception while running $phaseName on $unit"))
327327
throw ex
328328
unitCtx.compilationUnit
329329
}

compiler/src/dotty/tools/dotc/core/TypeErasure.scala

+2
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,8 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
687687
tp
688688
case tp if (tp `eq` NoType) || (tp `eq` NoPrefix) =>
689689
tp
690+
case tp =>
691+
unreachable(i"TypeErasure applied to $tp (of class ${tp.className})")
690692
}
691693

692694
/** Like translucentSuperType, but issue a fatal error if it does not exist. */

compiler/src/dotty/tools/dotc/core/TypeErrors.scala

-1
Original file line numberDiff line numberDiff line change
@@ -183,4 +183,3 @@ object CyclicReference:
183183
cyclicErrors.println(elem.toString)
184184
ex
185185
end CyclicReference
186-

compiler/src/dotty/tools/dotc/report.scala

+60-3
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@ import reporting._
44
import Diagnostic._
55
import util.{SourcePosition, NoSourcePosition, SrcPos}
66
import core._
7-
import Contexts._, Symbols._, Decorators._
7+
import Contexts._, Flags.*, Symbols._, Decorators._
88
import config.SourceVersion
9-
import ast._
9+
import ast._, tpd.*
1010
import config.Feature.sourceVersion
1111
import java.lang.System.currentTimeMillis
1212

13-
1413
object report:
1514

1615
/** For sending messages that are printed only if -verbose is set */
@@ -129,4 +128,62 @@ object report:
129128
case Nil => pos
130129
recur(pos.sourcePos, tpd.enclosingInlineds)
131130

131+
private object messageRendering extends MessageRendering
132+
133+
// Should only be called from Run#enrichErrorMessage.
134+
def enrichErrorMessage(errorMessage: String)(using Context): String = try {
135+
def formatExplain(pairs: List[(String, Any)]) = pairs.map((k, v) => f"$k%20s: $v").mkString("\n")
136+
137+
val settings = ctx.settings.userSetSettings(ctx.settingsState).sortBy(_.name)
138+
val tree = ctx.tree
139+
val sym = tree.symbol
140+
val pos = tree.sourcePos
141+
val path = pos.source.path
142+
val site = ctx.outersIterator.map(_.owner).filter(sym => !sym.exists || sym.isClass || sym.is(Method)).next()
143+
144+
extension (tree: Tree) def summaryString: String = tree match
145+
case Literal(const) => s"Literal($const)"
146+
case Ident(name) => s"Ident(${name.decode})"
147+
case Select(qual, name) => s"Select(${qual.summaryString}, ${name.decode})"
148+
case tree: NameTree => (if tree.isType then "type " else "") + tree.name.decode
149+
case tree => s"${tree.className}${if tree.symbol.exists then s"(${tree.symbol})" else ""}"
150+
151+
val info1 = formatExplain(List(
152+
"while compiling" -> ctx.compilationUnit,
153+
"during phase" -> ctx.phase.prevMega,
154+
"mode" -> ctx.mode,
155+
"library version" -> scala.util.Properties.versionString,
156+
"compiler version" -> dotty.tools.dotc.config.Properties.versionString,
157+
"settings" -> settings.map(s => if s.value == "" then s"${s.name} \"\"" else s"${s.name} ${s.value}").mkString(" "),
158+
))
159+
val symbolInfos = if sym eq NoSymbol then List("symbol" -> sym) else List(
160+
"symbol" -> sym.showLocated,
161+
"symbol definition" -> s"${sym.showDcl} (a ${sym.className})",
162+
"symbol package" -> sym.enclosingPackageClass.fullName,
163+
"symbol owners" -> sym.showExtendedLocation,
164+
)
165+
val info2 = formatExplain(List(
166+
"tree" -> tree.summaryString,
167+
"tree position" -> (if pos.exists then s"$path:${pos.line + 1}:${pos.column}" else s"$path:<unknown>"),
168+
"tree type" -> tree.typeOpt.show,
169+
) ::: symbolInfos ::: List(
170+
"call site" -> s"${site.showLocated} in ${site.enclosingPackageClass}"
171+
))
172+
val context_s = try
173+
s""" == Source file context for tree position ==
174+
|
175+
|${messageRendering.messageAndPos(Diagnostic.Error("", pos))}""".stripMargin
176+
catch case _: Exception => "<Cannot read source file>"
177+
s"""
178+
| $errorMessage
179+
|
180+
| An unhandled exception was thrown in the compiler. Please file a crash
181+
| report here: https://github.com/lampepfl/dotty/issues/new/choose
182+
|
183+
|$info1
184+
|
185+
|$info2
186+
|
187+
|$context_s""".stripMargin
188+
} catch case _: Throwable => errorMessage // don't introduce new errors trying to report errors, so swallow exceptions
132189
end report

compiler/src/dotty/tools/dotc/typer/ReTyper.scala

+4-6
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,10 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking
124124

125125
override def typedUnadapted(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree =
126126
try super.typedUnadapted(tree, pt, locked)
127-
catch {
128-
case NonFatal(ex) =>
129-
if ctx.phase != Phases.typerPhase && ctx.phase != Phases.inliningPhase then
130-
println(i"exception while typing $tree of class ${tree.getClass} # ${tree.uniqueId}")
131-
throw ex
132-
}
127+
catch case NonFatal(ex) if ctx.phase != Phases.typerPhase && ctx.phase != Phases.inliningPhase && !ctx.run.enrichedErrorMessage =>
128+
val treeStr = tree.show(using ctx.withPhase(ctx.phase.prevMega))
129+
println(ctx.run.enrichErrorMessage(s"exception while retyping $treeStr of class ${tree.className} # ${tree.uniqueId}"))
130+
throw ex
133131

134132
override def inlineExpansion(mdef: DefDef)(using Context): List[Tree] = mdef :: Nil
135133

compiler/src/dotty/tools/package.scala

+21-6
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,27 @@ package object tools {
3939
def unreachable(x: Any = "<< this case was declared unreachable >>"): Nothing =
4040
throw new MatchError(x)
4141

42-
transparent inline def assertShort(inline assertion: Boolean, inline message: Any = null): Unit =
43-
if !assertion then
44-
val msg = message
45-
val e = if msg == null then AssertionError() else AssertionError("assertion failed: " + msg)
46-
e.setStackTrace(Array())
47-
throw e
42+
transparent inline def assertShort(inline assertion: Boolean, inline message: Any | Null = null): Unit =
43+
if !assertion then throwAssertionShortError(message)
44+
45+
private def throwAssertionShortError(msg: Any): Nothing =
46+
val e = AssertionError("assertion failed: " + String.valueOf(msg))
47+
e.setStackTrace(Array())
48+
throw e
49+
50+
import dotty.tools.dotc.core.Contexts.Context
51+
52+
transparent inline def assert(inline assertion: Boolean, inline message: Any | Null = null)(using inline ctx: Context | Null = null): Unit =
53+
if !assertion then throwAssertionError(message)
54+
55+
// extracted from `assert` to make it as small (and inlineable) as possible
56+
private def throwAssertionError(msg: Any | Null)(using ctx: Context | Null): Nothing =
57+
if ctx == null || ctx.run == null then
58+
throw AssertionError("assertion failed: " + String.valueOf(msg))
59+
else if ctx.run == null then
60+
throw AssertionError("assertion failed: " + dotc.report.enrichErrorMessage(String.valueOf(msg)))
61+
else
62+
throw AssertionError("assertion failed: " + ctx.run.enrichErrorMessage(String.valueOf(msg)))
4863

4964
// Ensure this object is already classloaded, since it's only actually used
5065
// when handling stack overflows and every operation (including class loading)

0 commit comments

Comments
 (0)