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
Principles
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
Discussion
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
See example-errors.txt attachment for resulting error message examples.
Environment
None
Attachments
13
05 Sep 2018, 04:40 PM
05 Sep 2018, 03:05 AM
04 Sep 2018, 08:23 PM
29 Aug 2018, 01:14 AM
22 Aug 2018, 11:01 PM
22 Aug 2018, 03:11 PM
21 Aug 2018, 04:05 PM
16 Aug 2018, 06:39 PM
16 Aug 2018, 06:16 PM
11 Aug 2018, 10:08 PM
12 Jul 2018, 06:12 AM
09 Jul 2018, 05:59 PM
Activity
Alex Miller September 5, 2018 at 3:05 AM
Added -9 patch that fixes test errors in -8 patch
Alex Miller September 5, 2018 at 1:42 AM
Applied spec patch...
Alex Miller August 22, 2018 at 11:01 PM
Added -7 that fixes a double-period on error reading from source file.
Alex Miller August 22, 2018 at 3:11 PM
-6 patch fixes a couple bugs in printing read errors from source files.
Alex Miller August 21, 2018 at 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.
Problems
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
Principles
exception messages should be small strings
with the expectation that
getMessage
will be printedtotally 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 bindingsconcise 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 contentsspec.alpha/macroexpand-check
should stop includingexplain-out
in messageand instead print data (configurably) at the edges
repl/pst
andmain/repl-caught
print spec per
explain-out
print
ExceptionInfo
keysmake
CompilerException
wrappers special at print time instead of construct timeCE message is "CompilerException + file/line"
wrapped message is whatever it is
edge printers should print both
Discussion
what should tools do?
leverage the data provided in the exception chain to do whatever they want
can use
main/repl-caught
as a guidewhat 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: https://dev.clojure.org/display/design/Exception+handling+update
Screener's Notes
See example-errors.txt attachment for resulting error message examples.