Skip to content

Commit 0955899

Browse files
bertlebeedwijnand
andcommitted
Improved crash handling
Co-authored-by: Dale Wijnand <[email protected]>
1 parent 13b8d7d commit 0955899

21 files changed

+239
-49
lines changed

compiler/src/dotty/tools/backend/jvm/BTypes.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ abstract class BTypes {
256256
*/
257257
final def maxValueType(other: BType): BType = {
258258

259-
def uncomparable: Nothing = throw new AssertionError(s"Cannot compute maxValueType: $this, $other")
259+
def uncomparable: Nothing = ctx.implode(s"Cannot compute maxValueType: $this, $other")
260260

261261
if (!other.isPrimitive && !other.isNothingType) uncomparable
262262

compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala

+10-10
Original file line numberDiff line numberDiff line change
@@ -2368,10 +2368,10 @@ class JSCodeGen()(using genCtx: Context) {
23682368
// Construct inline class definition
23692369

23702370
val jsClassCaptures = originalClassDef.jsClassCaptures.getOrElse {
2371-
throw new AssertionError(s"no class captures for anonymous JS class at $pos")
2371+
genCtx.implode(s"no class captures for anonymous JS class at $pos")
23722372
}
23732373
val js.JSConstructorDef(_, ctorParams, ctorRestParam, ctorBody) = constructor.getOrElse {
2374-
throw new AssertionError("No ctor found")
2374+
genCtx.implode("No ctor found")
23752375
}
23762376
assert(ctorParams.isEmpty && ctorRestParam.isEmpty,
23772377
s"non-empty constructor params for anonymous JS class at $pos")
@@ -2396,17 +2396,17 @@ class JSCodeGen()(using genCtx: Context) {
23962396

23972397
val memberDefinitions0 = instanceMembers.toList.map {
23982398
case fdef: js.FieldDef =>
2399-
throw new AssertionError("unexpected FieldDef")
2399+
genCtx.implode("unexpected FieldDef")
24002400

24012401
case fdef: js.JSFieldDef =>
24022402
implicit val pos = fdef.pos
24032403
js.Assign(js.JSSelect(selfRef, fdef.name), jstpe.zeroOf(fdef.ftpe))
24042404

24052405
case mdef: js.MethodDef =>
2406-
throw new AssertionError("unexpected MethodDef")
2406+
genCtx.implode("unexpected MethodDef")
24072407

24082408
case cdef: js.JSConstructorDef =>
2409-
throw new AssertionError("unexpected JSConstructorDef")
2409+
genCtx.implode("unexpected JSConstructorDef")
24102410

24112411
case mdef: js.JSMethodDef =>
24122412
implicit val pos = mdef.pos
@@ -2773,7 +2773,7 @@ class JSCodeGen()(using genCtx: Context) {
27732773
if (from == to || from == jstpe.NothingType) {
27742774
value
27752775
} else if (from == jstpe.BooleanType || to == jstpe.BooleanType) {
2776-
throw new AssertionError(s"Invalid genConversion from $from to $to")
2776+
genCtx.implode(s"Invalid genConversion from $from to $to")
27772777
} else {
27782778
def intValue = (from: @unchecked) match {
27792779
case jstpe.IntType => value
@@ -3134,7 +3134,7 @@ class JSCodeGen()(using genCtx: Context) {
31343134
case value :: Nil =>
31353135
genSelectSet(genExpr(jsName), value)
31363136
case _ =>
3137-
throw new AssertionError(s"property methods should have 0 or 1 non-varargs arguments at $pos")
3137+
genCtx.implode(s"property methods should have 0 or 1 non-varargs arguments at $pos")
31383138
}
31393139

31403140
case JSCallingConvention.BracketAccess =>
@@ -3144,7 +3144,7 @@ class JSCodeGen()(using genCtx: Context) {
31443144
case keyArg :: valueArg :: Nil =>
31453145
genSelectSet(keyArg, valueArg)
31463146
case _ =>
3147-
throw new AssertionError(s"@JSBracketAccess methods should have 1 or 2 non-varargs arguments at $pos")
3147+
genCtx.implode(s"@JSBracketAccess methods should have 1 or 2 non-varargs arguments at $pos")
31483148
}
31493149

31503150
case JSCallingConvention.BracketCall =>
@@ -3238,7 +3238,7 @@ class JSCodeGen()(using genCtx: Context) {
32383238
s"Trying to call the super constructor of Object in a non-native JS class at $pos")
32393239
genApplyMethod(genReceiver, sym, genScalaArgs)
32403240
} else if (sym.isClassConstructor) {
3241-
throw new AssertionError(
3241+
genCtx.implode(
32423242
s"calling a JS super constructor should have happened in genPrimaryJSClassCtor at $pos")
32433243
} else if (sym.owner.isNonNativeJSClass && !sym.isJSExposed) {
32443244
// Reroute to the static method
@@ -3358,7 +3358,7 @@ class JSCodeGen()(using genCtx: Context) {
33583358

33593359
clauses = clauses.reverse
33603360
val defaultClause = optDefaultClause.getOrElse {
3361-
throw new AssertionError("No elseClause in pattern match")
3361+
genCtx.implode("No elseClause in pattern match")
33623362
}
33633363

33643364
/* Builds a `js.Match`, but simplifies it to a `js.If` if there is only

compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) {
176176
js.TopLevelMethodExportDef(info.moduleID, methodDef)
177177

178178
case Property =>
179-
throw new AssertionError("found top-level exported property")
179+
ctx.implode("found top-level exported property")
180180

181181
case Field =>
182182
val sym = checkSingleField(tups)
@@ -222,7 +222,7 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) {
222222
js.JSFieldDef(flags, name, irTpe)
223223

224224
case kind =>
225-
throw new AssertionError(s"unexpected static export kind: $kind")
225+
ctx.implode(s"unexpected static export kind: $kind")
226226
}
227227
}).toList
228228
}
@@ -743,7 +743,7 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) {
743743
val modAccessor = outer.info.allMembers.find { denot =>
744744
denot.symbol.is(Module) && denot.name.unexpandedName == name
745745
}.getOrElse {
746-
throw new AssertionError(i"could not find module accessor for ${targetSym.fullName} at $pos")
746+
ctx.implode(i"could not find module accessor for ${targetSym.fullName} at $pos")
747747
}.symbol
748748

749749
val receiver = captures.head

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

+5-7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import dotty.tools.FatalError
44
import config.CompilerCommand
55
import core.Comments.{ContextDoc, ContextDocstrings}
66
import core.Contexts._
7+
import util.Implosion
78
import core.{MacroClassLoader, TypeError}
89
import dotty.tools.dotc.ast.Positioned
910
import dotty.tools.io.AbstractFile
@@ -35,14 +36,11 @@ class Driver {
3536
run.compile(files)
3637
finish(compiler, run)
3738
catch
38-
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-
throw ex
39+
case ex: Implosion =>
40+
// All handling related to the Implosion is done during creation, so we can swallow this
4341
case ex: Throwable =>
44-
println(s"$ex while compiling ${files.map(_.path).mkString(", ")}")
45-
throw ex
42+
ctx.lateImplode(ex) //this should never happen except in the case of `FatalError`s
43+
4644
ctx.reporter
4745

4846
protected def finish(compiler: Compiler, run: Run)(using Context): Unit =

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

+9-4
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,15 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
179179
compileSources(sources)
180180
catch
181181
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
185-
182+
try
183+
if units.nonEmpty then ctx.lateImplode(i"exception occurred while compiling $units%, %")
184+
else ctx.lateImplode(s"exception occurred while compiling ${files.map(_.name).mkString(", ")}")
185+
catch
186+
case _: Implosion =>
187+
188+
case _ : Implosion =>
189+
// All handling related to the Implosion is done during creation, so we can swallow this
190+
186191
/** TODO: There's a fundamental design problem here: We assemble phases using `fusePhases`
187192
* when we first build the compiler. But we modify them with -Yskip, -Ystop
188193
* on each run. That modification needs to either transform the tree structure,

compiler/src/dotty/tools/dotc/cc/CaptureSet.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,7 @@ object CaptureSet:
690690
upper.isAlwaysEmpty || upper.isConst && upper.elems.size == 1 && upper.elems.contains(r1)
691691
if variance > 0 || isExact then upper
692692
else if variance < 0 then CaptureSet.empty
693-
else assert(false, i"trying to add $upper from $r via ${tm.getClass} in a non-variant setting")
693+
else ctx.implode( i"trying to add $upper from $r via ${tm.getClass} in a non-variant setting")
694694

695695
/** Apply `f` to each element in `xs`, and join result sets with `++` */
696696
def mapRefs(xs: Refs, f: CaptureRef => CaptureSet)(using Context): CaptureSet =

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

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import ast.untpd
88

99
/** Extension methods for contexts where we want to keep the ctx.<methodName> syntax */
1010
object ContextOps:
11-
1211
extension (ctx: Context)
1312

1413
/** Enter symbol into current class, if current class is owner of current context,

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

+32-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import Scopes._
1313
import Uniques._
1414
import ast.Trees._
1515
import ast.untpd
16-
import util.{NoSource, SimpleIdentityMap, SourceFile, HashSet, ReusableInstance}
16+
import util.{NoSource, SimpleIdentityMap, SourceFile, HashSet, ReusableInstance, Implosion}
1717
import typer.{Implicits, ImportInfo, SearchHistory, SearchRoot, TypeAssigner, Typer, Nullables}
1818
import inlines.Inliner
1919
import Nullables._
@@ -454,6 +454,37 @@ object Contexts {
454454
util.Stats.record("Context.fresh")
455455
FreshContext(base).init(outer, this).setTyperState(this.typerState)
456456

457+
/** Crash the compiler with a `Throwable` in a controlled way, reporting useful info
458+
* and asking users to submit a crash report.
459+
* This helps users by providing information they can use to work around the crash
460+
* and helps compiler maintainers by capturing important information about the crash.
461+
* With the exception of `ControlThrowable`s, this method should be used instead of
462+
* throwing exceptions whenever a context is available.
463+
*
464+
* instead of:
465+
* `throw Exception("foo")`
466+
* use:
467+
* `ctx.implode(Exception("foo"))`
468+
*/
469+
inline def implode(inline cause: Throwable): Nothing =
470+
Implosion(cause)(using thiscontext)
471+
472+
/**
473+
* Crash the compiler with a message in a controlled way
474+
*/
475+
inline def implode(inline msg: Any): Nothing =
476+
implode(AssertionError(msg))
477+
478+
/**
479+
* A fallback for when an exception has been thrown without using `implode`
480+
* This will capture context from this context which may be a parent of the context the actual exception was thrown in.
481+
*/
482+
def lateImplode(cause: Throwable): Nothing =
483+
Implosion(Exception("Context was thrown away, this crash report may not be accurate", (cause)))(using ctx)
484+
485+
def lateImplode(msg: Any): Nothing =
486+
lateImplode(AssertionError(msg))
487+
457488
final def withOwner(owner: Symbol): Context =
458489
if (owner ne this.owner) fresh.setOwner(owner) else this
459490

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

+6-3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import ast.{tpd, untpd}
2222
import scala.annotation.internal.sharable
2323
import scala.util.control.NonFatal
2424

25+
26+
2527
object Phases {
2628

2729
inline def phaseOf(id: PhaseId)(using Context): Phase =
@@ -322,9 +324,10 @@ object Phases {
322324
units.map { unit =>
323325
val unitCtx = ctx.fresh.setPhase(this.start).setCompilationUnit(unit).withRootImports
324326
try run(using unitCtx)
325-
catch case ex: Throwable =>
326-
println(s"$ex while running $phaseName on $unit")
327-
throw ex
327+
catch
328+
case NonFatal(ex) =>
329+
unitCtx.lateImplode(Exception(i"$ex while running $phaseName on $unit", ex))
330+
328331
unitCtx.compilationUnit
329332
}
330333

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ object TypeOps:
250250
}
251251

252252
def mergeRefinedOrApplied(tp1: Type, tp2: Type): Type = {
253-
def fail = throw new AssertionError(i"Failure to join alternatives $tp1 and $tp2")
253+
def fail = ctx.implode(i"Failure to join alternatives $tp1 and $tp2")
254254
def fallback = tp2 match
255255
case AndType(tp21, tp22) =>
256256
mergeRefinedOrApplied(tp1, tp21) & mergeRefinedOrApplied(tp1, tp22)

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -3088,8 +3088,8 @@ object Types {
30883088

30893089
override def underlying(using Context): Type = parent
30903090

3091-
private def badInst =
3092-
throw new AssertionError(s"bad instantiation: $this")
3091+
private def badInst(using Context) =
3092+
ctx.implode(s"bad instantiation: $this")
30933093

30943094
def checkInst(using Context): this.type = this // debug hook
30953095

compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,7 @@ class ClassfileParser(
863863
// create a new class member for immediate inner classes
864864
if entry.outer.name == currentClassName then
865865
val file = ctx.platform.classPath.findClassFile(entry.externalName.toString) getOrElse {
866-
throw new AssertionError(entry.externalName)
866+
ctx.implode(entry.externalName)
867867
}
868868
enterClassAndModule(entry, file, entry.jflags)
869869
}

compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ trait MessageRendering {
280280
sb.toString
281281
}
282282

283-
private def hl(str: String)(using Context, Level): String =
283+
private def hl(str: String)(using Context, Level): String =
284284
summon[Level].value match
285285
case interfaces.Diagnostic.ERROR => Red(str).show
286286
case interfaces.Diagnostic.WARNING => Yellow(str).show

compiler/src/dotty/tools/dotc/transform/Memoize.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase =>
6565
override def checkPostCondition(tree: Tree)(using Context): Unit = {
6666
def errorLackImplementation(t: Tree) = {
6767
val definingPhase = phaseOf(t.symbol.initial.validFor.firstPhaseId)
68-
throw new AssertionError(
68+
ctx.implode(
6969
i"Non-deferred definition introduced by $definingPhase lacks implementation: $t")
7070
}
7171
tree match {

compiler/src/dotty/tools/dotc/transform/Recheck.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ abstract class Recheck extends Phase, SymTransformer:
285285
constFold(tree, instantiate(fntpe, argTypes, tree.fun.symbol))
286286
//.showing(i"typed app $tree : $fntpe with ${tree.args}%, % : $argTypes%, % = $result")
287287
case tp =>
288-
assert(false, i"unexpected type of ${tree.fun}: $funtpe")
288+
ctx.implode(i"unexpected type of ${tree.fun}: $funtpe")
289289

290290
def recheckTypeApply(tree: TypeApply, pt: Type)(using Context): Type =
291291
recheck(tree.fun).widen match

compiler/src/dotty/tools/dotc/transform/TypeUtils.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ object TypeUtils {
8888
def toNestedPairs(using Context): Type =
8989
tupleElementTypes match
9090
case Some(types) => TypeOps.nestedPairs(types)
91-
case None => throw new AssertionError("not a tuple")
91+
case None => ctx.implode("not a tuple")
9292

9393
def refinedWith(name: Name, info: Type)(using Context) = RefinedType(self, name, info)
9494

compiler/src/dotty/tools/dotc/transform/patmat/Space.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,7 @@ class SpaceEngine(using Context) extends SpaceLogic {
752752

753753
/** Whether the counterexample is satisfiable. The space is flattened and non-empty. */
754754
def satisfiable(sp: Space): Boolean = {
755-
def impossible: Nothing = throw new AssertionError("`satisfiable` only accepts flattened space.")
755+
def impossible: Nothing = ctx.implode("`satisfiable` only accepts flattened space.")
756756

757757
def genConstraint(space: Space): List[(Type, Type)] = space match {
758758
case Prod(tp, unappTp, ss) =>

compiler/src/dotty/tools/dotc/transform/sjs/PrepJSExports.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ object PrepJSExports {
248248
}
249249

250250
case _: ExportDestination.TopLevel =>
251-
throw new AssertionError(
251+
ctx.implode(
252252
em"Found a top-level export without an explicit name at ${exportPos.sourcePos}")
253253

254254
case ExportDestination.Static =>

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

+5-4
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,11 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking
125125
override def typedUnadapted(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree =
126126
try super.typedUnadapted(tree, pt, locked)
127127
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
128+
case NonFatal(ex) if ctx.phase != Phases.typerPhase && ctx.phase != Phases.inliningPhase =>
129+
ctx.lateImplode(
130+
Exception(i"exception while typing $tree of class ${tree.getClass} # ${tree.uniqueId}", ex)
131+
)
132+
132133
}
133134

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

0 commit comments

Comments
 (0)