Skip to content

errors: drop the :message field from public runtime exceptions#392

Merged
davydog187 merged 1 commit into
mainfrom
errors/drop-message-field
Jul 15, 2026
Merged

errors: drop the :message field from public runtime exceptions#392
davydog187 merged 1 commit into
mainfrom
errors/drop-message-field

Conversation

@davydog187

Copy link
Copy Markdown
Contributor

Why

Lua.RuntimeException carried a :message struct field that was inconsistently populated — a rendered string for a few constructor shapes, but nil for the most common case: a wrapped VM exception out of eval!/call_function, where message/1 renders lazily to keep the ANSI gate honest (#384).

So reading exception.message directly returned nil for most real runtime errors. Anyone reaching for the conventional field (Logger.error(exception.message)) silently logged nothing. The migration guide even had to warn readers that ":message may be nil."

The data needed to render was already present on semantic fields (:original, :kind, :value, :line, :source, :call_stack). The :message field was pure memoization layered on top — and the one field that misled.

Landing this before the v1.0.0 tag while the public surface is still unfrozen.

What

  • lib/lua/runtime_exception.ex — drop :message from defexception. message/1 is now total, composing lazily from :original (exception → keyword descriptor → binary → {:badarith}/{:illegal_index} term → inspect fallback), all through the existing single-prefix guard. Constructors store their source on :original (binary now lands there instead of nil). Delete the dead {:api_error, …} clause — nothing in lib/ ever raised it.
  • lib/lua/vm/internal_error.ex — same normalization: drop its redundant :message field (always == stringify(value)), add a message/1 callback. Raise sites untouched (all pass value:).
  • tasks/lua.suite.ex — one dev task built the struct with %{message: …} directly; switched to the string constructor.
  • guides/migrating-to-1.0.md — rewrite the ":message may be nil" note to "there is no :message field — use Exception.message/1."
  • Tests — remove the :api_error block, update binary-clause :original assertions, pin refute Map.has_key?(exception, :message), add an InternalError message/1 assertion.

Verification

  • mix test2597 passed, 7 skipped
  • mix format --check-formatted — clean
  • iex sanity: error('boom')Exception.message/1 renders the full string with :kind/:value populated and no :message key; binary, keyword, and illegal_index paths render unchanged.

Note

The removed :api_error clause rendered with a distinct "Lua API error: " prefix. Since it was unreachable dead code, it's gone with the clause. If a real host-API failure path ever needs that prefix, it should come back as a semantic :kind rather than a baked-in :message string.

Lua.RuntimeException carried a :message struct field that was populated
eagerly for some constructor shapes but left nil for the common case of a
wrapped VM exception (rendered lazily to keep the #384 ANSI gate honest).
Reading exception.message directly therefore returned nil for most runtime
errors out of eval!/call_function — a silent footgun for anyone logging
the conventional field.

Remove the field entirely and make Exception.message/1 the single renderer,
composing lazily from the already-present semantic fields (:original, :kind,
:value, :line, :source, :call_stack). Drop the dead {:api_error, ...} clause
while here, and normalize Lua.VM.InternalError the same way (its :message
was always stringify(value); every raise site passes only value:).

Lands before the 1.0.0 tag while the public surface is still unfrozen.
@davydog187 davydog187 merged commit 58beb25 into main Jul 15, 2026
5 checks passed
@davydog187 davydog187 deleted the errors/drop-message-field branch July 15, 2026 18:36
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.

1 participant