Skip to content

fix(error): stdlib bad-arg raises auto-populate line and source#215

Merged
davydog187 merged 3 commits into
mainfrom
fix/error-line-info-stdlib
May 9, 2026
Merged

fix(error): stdlib bad-arg raises auto-populate line and source#215
davydog187 merged 3 commits into
mainfrom
fix/error-line-info-stdlib

Conversation

@davydog187
Copy link
Copy Markdown
Contributor

Stdlib bad-argument raises read source position from process dict

Plan: .agents/plans/A19-error-line-info-native-funcs.md

A18 wired line/source through every executor raise and through the
assert/error native-call boundary. This plan finishes the job for
every other stdlib raise — string.upper(nil), math.floor("x"),
table.insert with bad pos, etc.

The implementation strategy turned out cleaner than the plan
anticipated: instead of touching ~80 raise sites individually, the
four exception modules (RuntimeError, TypeError, AssertionError,
ArgumentError) now auto-populate :line / :source from
Lua.VM.Executor.current_position/0 inside exception/1 when not
given explicitly. Zero callsite changes for the ~80 ArgumentError
raises across math, string, table, and the rest of stdlib.

Explicit opts still win — every executor raise site that already
passes :line / :source (the safe_* helpers, the
divide-by-zero / modulo-by-zero sites) is unaffected.

ArgumentError gains :line / :source / :call_stack fields and
now renders through Lua.VM.ErrorFormatter so the
at <source>:<line>: prefix shows up consistently with the other
exception types.

Success criteria

  • mix test passes (1577 → 1585, +8 new tests in error_messages_test).
  • mix test --only lua53 passes 5/29 (no regression).
  • string.upper(nil) → TypeError with line/source.
  • table.insert(t, nil, 5) (bad pos) → has line/source.
  • math.floor("x") → has line/source.
  • No measurable perf regression on the benchee harness vs A18.
    Re-ran fib(28) and a 1000-iteration string-ops workload on
    both main and this branch with a fresh build each time;
    medians within ~1% (within the noise floor). Exception module
    construction is off the hot path — Process.get only fires
    when an exception is actually being constructed.

Tour of new behavior

iex> try do
...>   Lua.eval!(Lua.new(), "local x = 1\nreturn string.upper(nil)", source: "demo.lua")
...> rescue
...>   e -> {e.line, e.source, Exception.message(e)}
...> end
{2, "demo.lua",
 "Lua runtime error: Runtime Type Error\n\n  at demo.lua:2:\n\n  bad argument #1 to 'string.upper' (string expected, got nil)"}
iex> try do
...>   Lua.eval!(Lua.new(), "local y = 2\nreturn math.floor(\"x\")", source: "demo.lua")
...> rescue
...>   e -> {e.line, e.source}
...> end
{2, "demo.lua"}

Defensive behavior

When an exception is raised outside any Lua execution (e.g. someone
calls a stdlib helper directly from Elixir, or constructs the
exception by hand), current_position/0 returns {nil, nil} and
the message just renders without a location prefix:

iex> raise Lua.VM.ArgumentError, function_name: "outside_lua", arg_num: 1, expected: "string"
** (Lua.VM.ArgumentError) bad argument #1 to 'outside_lua' (string expected)

Files touched

  • lib/lua/vm/argument_error.ex — added :line / :source /
    :call_stack fields, auto-populate, render via ErrorFormatter.
  • lib/lua/vm/runtime_error.ex — auto-populate.
  • lib/lua/vm/type_error.ex — auto-populate.
  • lib/lua/vm/assertion_error.ex — auto-populate.
  • test/lua/error_messages_test.exs — 8 new tests under
    "stdlib bad-argument raises carry line and source".

Out of scope (intentional, per plan)

  • The two string.pack/string.unpack "not yet implemented" raises
    in lib/lua/vm/stdlib/string.ex. A25 implements them.
  • Two pre-existing oddities surfaced during smoke testing, neither
    introduced by this change:
    • table.insert(t, nil, 5) reports the wrong arg number in its
      "bad argument" message (says Pull in code #1, should be Add CI #2). Logged as a
      candidate for an A24 sub-plan.
    • setmetatable(5, {}) on line 1 of a script reports line 0
      same source_line off-by-one A18 already flagged.

Verification

mix format
mix compile --warnings-as-errors
mix test                                # 1585 passing, 0 failing
mix test --only lua53                   # 5/29 passing

davydog187 added 3 commits May 8, 2026 09:07
A18 wired line/source through every executor raise and through the
assert/error native-call boundary. This finishes the job for every
other stdlib raise (string.upper(nil), math.floor("x"), table.insert
with bad pos, etc.) by having the four exception modules — RuntimeError,
TypeError, AssertionError, ArgumentError — auto-populate :line / :source
from Executor.current_position/0 inside exception/1 when not given.

Zero callsite changes for ~80 ArgumentError raises across math, string,
table, and the rest of stdlib. Explicit opts still win (raise sites that
already pass :line / :source — like the executor's safe_* helpers and
the divide-by-zero / modulo-by-zero sites — are unaffected).

ArgumentError gains :line, :source, :call_stack fields and now renders
through Lua.VM.ErrorFormatter so the "at <source>:<line>:" prefix shows
up consistently with the other exceptions.

Verified:
- 1577 -> 1585 tests pass (+8 new in error_messages_test).
- 5/29 lua53 suite still passes (no regression).
- fib(28) and string_ops bench show no measurable perf delta vs main
  (within ~1% noise floor; exception modules are off the hot path).
- Defensive: when raised outside a Lua execution, current_position/0
  returns {nil, nil} and exceptions render without a location prefix.

Plan: A19
@davydog187 davydog187 merged commit e740a8c into main May 9, 2026
4 checks passed
@davydog187 davydog187 deleted the fix/error-line-info-stdlib branch May 9, 2026 01:56
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