Skip to content

Commit 3009036

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

File tree

8 files changed

+123
-34
lines changed

8 files changed

+123
-34
lines changed

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

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
package dotty.tools.dotc
1+
package dotty.tools
2+
package dotc
23

34
import dotty.tools.FatalError
45
import config.CompilerCommand
@@ -30,18 +31,20 @@ class Driver {
3031

3132
protected def doCompile(compiler: Compiler, files: List[AbstractFile])(using Context): Reporter =
3233
if files.nonEmpty then
34+
var run1 = ctx.run
3335
try
3436
val run = compiler.newRun
37+
run1 = run
3538
run.compile(files)
3639
finish(compiler, run)
3740
catch
3841
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(", ")}")
42+
report.error(run1.enrichErrorMessage(ex.getMessage.nn)) // signals that we should fail compilation.
43+
case ex: TypeError if !run1.enrichedErrorMessage =>
44+
println(run1.enrichErrorMessage(s"TypeError while compiling ${files.map(_.path).mkString(", ")}"))
4245
throw ex
43-
case ex: Throwable =>
44-
println(s"$ex while compiling ${files.map(_.path).mkString(", ")}")
46+
case NonFatal(ex) if !run1.enrichedErrorMessage =>
47+
println(run1.enrichErrorMessage(s"Exception while compiling ${files.map(_.path).mkString(", ")}"))
4548
throw ex
4649
ctx.reporter
4750

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

+24-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
@@ -395,3 +402,12 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
395402
given runContext[Dummy_so_its_a_def]: Context = myCtx.nn
396403
assert(runContext.runId <= Periods.MaxPossibleRunId)
397404
}
405+
406+
object Run {
407+
extension (run: Run | Null)
408+
def enrichedErrorMessage: Boolean = if run == null then false else run.enrichedErrorMessage
409+
def enrichErrorMessage(errorMessage: String)(using Context): String =
410+
if run == null
411+
then report.enrichErrorMessage(errorMessage)
412+
else run.enrichErrorMessage(errorMessage)
413+
}

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/report.scala

+60-2
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
99
import ast._
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,63 @@ 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+
import untpd.*
145+
extension (tree: Tree) def summaryString: String = tree match
146+
case Literal(const) => s"Literal($const)"
147+
case Ident(name) => s"Ident(${name.decode})"
148+
case Select(qual, name) => s"Select(${qual.summaryString}, ${name.decode})"
149+
case tree: NameTree => (if tree.isType then "type " else "") + tree.name.decode
150+
case tree => s"${tree.className}${if tree.symbol.exists then s"(${tree.symbol})" else ""}"
151+
152+
val info1 = formatExplain(List(
153+
"while compiling" -> ctx.compilationUnit,
154+
"during phase" -> ctx.phase.prevMega,
155+
"mode" -> ctx.mode,
156+
"library version" -> scala.util.Properties.versionString,
157+
"compiler version" -> dotty.tools.dotc.config.Properties.versionString,
158+
"settings" -> settings.map(s => if s.value == "" then s"${s.name} \"\"" else s"${s.name} ${s.value}").mkString(" "),
159+
))
160+
val symbolInfos = if sym eq NoSymbol then List("symbol" -> sym) else List(
161+
"symbol" -> sym.showLocated,
162+
"symbol definition" -> s"${sym.showDcl} (a ${sym.className})",
163+
"symbol package" -> sym.enclosingPackageClass.fullName,
164+
"symbol owners" -> sym.showExtendedLocation,
165+
)
166+
val info2 = formatExplain(List(
167+
"tree" -> tree.summaryString,
168+
"tree position" -> (if pos.exists then s"$path:${pos.line + 1}:${pos.column}" else s"$path:<unknown>"),
169+
"tree type" -> tree.typeOpt.show,
170+
) ::: symbolInfos ::: List(
171+
"call site" -> s"${site.showLocated} in ${site.enclosingPackageClass}"
172+
))
173+
val context_s = try
174+
s""" == Source file context for tree position ==
175+
|
176+
|${messageRendering.messageAndPos(Diagnostic.Error("", pos))}""".stripMargin
177+
catch case _: Exception => "<Cannot read source file>"
178+
s"""
179+
| $errorMessage
180+
|
181+
| An unhandled exception was thrown in the compiler. Please file a crash
182+
| report here: https://github.com/lampepfl/dotty/issues/new/choose
183+
|
184+
|$info1
185+
|
186+
|$info2
187+
|
188+
|$context_s""".stripMargin
189+
} catch case _: Throwable => errorMessage // don't introduce new errors trying to report errors, so swallow exceptions
132190
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.*
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(message: Any | Null)(using ctx: Context | Null): Nothing =
57+
val msg = String.valueOf(message).nn
58+
if ctx == null then
59+
throw AssertionError("assertion failed: " + msg)
60+
else inContext(ctx) {
61+
throw AssertionError("assertion failed: " + ctx.run.enrichErrorMessage(msg))
62+
}
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)