improvements to exception messages and printing



  • Discussions about "make errors better" often conflate error message, stacktraces, exception data

  • Errors do not clearly indicate in what "phase" of execution they occur (read, compile, macroexpand, evaluation, printing)

  • Errors during macroexpansion do not capture the location in caller source (like read and compile exceptions do), and thus the wrong "location" is reported

  • Clojure prints data into Throwable messages

    • big and ugly

    • outside user control


  • exception messages should be small strings

    • with the expectation that getMessage will be printed

    • totally controlled by throwing code

    • never print arbitrary data into a message

  • flow data all the way to the edge

    • ex-info & ex-data

    • macroexpand spec errors have a well known format

  • edge printing should be concise by default, configurable by user

    • edge functions like repl-caught respect print and spec bindings

    • concise summary by default

    • all the details when you want

Proposed impl changes

  • stop printing data into message strings

    • ExceptionInfo.toString should list keys, not entire map contents

    • spec.alpha/macroexpand-check should stop including explain-out in message

  • and instead print data (configurably) at the edges repl/pst and main/repl-caught

    • print spec per explain-out

    • print ExceptionInfo keys

  • make CompilerException wrappers special at print time instead of construct time

    • CE message is "CompilerException + file/line"

    • wrapped message is whatever it is

    • edge printers should print both


  • what should tools do?

    • leverage the data provided in the exception chain to do whatever they want

    • can use main/repl-caught as a guide

  • what happens to programs that parse exception messages?

    • probably should not do this (Clojure's tests do this but try to minimize it)

    • this proposal changes some wrapper message text, but that was never part of the contract

Patch Status

  • clj-2373-spec-alpha-2.patch - for spec.alpha - don't print explain data into message (callers can do what they want with that)

  • clj-2373-9.patch - for clojure

    • LispReader - made ReaderException line/column fields public so CompilerEx can swipe them

    • Compiler

      • CompilerException now implements IExceptionInfo and works with ex-data

      • Created well-known keys :clojure.error/source,line,column,phase,symbol

      • Created well-known phases :read, :macroexpand, :compile

      • Left existing CompilerException fields, but stored rest into data map

      • Construct new syntax error messages on construction

      • Use toString() to encapsulate combining wrapper and cause into a message

      • On read, spec, macroexpand exceptions, add appropriate wrapping

      • Tweaked existing compiler exception calls as much as possible to record symbol (more could potentially be done here - in some cases we have forms or non-symbol things we could report)

      • Moves "Cause:" down to second line except for macroexpand spec errors

    • Var - expose a method to get the symbol for a var. Many people have requested this functionality via a core function, maybe this is a step in that direction.

    • clojure.main

      • add init-cause (private) function to get initial root (rather than existing weird root-cause)

      • add ex-str function (public) to construct the message to print based on an ex. tools could use this. Embeds some of the phase/printing logic but piggiebacks on CompilerException.toString() for some as well. Handles spec problem ex-data specially - this could be genericized but I have not tried to do that here.

      • change repl-caught to rely on ex-str

      • change main repl code to catch and wrap read and print exceptions to specialize errors

    • test* - in general these are tweaks to test to check the cause message rather than the wrapper message with the help of a new assertion type defined in test-helper

Also see:

Screener's Notes

See example-errors.txt attachment for resulting error message examples.




Alex Miller
August 21, 2018, 4:05 PM

Added -5 patch which removes the macroexpand sub-phase differences and isolates that into the message construction. Also cleans up the repl read exception wrapping to not confusingly wrap in CompilerException.

Alex Miller
August 22, 2018, 3:11 PM

-6 patch fixes a couple bugs in printing read errors from source files.

Alex Miller
August 22, 2018, 11:01 PM

Added -7 that fixes a double-period on error reading from source file.

Alex Miller
September 5, 2018, 1:42 AM

Applied spec patch...

Alex Miller
September 5, 2018, 3:05 AM

Added -9 patch that fixes test errors in -8 patch





Stuart Halloway





Fix versions