Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved crash handling #16593

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft

Conversation

robmwalsh
Copy link

@robmwalsh robmwalsh commented Dec 28, 2022

following on from discussion here https://discord.com/channels/632150470000902164/632628489719382036/1052965390382280714

adds an implode method to dump state from the context and a better assert which makes use of this for some hope at figuring out what's causing the crashes. assert is source compatible with Predef.assert so swapping can be as easy as importing it.

cc @smarter

example (updated) compiler output for #16207 (somewhat mangled by markdown) :

[info] running (fork) dotty.tools.dotc.Main -classpath /home/rob/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.13.10/scala-library-2.13.10.jar:/home/rob/code/dotty/library/../out/bootstrap/scala3-library-bootstrapped/scala-3.3.0-RC1-bin-SNAPSHOT-nonbootstrapped/scala3-library_3-3.3.0-RC1-bin-SNAPSHOT.jar -d local/out local/i16207.scala
-- Error: local/i16207.scala:8:4 -----------------------------------------------
8 |val headers: List[String] = labelsOf[C].toList.map(_.toString)
  |^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  |  An unhandled exception was thrown in the compiler. Please file a crash
  |  report here: https://github.com/lampepfl/dotty/issues/new/choose
  |  with the following information:
  |
  |java.lang.AssertionError: assertion failed: no owner from  <none>/ <none> in {
  |  val p$proxy1:
  |    
  |      scala.deriving.Mirror.Product{
  |        type MirroredMonoType = C; type MirroredType = C;
  |          type MirroredLabel = ("C" : String);
  |          type MirroredElemTypes = (Int, Int);
  |          type MirroredElemLabels = (("date" : String), ("time" : String))
  |      }
  |    
  |   =
  |    C.$asInstanceOf[
  |      
  |        scala.deriving.Mirror.Product{
  |          type MirroredMonoType = C; type MirroredType = C;
  |            type MirroredLabel = ("C" : String);
  |            type MirroredElemTypes = (Int, Int);
  |            type MirroredElemLabels = (("date" : String), ("time" : String))
  |        }
  |      
  |    ]
  |  {
  |    val Tuple_this: Tuple =
  |      (
  |        {
  |          val res: Tuple =
  |            {
  |              val x$1: ("date" : String) = "date"
  |              {
  |                val Tuple_this: ("time" : String) *: EmptyTuple.type =
  |                  {
  |                    val res: Tuple =
  |                      {
  |                        val x$1: ("time" : String) = "time"
  |                        {
  |                          val Tuple_this: EmptyTuple.type = EmptyTuple
  |                          runtime.Tuples.cons("time", Tuple_this).asInstanceOf[
  |                            ("time" : String) *: EmptyTuple.type]:
  |                            ("time" : String) *: EmptyTuple.type
  |                        }:Any *: Tuple
  |                      }
  |                    res.asInstanceOf[("time" : String) *: EmptyTuple.type]
  |                  }:("time" : String) *: EmptyTuple.type
  |                runtime.Tuples.cons("date", Tuple_this).asInstanceOf[
  |                  (("date" : String), ("time" : String))]:
  |                  (("date" : String), ("time" : String))
  |              }:Any *: Tuple
  |            }
  |          res.asInstanceOf[p$proxy1.MirroredElemLabels]
  |        }:p$proxy1.MirroredElemLabels
  |      :Tuple)
  |    (
  |      Tuple_this.productIterator.toList.asInstanceOf[
  |        scala.collection.immutable.List[Tuple.Union[(Tuple_this : Product)]]]
  |    :scala.collection.immutable.List[Tuple.Union[(Tuple_this : Product)]])
  |  }
  |}.<none>
  |
  |java.lang.AssertionError: assertion failed: no owner from  <none>/ <none> in {
  |  val p$proxy1:
  |    
  |      scala.deriving.Mirror.Product{
  |        type MirroredMonoType = C; type MirroredType = C;
  |          type MirroredLabel = ("C" : String);
  |          type MirroredElemTypes = (Int, Int);
  |          type MirroredElemLabels = (("date" : String), ("time" : String))
  |      }
  |    
  |   =
  |    C.$asInstanceOf[
  |      
  |        scala.deriving.Mirror.Product{
  |          type MirroredMonoType = C; type MirroredType = C;
  |            type MirroredLabel = ("C" : String);
  |            type MirroredElemTypes = (Int, Int);
  |            type MirroredElemLabels = (("date" : String), ("time" : String))
  |        }
  |      
  |    ]
  |  {
  |    val Tuple_this: Tuple =
  |      (
  |        {
  |          val res: Tuple =
  |            {
  |              val x$1: ("date" : String) = "date"
  |              {
  |                val Tuple_this: ("time" : String) *: EmptyTuple.type =
  |                  {
  |                    val res: Tuple =
  |                      {
  |                        val x$1: ("time" : String) = "time"
  |                        {
  |                          val Tuple_this: EmptyTuple.type = EmptyTuple
  |                          runtime.Tuples.cons("time", Tuple_this).asInstanceOf[
  |                            ("time" : String) *: EmptyTuple.type]:
  |                            ("time" : String) *: EmptyTuple.type
  |                        }:Any *: Tuple
  |                      }
  |                    res.asInstanceOf[("time" : String) *: EmptyTuple.type]
  |                  }:("time" : String) *: EmptyTuple.type
  |                runtime.Tuples.cons("date", Tuple_this).asInstanceOf[
  |                  (("date" : String), ("time" : String))]:
  |                  (("date" : String), ("time" : String))
  |              }:Any *: Tuple
  |            }
  |          res.asInstanceOf[p$proxy1.MirroredElemLabels]
  |        }:p$proxy1.MirroredElemLabels
  |      :Tuple)
  |    (
  |      Tuple_this.productIterator.toList.asInstanceOf[
  |        scala.collection.immutable.List[Tuple.Union[(Tuple_this : Product)]]]
  |    :scala.collection.immutable.List[Tuple.Union[(Tuple_this : Product)]])
  |  }
  |}.<none>
  |     at dotty.tools.package$.throwAssertionError(package.scala:66)
  |     at dotty.tools.package$.inline$throwAssertionError(package.scala:64)
  |     at dotty.tools.dotc.transform.Erasure$Typer.typedSelect(Erasure.scala:715)
  |     at dotty.tools.dotc.typer.Typer.typedNamed$1(Typer.scala:2887)
  |     at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2980)
  |     at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:127)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3048)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3052)
  |     at dotty.tools.dotc.typer.Typer.typedExpr(Typer.scala:3168)
  |     at dotty.tools.dotc.transform.Erasure$Typer.typedTypeApply(Erasure.scala:811)
  |     at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:2937)
  |     at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2981)
  |     at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:127)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3048)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3052)
  |     at dotty.tools.dotc.typer.Typer.typedExpr(Typer.scala:3168)
  |     at dotty.tools.dotc.transform.Erasure$Typer.typedApply(Erasure.scala:830)
  |     at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:2918)
  |     at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2981)
  |     at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:127)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3048)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3052)
  |     at dotty.tools.dotc.typer.Typer.typedExpr(Typer.scala:3168)
  |     at dotty.tools.dotc.typer.Typer.$anonfun$48(Typer.scala:2372)
  |     at dotty.tools.dotc.inlines.PrepareInlineable$.dropInlineIfError(PrepareInlineable.scala:249)
  |     at dotty.tools.dotc.typer.Typer.typedDefDef(Typer.scala:2372)
  |     at dotty.tools.dotc.transform.Erasure$Typer.typedDefDef(Erasure.scala:947)
  |     at dotty.tools.dotc.typer.Typer.typedNamed$1(Typer.scala:2894)
  |     at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2980)
  |     at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:127)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3048)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3052)
  |     at dotty.tools.dotc.typer.Typer.traverse$1(Typer.scala:3074)
  |     at dotty.tools.dotc.typer.Typer.typedStats(Typer.scala:3124)
  |     at dotty.tools.dotc.transform.Erasure$Typer.typedStats(Erasure.scala:1046)
  |     at dotty.tools.dotc.typer.Typer.typedClassDef(Typer.scala:2552)
  |     at dotty.tools.dotc.transform.Erasure$Typer.typedClassDef(Erasure.scala:1035)
  |     at dotty.tools.dotc.typer.Typer.typedTypeOrClassDef$1(Typer.scala:2906)
  |     at dotty.tools.dotc.typer.Typer.typedNamed$1(Typer.scala:2910)
  |     at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2980)
  |     at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:127)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3048)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3052)
  |     at dotty.tools.dotc.typer.Typer.traverse$1(Typer.scala:3074)
  |     at dotty.tools.dotc.typer.Typer.typedStats(Typer.scala:3124)
  |     at dotty.tools.dotc.transform.Erasure$Typer.typedStats(Erasure.scala:1046)
  |     at dotty.tools.dotc.typer.Typer.typedPackageDef(Typer.scala:2682)
  |     at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:2951)
  |     at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2981)
  |     at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:127)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3048)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3052)
  |     at dotty.tools.dotc.typer.Typer.typedExpr(Typer.scala:3168)
  |     at dotty.tools.dotc.transform.Erasure.run(Erasure.scala:145)
  |     at dotty.tools.dotc.core.Phases$Phase.runOn$$anonfun$1(Phases.scala:327)
  |     at scala.collection.immutable.List.map(List.scala:246)
  |     at dotty.tools.dotc.core.Phases$Phase.runOn(Phases.scala:332)
  |     at dotty.tools.dotc.Run.runPhases$1$$anonfun$1(Run.scala:246)
  |     at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
  |     at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
  |     at scala.collection.ArrayOps$.foreach$extension(ArrayOps.scala:1321)
  |     at dotty.tools.dotc.Run.runPhases$1(Run.scala:262)
  |     at dotty.tools.dotc.Run.compileUnits$$anonfun$1(Run.scala:270)
  |     at dotty.tools.dotc.Run.compileUnits$$anonfun$adapted$1(Run.scala:279)
  |     at dotty.tools.dotc.util.Stats$.maybeMonitored(Stats.scala:68)
  |     at dotty.tools.dotc.Run.compileUnits(Run.scala:279)
  |     at dotty.tools.dotc.Run.compileSources(Run.scala:197)
  |     at dotty.tools.dotc.Run.compile(Run.scala:179)
  |     at dotty.tools.dotc.Driver.doCompile(Driver.scala:36)
  |     at dotty.tools.dotc.Driver.process(Driver.scala:193)
  |     at dotty.tools.dotc.Driver.process(Driver.scala:161)
  |     at dotty.tools.dotc.Driver.process(Driver.scala:173)
  |     at dotty.tools.dotc.Driver.main(Driver.scala:203)
  |     at dotty.tools.dotc.Main.main(Main.scala)
  |
  |
  |     while compiling: local/i16207.scala
  |        during phase: erasure
  |                mode: Mode(ImplicitsEnabled)
  |     library version: version 2.13.10
  |    compiler version: version 3.3.0-RC1-bin-SNAPSHOT-nonbootstrapped-git-411e018
  |            settings: -classpath /home/rob/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.13.10/scala-library-2.13.10.jar:/home/rob/code/dotty/library/../out/bootstrap/scala3-library-bootstrapped/scala-3.3.0-RC1-bin-SNAPSHOT-nonbootstrapped/scala3-library_3-3.3.0-RC1-bin-SNAPSHOT.jar -d local/out
  |
  |                tree: headers
  |       tree position: local/i16207.scala:8:4
  |           tree type: (headers : (): scala.collection.immutable.List)
  |              symbol: getter headers
  |   symbol definition: def headers(): scala.collection.immutable.List (a Symbol)
  |      symbol package: <empty>
  |       symbol owners:  in package object i16207$package
  |           call site: getter headers in module class <empty>
  |
  |
1 error found
[error] Nonzero exit code returned from runner: 1
[error] (scala3-compiler / Compile / runMain) Nonzero exit code returned from runner: 1
[error] Total time: 7 s, completed Jan 20, 2023, 7:23:20 PM

@robmwalsh
Copy link
Author

I've signed the CLA, please recheck

@robmwalsh
Copy link
Author

Some other notes:

  1. The compiler often uses exceptions for control flow, e.g. to suspend compilation of a unit. There's not any way to determine if an exception is indeed exceptional or an expected state.
  2. The current approach of catching all NonFatal and rethrowing makes it harder to find the root cause of an exception and can result (particularly for recursive methods) in an explosion of output (see https://discord.com/channels/632150470000902164/632628489719382036/1053386568066408549)

To solve these issues, I propose some new rules:

  1. all control flow exceptions should extend a marker trait ControlFlow
  2. only ControlFlow exceptions should be thrown - other exceptions should be handled by context implosion
  3. using NonFatal to catch exceptions should be limited to as far out in the compiler as possible, probably somewhere in Run?
  4. usage of NonFatal should otherwise be replaced with something that serves a similar purpose but never catches a ControlFlow exception
  5. catching and rethrowing should never happen - either recover (e.g. for a ControlFlow exception) or implode the context.

@odersky
Copy link
Contributor

odersky commented Jan 2, 2023

I actually think that the current exception handling in the compiler is mostly fine. Essentially the rules are:

  • If this is a documented exception class in the compiler, it's used for control flow.
  • AssertionErrors and the like are always exceptional.
  • The level of reporting for determining problems is actually fine; if anything I'd like to see more stacktraces.

I like the idea of documenting expected exceptions with a ControlFlow base trait.

I also like the idea to fine-tune what gets reported to the user when they note an exception. We should prefix that with a friendly invitation to submit a bug report. So I'd like to keep that part of the proposal. Truncating exception trace is not helpful IMO. If an exception occurs we want to see all the info that's available in the bug report.

But more sweeping changes might cause major friction and loss of time for the people fixing bugs. There are many established processes that help us fixing issues quickly. I am skeptical that introducing more ceremony like Implosion would help.

@ckipp01
Copy link
Member

ckipp01 commented Jan 3, 2023

I actually think that the current exception handling in the compiler is mostly fine

Just to throw one more outsiders opinion in here. I think this is true for those working on the compiler on a regular basis. However, I've experience this first hand that when an assertion fails in the compiler, I literally have no idea what it means half the time. It's incredibly hard to make sense of the huge jumbled tree that you get without detailed knowledge. UX improvements in this area could really really improve this. I encourage you to look at this from the perspective of someone that doesn't have literally dozens of years working on compilers and dealing with trees.

Truncating exception trace is not helpful IMO. If an exception occurs we want to see all the info that's available in the bug report.

This totally makes sense, we should probably for sure keep an easy way to get the entire output for reports.

But more sweeping changes might cause major friction and loss of time for the people fixing bugs.

True, but having a much nicer output when things blow up with a message that's easy to digest may encourage users to more often report these things, which could also help those working on fixing the bugs tackle them.

@robmwalsh
Copy link
Author

robmwalsh commented Jan 3, 2023

  • The level of reporting for determining problems is actually fine; if anything I'd like to see more stacktraces.

I started working on this because, like @ckipp01, I had no idea what the cause might be. The crash report asks for a minimized example - when you don't even know how to get your code compiling again, that's a hard ask.

Truncating exception trace is not helpful IMO. If an exception occurs we want to see all the info that's available in the bug report.

I agree that more information is generally better; however many stack traces (and sometimes the trees that get printed with them) are so large that it gets lost (as in scrolls off the top of my terminal). The bottom 100 lines of the stack trace are useless if you miss even a few rows at the top. A way around this would be to keep the truncated stack trace to display to the user (along with other helpful info) like tree position etc., then dump as much info as possible into a zip file which the user can review for more information or submit with the bug report. I think putting the sources in a 2nd zip file may be helpful, so users can choose to submit sources or not depending on the sensitivity

But more sweeping changes might cause major friction and loss of time for the people fixing bugs. There are many established processes that help us fixing issues quickly. I am skeptical that introducing more ceremony like Implosion would help.

The amount of ceremony involved is minimal.
For assertions, it's an import. The assertion definition triggers context implosion if the predicate isn't satisfied. With judicious use of some exports, even this could be eliminated. I can improve the definition so it reverts to the current assert if no Context is in scope, so you wouldn't even have to think about which one to use.
I can also reuse the message from the Throwable for other (non-control flow) errors, so instead of throw new IllegalStateException("Bad!"), it'd be ctx.implode(new IllegalStateException("Bad!"). implode still throws, so there's no more error handling to deal with. The key difference is that it grabs state from the context before throwing.

This is important because of the way that Contexts work - if you throw, you blow away all the stack frames between where you threw and where you catch - along with whatever contexts exist within them. This is exacerbated because a lot of care has (rightly) been taken to ensure references to contexts don't survive. This approach is different to scala 2, where a lot more state survives when an exception is thrown. Implosion guarantees that the most up-to-date context information is caught. That can only be a good thing for the compiler team trying to fix bugs and provides a much better experience for people who don't have intimate knowledge of the compiler.

@smarter
Copy link
Member

smarter commented Jan 3, 2023

I agree with Martin that we should never truncate stacktraces, they're precious information and they rarely get very big (unless we get a stack overflow, which we try to manage with handleRecursive).
On the other hand, I agree that the tree printing is worth cleaning up: the main problem with the current logic is that it prints the current tree, then rethrow the exception to the outer recursive call, which prints the outer tree, then rethrow the exception, etc. I'm happy to see that this PR fixes that and add contextual information to figure out where the crash happened.
I haven't looked too much into it but implode seems fine to me. If we look at the corresponding Scala 2 logic, it avoids the ceremony by doing a catch/log/rethrow at the level of the compilation unit:
https://github.com/scala/scala/blob/18ce8d410b57f30f4161896b49b03d2d3e7b0401/src/compiler/scala/tools/nsc/ast/Trees.scala#L183-L188
where supplementErrorMessage call supplementTyperState:
https://github.com/scala/scala/blob/18ce8d410b57f30f4161896b49b03d2d3e7b0401/src/compiler/scala/tools/nsc/Global.scala#L1069-L1112
This relies on lastTreeToTyper which has no equivalent in Scala 3, and as @robmwalsh noted, we'd also need the corresponding Context to be able to pretty-print the type of the tree correctly.


/** Extension methods for contexts where we want to keep the ctx.<methodName> syntax */
object ContextOps:

case class Implosion(ex: Throwable) extends Exception(ex)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Implosion should be an Error, although that won't be enough to pass through the compiler cleanly since there's many places where we catch all Throwable or use the NonFatal extractor

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Error probably makes sense. Per previous comment I think the use of Throwable & the NonFatal extractor should be replaced with something specific to the compiler which won't catch ControlFlow exceptions, so this wouldn't be a problem

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to ControlThrowable instead of Error to avoid being caught by existing NonFatal extractors

@som-snytt
Copy link
Contributor

A "final" production compiler could dump to a file. The nice user message shows the path to the file to submit the bug report. That's much nicer than cut/paste from terminal. (Though I tire of hotspot crash files accumulating.) Any other development version dumps to stderr as usual, or also under -verbose or -Xdev etc.

Dotty needs -Yimports with a custom predef to avoid ceremony around its custom assert.

Someone recently asked why Scala 2 doesn't do better on crashes, meaning exits not initiated on abort, which has the nice supplementary message. My point is that graceful exit is a frequent issue.

I like that "implode" suggests "controlled implosion", but it also sounds jokey to me. I think "abort" can be avoided. Maybe "exit" is sufficient to say we are exiting instead of completing normally. In a cinema, there is no distinction between an emergency exit and a normal one. If I am exiting and the movie hasn't finished, it must be an emergency.

There is StackTraceOps for truncation, which does not omit suppressed exceptions. I bet users would still like to see a short stack, even if the full debug output is sent to a file.

An alternative to log and throw is to throw and catch in main and then log. Someone added a SystemExit throwable to Scala 2 for this purpose. Dotty needs a custom NonFatal, or does it already have one.

@robmwalsh
Copy link
Author

very related:

http4s/http4s#6899 (comment)

@odersky
Copy link
Contributor

odersky commented Jan 7, 2023

I still don't see the value of Implosion in my mind this will just make it harder to work with exception traces, which is something that compiler maintainers do every day.

@robmwalsh
Copy link
Author

I like that "implode" suggests "controlled implosion", but it also sounds jokey to me. I think "abort" can be avoided. Maybe "exit" is sufficient to say we are exiting instead of completing normally. In a cinema, there is no distinction between an emergency exit and a normal one. If I am exiting and the movie hasn't finished, it must be an emergency.

Perhaps the movie is just terrible :) I walked out of Man on Fire because it was terrible - from the ratings others disagreed, but I digress - implode wasn't intended to be jokey, though I wouldn't have a problem if it were. Fun is important too! I picked it because abort is already used, exit IMO isn't clear enough that it's a problem (as you said there's no distinction between an emergency exit and a normal one, and an error is a normal way to exit). Implode implies deliberate, safe, catastrophic, complete, and most importantly unstoppable (there's absolutely no expectation that it will be caught and recovered from). It's not perfect, because it also implies planned (I guess it is planned as a contingency, but that's probably a stretch) I'm happy to use a different name if most people think we should, but I think this is the best name atm.

There is StackTraceOps for truncation, which does not omit suppressed exceptions. I bet users would still like to see a short stack, even if the full debug output is sent to a file.

Thanks - I'll look into it.

An alternative to log and throw is to throw and catch in main and then log. Someone added a SystemExit throwable to Scala 2 for this purpose. Dotty needs a custom NonFatal, or does it already have one.

I've suggested a custom NonFatal - I'll add it to my next update if nobody can point me in the direction of an existing one.

@robmwalsh
Copy link
Author

I still don't see the value of Implosion in my mind this will just make it harder to work with exception traces, which is something that compiler maintainers do every day.

It's not just about maintainers though, people have to actually use the compiler as well, and shouldn't be expected to deal with or have any understanding of compiler internals. Asking for a minimized reproduction is a big disincentive to even report crashes (see the http4s comment above). I honestly believe these compiler crashes are hurting the adoption of scala 3.

I recently started a greenfields project in scala 3, very excited to use all these great new features and honestly, it's the single biggest productivity mistake I've made so far. I didn't want to start a brand new project in 2.13 because 2.x is something close to EOL, but after using in in anger for 3 months, my opinion that scala 3 isn't ready for serious use at this stage.

That means there are currently no good options for a new scala project and very little incentive for existing projects to upgrade. Scala is in no man's land at the moment, and if these issues don't improve soon, it may seriously damage scala's usage.

In any case, if I make the changes I mentioned above, you should have far more information to work with, not less, because I can stuff as much state as you like into a zip file, and can even get it to print full stack traces and whatever other information you'd care for if you run the compiler with some verbose option (I assume one exists but I don't generally want to know anything other than what errors I need to fix).

@odersky
Copy link
Contributor

odersky commented Jan 7, 2023

If you want to stabilize Scala 3 there are better options than make existing compiler maintainers drop into a productivity hole because you change the infrastructure they are used to work with.

  • Minimize problems and file bug reports. That should be priority #1. Have you filed any issues so far?
  • Try to come up with bug fixes yourself.

Both of these are hard, but both of these have definite positive impact.

EDIT: I see now that you did file three issues that are quite actionable. I was using the wrong search term so I did not see them before. Thank you for that! Hopefully someone will have a look at them soon. I am still very sceptical about changing error output of the compiler. There's a lot of implicit knowledge going on that could be made useless, and we don't want that.

@robmwalsh
Copy link
Author

If you want to stabilize Scala 3 there are better options than make existing compiler maintainers drop into a productivity hole because you change the infrastructure they are used to work with.

I really don't see how this proposal will significantly change either your infrastructure or workflows for solving problems. You'll still have all the information you currently have plus significantly more to help you nail down problems. I'm planning on working on this some more tomorrow, perhaps once I push some more updates you'll feel more comfortable with it. So far everyone else who has commented seems generally supportive of this change.

  • Minimize problems and file bug reports. That should be priority #1. Have you filed any issues so far?

As you note, in your edit, yes I have, but I've also had many crashes where I have no idea where to even start minimization, and these haven't been reported. Frequently I just don't have the time to even consider it. My new workflow for a crash is to commit in a new branch so I have a crash to come back to if I find the time, then just undo a bunch of changes or go back to my last commit and try a different approach.

  • Try to come up with bug fixes yourself.

You've been working on dotty for many years now and have intricate knowledge of the compiler, in addition to decades of experience working on compilers in general. I don't think you realise how opaque and generally unapproachable the dotty codebase is to an outsider. I've read all the docs I can find and asked a bunch of questions on discord (big thanks to everyone there and especially @SethTisue for their help), and I'm still just scratching the surface. I'm still a long way from being able to track down and fix a bug. I happen to have a burning desire to know how just about everything under the sun works, but most don't share that interest. If I'm struggling, I suspect many may not even get as far as I have.

In any case, not everyone wants to become dotty contributor, and nor should they need to in order to use scala. There are many prolific OS contributors that could contribute here, but that would take away from their contributions elsewhere. Others are already pulling long hours at companies trying to deliver their own products.

Both of these are hard, but both of these have a definite positive impact.

This PR is (the start of) my attempt at making both of these options significantly easier for users.

EDIT: I see now that you did file three issues that are quite actionable. I was using the wrong search term so I did not see them before. Thank you for that! Hopefully someone will have a look at them soon.

One of those issues is from April. There's little incentive to spend time minimising and reporting errors if they don't get fixed. Someone that shall remain nameless unless they wish to identify themselves said to me fairly recently that "reporting compiler errors without a PR to fix may as well be sending them to /dev/null". This is obviously an exaggeration, but hopefully, you can see the grain of truth in the comment that might cause this person to say that.

I am still very sceptical about changing error output of the compiler. There's a lot of implicit knowledge going on that could be made useless, and we don't want that.

I still don't think my proposed changes will fundamentally change anything here, but of course, I have no idea what this implicit knowledge is and no way of finding out. Let's revisit this once I've made some more changes, and perhaps you can give me some concrete examples of how the changes make things harder for maintainers and perhaps I can address some of those issues. I really want to see Scala 3 succeed, but for it to succeed, it needs to be easy for users (including when things go wrong) as well as maintainers.

@SethTisue
Copy link
Member

I really hope we can find some way forward here, as I strongly agree that the problem here for end users is real.

I've seen it over and over again in various chat rooms, forums, and bug trackers that actual Scala 3 users are hitting compiler crashes and the compiler is giving them almost nothing useful to help them and encourage them to isolate, minimize, and report the problem so it can be fixed.

http4s/http4s#6899 (comment) is a perfect example, and I'm sorry to report that it isn't an outlier — it's something I'm seeing happen over and over again out in the community. We should do our best to find some way to meet our users at least halfway on this. It will help them help us and everyone will benefit.

@robmwalsh
Copy link
Author

robmwalsh commented Jan 15, 2023

by no means finished, but I've tried to address some feedback and incorporated a lot of @dwijnand's work from #16685 (thanks for your work on this).

I haven't got to writing files yet so at this stage displayed stack traces are not truncated - I would like to do this once they're saved using StackTraceOps as @som-snytt suggested.

Dotty needs a custom NonFatal, or does it already have one.

I like the idea of documenting expected exceptions with a ControlFlow base trait.

I've used ControlThrowable so that current usages of NonFatal mostly don't need to be changed. Happy to go with a separate ControlFlow base trait and a custom NonFatal if that would be preferred.

I've also added a "black box" to the enrichErrorMessage method because swallowing an exception here would end up worse than the current situation since we'd lose the stack trace. It'll look horrible, but it's something to work with at least!

I'm getting a couple of compiler errors, and I can't see how they relate to anything I've done. If anybody has any suggestions on fixing this it'd be appreciated :)

[info] compiling 534 Scala sources and 6 Java sources to /home/rob/code/dotty/compiler/target/scala-3.2.2-RC2/classes ...
[error] -- [E007] Type Mismatch Error: /home/rob/code/dotty/compiler/src/dotty/tools/dotc/reporting/messages.scala:1325:11 
[error] 1325 |      i"""$howVisible$qualifier by ${whereFound.importInfo}"""
[error]      |           ^^^^^^^^^^
[error]      |           Found:    (howVisible : Matchable)
[error]      |           Required: dotty.tools.dotc.printing.Formatting.Shown
[error]      |
[error]      | longer explanation available when compiling with `-explain`
[error] -- [E007] Type Mismatch Error: /home/rob/code/dotty/compiler/src/dotty/tools/dotc/reporting/messages.scala:1327:11 
[error] 1327 |      i"""$howVisible$qualifier in ${whereFound.owner}"""
[error]      |           ^^^^^^^^^^
[error]      |           Found:    (howVisible : Matchable)
[error]      |           Required: dotty.tools.dotc.printing.Formatting.Shown
[error]      |
[error]      | longer explanation available when compiling with `-explain`
[error] two errors found

@smarter
Copy link
Member

smarter commented Jan 16, 2023

I haven't got to writing files yet so at this stage displayed stack traces are not truncated

I think this will be a more controversial change (I would likely be against it), so I'd prefer to see it in a separate PR.

I'm getting a couple of compiler errors, and I can't see how they relate to anything I've done.

howVisible is set using a match, one of the branch of this match calls assert and the others return a String. Your custom definition of assert returns Unit instead of Nothing, so the type of howVisible is inferred to be Matchable (the non-union least-upper-bound of String and Unit) rather than String.

Comment on lines 108 to 109
class SuspendException extends Exception
class SuspendException extends ControlThrowable
Copy link
Member

@smarter smarter Jan 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change came up recently and was rejected: #16616 (comment)
More generally it seems from https://contributors.scala-lang.org/t/nonfatal-and-controlthrowable-changed-in-stdlib/6049 that there is a desire to move away from / deprecate ControlThrowable.

@robmwalsh
Copy link
Author

I haven't got to writing files yet so at this stage displayed stack traces are not truncated

I think this will be a more controversial change (I would likely be against it), so I'd prefer to see it in a separate PR.

Makes sense, I'll leave that out for now

howVisible is set using a match, one of the branch of this match calls assert and the others return a String. Your custom definition of assert returns Unit instead of Nothing, so the type of howVisible is inferred to be Matchable (the non-union least-upper-bound of String and Unit) rather than String.

Ah, thanks! I changed this and implode to Unit to experiment with not throwing if somebody called implode with a ControlThrowable, but hit this issue (for obvious reasons) so and thought I changed them both, but apparently forgot to change assert back. A case of seeing what I expected to see rather than what is there :(

Since implode is specifically designed to ensure the compiler crashes, I think crashing anyway is the right behavior, but a better way is to ensure calling implode with an ControlThrowable is a compilation error to prevent this situation in the first place.

@robmwalsh robmwalsh closed this Jan 16, 2023
@robmwalsh robmwalsh reopened this Jan 17, 2023
@SethTisue
Copy link
Member

SethTisue commented Jan 17, 2023

Just as a meta-comment here, perhaps some splitting into multiple PRs would help, so that any separable clear wins could get merged, as we continue to discuss anything that seems to require more consideration.

(EDIT: Oh, I see I'm seconding Guillaume.)

@robmwalsh
Copy link
Author

Just as a meta-comment here, perhaps some splitting into multiple PRs would help,

Got some initial runs going on a couple of issues last night and it's looking really nice thanks to @dwijnand's significantly better formatting, so yeah I think I could do some minor tidy up tonight then I'll ask Martin to take another look.

I'll just revert the ControlThrowable change to SuspendException and can revisit the ideas of ControlFlow base trait and a custom NonFatal in another PR. I'll leave Implode as a ControlThrowable for now to avoid needing to add a bunch of conditions or re-throwing to the existing handlers.

@robmwalsh
Copy link
Author

pretty :)

[info] running (fork) dotty.tools.dotc.Main -classpath /home/rob/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.13.10/scala-library-2.13.10.jar:/home/rob/code/dotty/library/../out/bootstrap/scala3-library-bootstrapped/scala-3.3.0-RC1-bin-SNAPSHOT-nonbootstrapped/scala3-library_3-3.3.0-RC1-bin-SNAPSHOT.jar -d local/out local/i16207.scala
-- Error: local/i16207.scala:8:4 -----------------------------------------------
8 |val headers: List[String] = labelsOf[C].toList.map(_.toString)
  |^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  |  An unhandled exception was thrown in the compiler. Please file a crash
  |  report here: https://github.com/lampepfl/dotty/issues/new/choose
  |  with the following information:
  |
  |java.lang.AssertionError: assertion failed: no owner from  <none>/ <none> in {
  |  val p$proxy1:
  |    
  |      scala.deriving.Mirror.Product{
  |        type MirroredMonoType = C; type MirroredType = C;
  |          type MirroredLabel = ("C" : String);
  |          type MirroredElemTypes = (Int, Int);
  |          type MirroredElemLabels = (("date" : String), ("time" : String))
  |      }
  |    
  |   =
  |    C.$asInstanceOf[
  |      
  |        scala.deriving.Mirror.Product{
  |          type MirroredMonoType = C; type MirroredType = C;
  |            type MirroredLabel = ("C" : String);
  |            type MirroredElemTypes = (Int, Int);
  |            type MirroredElemLabels = (("date" : String), ("time" : String))
  |        }
  |      
  |    ]
  |  {
  |    val Tuple_this: Tuple =
  |      (
  |        {
  |          val res: Tuple =
  |            {
  |              val x$1: ("date" : String) = "date"
  |              {
  |                val Tuple_this: ("time" : String) *: EmptyTuple.type =
  |                  {
  |                    val res: Tuple =
  |                      {
  |                        val x$1: ("time" : String) = "time"
  |                        {
  |                          val Tuple_this: EmptyTuple.type = EmptyTuple
  |                          runtime.Tuples.cons("time", Tuple_this).asInstanceOf[
  |                            ("time" : String) *: EmptyTuple.type]:
  |                            ("time" : String) *: EmptyTuple.type
  |                        }:Any *: Tuple
  |                      }
  |                    res.asInstanceOf[("time" : String) *: EmptyTuple.type]
  |                  }:("time" : String) *: EmptyTuple.type
  |                runtime.Tuples.cons("date", Tuple_this).asInstanceOf[
  |                  (("date" : String), ("time" : String))]:
  |                  (("date" : String), ("time" : String))
  |              }:Any *: Tuple
  |            }
  |          res.asInstanceOf[p$proxy1.MirroredElemLabels]
  |        }:p$proxy1.MirroredElemLabels
  |      :Tuple)
  |    (
  |      Tuple_this.productIterator.toList.asInstanceOf[
  |        scala.collection.immutable.List[Tuple.Union[(Tuple_this : Product)]]]
  |    :scala.collection.immutable.List[Tuple.Union[(Tuple_this : Product)]])
  |  }
  |}.<none>
  |
  |java.lang.AssertionError: assertion failed: no owner from  <none>/ <none> in {
  |  val p$proxy1:
  |    
  |      scala.deriving.Mirror.Product{
  |        type MirroredMonoType = C; type MirroredType = C;
  |          type MirroredLabel = ("C" : String);
  |          type MirroredElemTypes = (Int, Int);
  |          type MirroredElemLabels = (("date" : String), ("time" : String))
  |      }
  |    
  |   =
  |    C.$asInstanceOf[
  |      
  |        scala.deriving.Mirror.Product{
  |          type MirroredMonoType = C; type MirroredType = C;
  |            type MirroredLabel = ("C" : String);
  |            type MirroredElemTypes = (Int, Int);
  |            type MirroredElemLabels = (("date" : String), ("time" : String))
  |        }
  |      
  |    ]
  |  {
  |    val Tuple_this: Tuple =
  |      (
  |        {
  |          val res: Tuple =
  |            {
  |              val x$1: ("date" : String) = "date"
  |              {
  |                val Tuple_this: ("time" : String) *: EmptyTuple.type =
  |                  {
  |                    val res: Tuple =
  |                      {
  |                        val x$1: ("time" : String) = "time"
  |                        {
  |                          val Tuple_this: EmptyTuple.type = EmptyTuple
  |                          runtime.Tuples.cons("time", Tuple_this).asInstanceOf[
  |                            ("time" : String) *: EmptyTuple.type]:
  |                            ("time" : String) *: EmptyTuple.type
  |                        }:Any *: Tuple
  |                      }
  |                    res.asInstanceOf[("time" : String) *: EmptyTuple.type]
  |                  }:("time" : String) *: EmptyTuple.type
  |                runtime.Tuples.cons("date", Tuple_this).asInstanceOf[
  |                  (("date" : String), ("time" : String))]:
  |                  (("date" : String), ("time" : String))
  |              }:Any *: Tuple
  |            }
  |          res.asInstanceOf[p$proxy1.MirroredElemLabels]
  |        }:p$proxy1.MirroredElemLabels
  |      :Tuple)
  |    (
  |      Tuple_this.productIterator.toList.asInstanceOf[
  |        scala.collection.immutable.List[Tuple.Union[(Tuple_this : Product)]]]
  |    :scala.collection.immutable.List[Tuple.Union[(Tuple_this : Product)]])
  |  }
  |}.<none>
  |     at dotty.tools.package$.throwAssertionError(package.scala:66)
  |     at dotty.tools.package$.inline$throwAssertionError(package.scala:64)
  |     at dotty.tools.dotc.transform.Erasure$Typer.typedSelect(Erasure.scala:715)
  |     at dotty.tools.dotc.typer.Typer.typedNamed$1(Typer.scala:2887)
  |     at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2980)
  |     at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:127)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3048)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3052)
  |     at dotty.tools.dotc.typer.Typer.typedExpr(Typer.scala:3168)
  |     at dotty.tools.dotc.transform.Erasure$Typer.typedTypeApply(Erasure.scala:811)
  |     at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:2937)
  |     at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2981)
  |     at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:127)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3048)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3052)
  |     at dotty.tools.dotc.typer.Typer.typedExpr(Typer.scala:3168)
  |     at dotty.tools.dotc.transform.Erasure$Typer.typedApply(Erasure.scala:830)
  |     at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:2918)
  |     at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2981)
  |     at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:127)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3048)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3052)
  |     at dotty.tools.dotc.typer.Typer.typedExpr(Typer.scala:3168)
  |     at dotty.tools.dotc.typer.Typer.$anonfun$48(Typer.scala:2372)
  |     at dotty.tools.dotc.inlines.PrepareInlineable$.dropInlineIfError(PrepareInlineable.scala:249)
  |     at dotty.tools.dotc.typer.Typer.typedDefDef(Typer.scala:2372)
  |     at dotty.tools.dotc.transform.Erasure$Typer.typedDefDef(Erasure.scala:947)
  |     at dotty.tools.dotc.typer.Typer.typedNamed$1(Typer.scala:2894)
  |     at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2980)
  |     at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:127)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3048)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3052)
  |     at dotty.tools.dotc.typer.Typer.traverse$1(Typer.scala:3074)
  |     at dotty.tools.dotc.typer.Typer.typedStats(Typer.scala:3124)
  |     at dotty.tools.dotc.transform.Erasure$Typer.typedStats(Erasure.scala:1046)
  |     at dotty.tools.dotc.typer.Typer.typedClassDef(Typer.scala:2552)
  |     at dotty.tools.dotc.transform.Erasure$Typer.typedClassDef(Erasure.scala:1035)
  |     at dotty.tools.dotc.typer.Typer.typedTypeOrClassDef$1(Typer.scala:2906)
  |     at dotty.tools.dotc.typer.Typer.typedNamed$1(Typer.scala:2910)
  |     at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2980)
  |     at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:127)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3048)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3052)
  |     at dotty.tools.dotc.typer.Typer.traverse$1(Typer.scala:3074)
  |     at dotty.tools.dotc.typer.Typer.typedStats(Typer.scala:3124)
  |     at dotty.tools.dotc.transform.Erasure$Typer.typedStats(Erasure.scala:1046)
  |     at dotty.tools.dotc.typer.Typer.typedPackageDef(Typer.scala:2682)
  |     at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:2951)
  |     at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2981)
  |     at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:127)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3048)
  |     at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3052)
  |     at dotty.tools.dotc.typer.Typer.typedExpr(Typer.scala:3168)
  |     at dotty.tools.dotc.transform.Erasure.run(Erasure.scala:145)
  |     at dotty.tools.dotc.core.Phases$Phase.runOn$$anonfun$1(Phases.scala:327)
  |     at scala.collection.immutable.List.map(List.scala:246)
  |     at dotty.tools.dotc.core.Phases$Phase.runOn(Phases.scala:332)
  |     at dotty.tools.dotc.Run.runPhases$1$$anonfun$1(Run.scala:246)
  |     at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
  |     at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
  |     at scala.collection.ArrayOps$.foreach$extension(ArrayOps.scala:1321)
  |     at dotty.tools.dotc.Run.runPhases$1(Run.scala:262)
  |     at dotty.tools.dotc.Run.compileUnits$$anonfun$1(Run.scala:270)
  |     at dotty.tools.dotc.Run.compileUnits$$anonfun$adapted$1(Run.scala:279)
  |     at dotty.tools.dotc.util.Stats$.maybeMonitored(Stats.scala:68)
  |     at dotty.tools.dotc.Run.compileUnits(Run.scala:279)
  |     at dotty.tools.dotc.Run.compileSources(Run.scala:197)
  |     at dotty.tools.dotc.Run.compile(Run.scala:179)
  |     at dotty.tools.dotc.Driver.doCompile(Driver.scala:36)
  |     at dotty.tools.dotc.Driver.process(Driver.scala:193)
  |     at dotty.tools.dotc.Driver.process(Driver.scala:161)
  |     at dotty.tools.dotc.Driver.process(Driver.scala:173)
  |     at dotty.tools.dotc.Driver.main(Driver.scala:203)
  |     at dotty.tools.dotc.Main.main(Main.scala)
  |
  |
  |     while compiling: local/i16207.scala
  |        during phase: erasure
  |                mode: Mode(ImplicitsEnabled)
  |     library version: version 2.13.10
  |    compiler version: version 3.3.0-RC1-bin-SNAPSHOT-nonbootstrapped-git-411e018
  |            settings: -classpath /home/rob/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.13.10/scala-library-2.13.10.jar:/home/rob/code/dotty/library/../out/bootstrap/scala3-library-bootstrapped/scala-3.3.0-RC1-bin-SNAPSHOT-nonbootstrapped/scala3-library_3-3.3.0-RC1-bin-SNAPSHOT.jar -d local/out
  |
  |                tree: headers
  |       tree position: local/i16207.scala:8:4
  |           tree type: (headers : (): scala.collection.immutable.List)
  |              symbol: getter headers
  |   symbol definition: def headers(): scala.collection.immutable.List (a Symbol)
  |      symbol package: <empty>
  |       symbol owners:  in package object i16207$package
  |           call site: getter headers in module class <empty>
  |
  |
1 error found
[error] Nonzero exit code returned from runner: 1
[error] (scala3-compiler / Compile / runMain) Nonzero exit code returned from runner: 1
[error] Total time: 7 s, completed Jan 20, 2023, 7:23:20 PM

@robmwalsh robmwalsh changed the title [WIP] somewhat graceful crashing Improved crash handling Jan 20, 2023
@robmwalsh robmwalsh marked this pull request as ready for review January 20, 2023 10:21
@dwijnand
Copy link
Member

#16207 is a great example, because it prints a huge, compiler-generated tree in the assertion message, as a compiler hacker's choice. As that message (in the exception) is returned by the compiler and gets printed (at least in sbt running - not sure about in zinc compile) perhaps we shouldn't enrich the message and print the enrichment and then throw with the original message. I think we should just throw an exception with an enriched message so it gets printed once, followed by the stacktrace (in the default handling of exception).

@robmwalsh
Copy link
Author

I think we should just throw an exception with an enriched message so it gets printed once, followed by the stacktrace (in the default handling of exception).

I did consider this originally, but decided to use report.error instead so that diagnostics work in an IDE. Metals currently silently fails on a compiler crash which is rather frustrating, and one of the reasons I was getting into so much trouble with compiler crashes, because it wasn't obvious which change introduced the crash!

Are you saying it prints the message twice using SBT with this implementation? It shouldn't be, because at the edges of the compiler the Implosion is caught and silently discarded (not printed).

@robmwalsh robmwalsh marked this pull request as draft January 20, 2023 11:03
@robmwalsh
Copy link
Author

Looks like I have a few more errors to resolve :( that's a job for another day

@dwijnand
Copy link
Member

Are you saying it prints the message twice using SBT with this implementation? It shouldn't be, because at the edges of the compiler the Implosion is caught and silently discarded (not printed).

Your own comment shows it printed twice: #16593 (comment)

@robmwalsh
Copy link
Author

Your own comment shows it printed twice: #16593 (comment)

Oh, right 🙃 It was a long day... Thanks for the pickup.
Looks like I stuffed up some changes I made to enrichErrorMessage - throwing and allowing the default handling to print it would still show it twice. I can fix that up easily enough.

@SethTisue
Copy link
Member

a rebase should make the Scaladoc failure go away (we think, anyway)

the "Context was thrown away, this crash report may not be accurate" failure(s?) seem like a genuine logic problem with the current diffs

@robmwalsh
Copy link
Author

Thanks @SethTisue, I'll have some time over this weekend to take another look.

@dwijnand
Copy link
Member

@robmwalsh would you be ok with me landing some of the initial improvements I had my eye on in #16685, while this isn't moving?

@robmwalsh
Copy link
Author

@dwijnand go for it. I've spent some time trying to figure out what's wrong with this and I'm a bit stumped.

* use:
* `ctx.implode(Exception("foo"))`
*/
inline def implode(inline cause: Throwable): Nothing =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
inline def implode(inline cause: Throwable): Nothing =
def implode(inline cause: Throwable): Nothing =

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will not gain anything from inlining this definition. We might actually end up generating more code.

/**
* Crash the compiler with a message in a controlled way
*/
inline def implode(inline msg: Any): Nothing =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
inline def implode(inline msg: Any): Nothing =
def implode(inline msg: Any): Nothing =

val blackBox = new StringBuilder()

extension [A](a: A)
transparent inline def record: A =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
transparent inline def record: A =
def record: a.type =


transparent inline def assert(inline assertion: Boolean, inline message: Any): Unit =
if !assertion then
summonFrom {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation is missing here.

transparent inline def assert(inline assertion: Boolean): Unit =
assert(assertion, null)

transparent inline def assert(inline assertion: Boolean, inline message: Any): Unit =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better if this one is not called assert because it can be confused with the normal assert. There is no way to distinguish a call to this assert without a context with a call to Predef assert.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants