Skip to content

Commit f92d6f1

Browse files
authored
Enrich and finesse compiler crash reporting (#17031)
2 parents 424da9f + 099f9f2 commit f92d6f1

File tree

7 files changed

+115
-26
lines changed

7 files changed

+115
-26
lines changed

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

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

3131
protected def doCompile(compiler: Compiler, files: List[AbstractFile])(using Context): Reporter =
3232
if files.nonEmpty then
33+
var runOrNull = ctx.run
3334
try
3435
val run = compiler.newRun
36+
runOrNull = run
3537
run.compile(files)
3638
finish(compiler, run)
3739
catch
3840
case ex: FatalError =>
3941
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+
case ex: TypeError if !runOrNull.enrichedErrorMessage =>
43+
println(runOrNull.enrichErrorMessage(s"${ex.toMessage} while compiling ${files.map(_.path).mkString(", ")}"))
4244
throw ex
43-
case ex: Throwable =>
44-
println(s"$ex while compiling ${files.map(_.path).mkString(", ")}")
45+
case ex: Throwable if !runOrNull.enrichedErrorMessage =>
46+
println(runOrNull.enrichErrorMessage(s"Exception while compiling ${files.map(_.path).mkString(", ")}"))
4547
throw ex
4648
ctx.reporter
4749

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

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

176+
private var myEnrichedErrorMessage = false
177+
176178
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
179+
try compileSources(files.map(runContext.getSource(_)))
180+
catch case NonFatal(ex) if !this.enrichedErrorMessage =>
181+
val files1 = if units.isEmpty then files else units.map(_.source.file)
182+
report.echo(this.enrichErrorMessage(s"exception occurred while compiling ${files1.map(_.path)}"))
183+
throw ex
185184

186185
/** TODO: There's a fundamental design problem here: We assemble phases using `fusePhases`
187186
* when we first build the compiler. But we modify them with -Yskip, -Ystop
@@ -398,3 +397,16 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
398397
given runContext[Dummy_so_its_a_def]: Context = myCtx.nn
399398
assert(runContext.runId <= Periods.MaxPossibleRunId)
400399
}
400+
401+
object Run {
402+
extension (run: Run | Null)
403+
def enrichedErrorMessage: Boolean = if run == null then false else run.myEnrichedErrorMessage
404+
def enrichErrorMessage(errorMessage: String)(using Context): String =
405+
if run == null then
406+
report.enrichErrorMessage(errorMessage)
407+
else if !run.enrichedErrorMessage then
408+
run.myEnrichedErrorMessage = true
409+
report.enrichErrorMessage(errorMessage)
410+
else
411+
errorMessage
412+
}

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"... (cannot display due to ${ex.className} ${ex.getMessage}) ..."
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 ex: Throwable 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/report.scala

+61-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,64 @@ 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.
182+
| Please file a crash report here:
183+
| https://github.com/lampepfl/dotty/issues/new/choose
184+
|
185+
|$info1
186+
|
187+
|$info2
188+
|
189+
|$context_s""".stripMargin
190+
} catch case _: Throwable => errorMessage // don't introduce new errors trying to report errors, so swallow exceptions
132191
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

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package dotty.tools
2+
package dotc
3+
package core
4+
5+
import Contexts.*, Decorators.*, Denotations.*, SymDenotations.*, Symbols.*, Types.*
6+
import printing.Formatting.Show
7+
8+
import org.junit.Test
9+
import org.junit.Assert.*
10+
11+
class ShowDecoratorTest extends DottyTest:
12+
import ShowDecoratorTest.*
13+
14+
@Test def t1 = assertEquals("... (cannot display due to FooException boom) ...", Foo().tryToShow)
15+
end ShowDecoratorTest
16+
17+
object ShowDecoratorTest:
18+
import printing.*, Texts.*
19+
class FooException extends Exception("boom")
20+
case class Foo() extends Showable:
21+
def toText(printer: Printer): Text = throw new FooException

0 commit comments

Comments
 (0)