errors: make Exception.message/1 plain; add Lua.format_exception/1#393
Merged
Conversation
`Exception.message/1` on `Lua.RuntimeException` and `Lua.CompilerException` embedded ANSI color and a multi-line layout (location header, source context, stack trace, suggestion). ANSI was gated on `IO.ANSI.enabled?/0`, but that gate answers "is stdout a TTY?" — a poor proxy for "is this string about to be painted onto an interactive terminal?". A host that starts its app from a terminal and logs `Exception.message(e)` gets colored, multi-line log lines even though the destination is `Logger`, not the TTY. Colored, rich rendering is only useful for a human reading an error interactively — i.e. `mix lua.eval` and uncaught crashes. So move it there: - `Exception.message/1` now returns a plain, single-line, ANSI-free string (error body + a compact `(at source:line)` suffix), safe for `Logger` and error trackers. - `Lua.format_exception/1` renders the rich, ANSI-gated report and is what `mix lua.eval` prints. Each internal VM error gained a `format/1` sibling to `message/1`/`to_map/2`. - `Lua.RuntimeException.to_map/2` and `Lua.CompilerException.to_map/1` expose the existing wire-safe structured shape for JSON / UI reporting. Lua §6.1 semantics are untouched: pcall/xpcall/call_function hand back the raw value via `ProtectedCall.error_value/1`, never `Exception.message/1`.
davydog187
commented
Jul 15, 2026
The Exception.message/1 / Lua.format_exception/1 split ships as part of 1.0.0, not a later release, so move its entries out of [Unreleased] and into the 1.0.0 section. Reconcile the #384 entries that described the intermediate behavior (#393 supersedes: message is plain, rich report moves to Lua.format_exception/1) so the release reads coherently.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
A user reported that
Exception.message/1on Lua's exceptions embeds ANSI color and a multi-line layout (location header, source context, stack trace, suggestion). When they start their app in dev from a terminal,IO.ANSI.enabled?/0is legitimatelytrue, so everyLogger.error(Exception.message(e))gets colored, multi-line log lines.The root cause is a layering issue:
IO.ANSI.enabled?/0answers "is stdout a TTY?", a poor proxy for "is this string about to be painted onto an interactive terminal?". The two come apart the moment the string goes toLoggerinstead ofIO.puts. And ANSI is only the visible symptom — even with color off, the message was still multi-line.Colored, rich rendering is only genuinely useful for a human reading an error interactively — i.e.
mix lua.evaland uncaught crashes. So this PR moves it there and makes the library default plain.Changes
Exception.message/1(onLua.RuntimeException/Lua.CompilerException) now returns a plain, single-line, ANSI-free string — the error body plus a compact(at source:line)suffix — safe forLoggerand error trackers.Lua.format_exception/1renders the rich, ANSI-gated report (location, stack trace, suggestions) and is whatmix lua.evalnow prints. Each internal VM error gained aformat/1sibling to itsmessage/1/to_map/2.Lua.RuntimeException.to_map/2andLua.CompilerException.to_map/1expose the existing wire-safe structured shape (no ANSI) for JSON payloads, structured logs, and UI-facing reporting.Before / after (app started from a TTY,
ansi_enabled: true)Safety: Lua §6.1 is untouched
The value
pcall/xpcall/Lua.call_functionhand back to Lua goes throughLua.VM.ProtectedCall.error_value/1(raw:value/:lua_value/ArgumentError.raw_message), notException.message/1. Verified:pcall(error('boom'))still returns{false, "<eval>:1: boom"}.Compatibility note
If you relied on
Exception.message/1for the rich render, switch toLua.format_exception/1. Message strings aren't a stable API, but this is called out in the CHANGELOG since the output format visibly changes.Verification
mix test→ 2611 passed, 7 skipped;mix format --check-formattedclean; compiles with--warnings-as-errors.mix lua.evalon a failing script (piped/non-TTY): full rich report, zero ANSI escapes.ansi_enabled: true):Exception.message/1has no\e[and no\n.