Skip to content

fix(parser): adjust parenthesised call/vararg to a single value#278

Merged
davydog187 merged 3 commits into
mainfrom
fix/paren-adjust-single-value
May 28, 2026
Merged

fix(parser): adjust parenthesised call/vararg to a single value#278
davydog187 merged 3 commits into
mainfrom
fix/paren-adjust-single-value

Conversation

@davydog187
Copy link
Copy Markdown
Contributor

Adjust parenthesised call/vararg to a single value (Lua 5.3 §3.4)

Plan: .agents/plans/A42-paren-adjust-single-value.md
Closes #254

Goal

Per Lua 5.3 §3.4, (f()) and (...) must adjust to exactly one
value. Before this change the parser dropped parens at parse time, so
(f()) and f() produced identical ASTs; in multi-value positions
(last RHS, last arg, return, last table field) both expanded to all
results.

Spec repro now produces the expected result:

local function f() return 1, 2, 3 end
local a, b, c = (f())
return a, b, c        -- 1, nil, nil

Success criteria

  • New unit-test file test/lua/vm/paren_adjust_test.exs pins all four
    multi-value positions (last RHS of multi-assign, last return value,
    last call argument, last table-constructor field) for both Call and
    Vararg inners. (10 tests, all passing.)
  • constructs.lua now runs end-to-end through line 224. Its skip
    narrows from :all to 225..313 with a new, accurate reason
    covering the remaining debug.getinfo/os.time/load-driven
    blockers.
  • calls.lua line 207..208 §3.4 skip entry removed; the file still
    passes with the four remaining unrelated skips.
  • mix test: 2008 → 2019 passed (+10 paren tests, +1 from
    constructs.lua leaving :all), 24 → 23 skipped.
  • mix test --only lua53: 12 → 13 files passing.

Changes

 .agents/plans/A42-paren-adjust-single-value.md   | 138 ++++++++++++++++++++++
 lib/lua/ast/expr.ex                              |  23 +++-
 lib/lua/ast/pretty_printer.ex                    |   4 +
 lib/lua/ast/walker.ex                            |   6 +
 lib/lua/compiler/codegen.ex                      |   2 +
 lib/lua/compiler/scope.ex                        |   2 +
 lib/lua/parser.ex                                |  21 ++-
 test/lua/vm/paren_adjust_test.exs                | 117 +++++++++++++++++++
 test/lua53_skips.exs                             |  11 +-

Mechanism:

  • New Expr.Paren{inner, meta} AST node wraps only Expr.Call,
    Expr.MethodCall, and Expr.Vararg. Other parenthesised
    expressions stay unwrapped — semantically transparent and no need
    to disturb the existing AST shape.
  • The parser wraps these three multi-value kinds in
    parse_paren_expr/1; everything else falls through.
  • Scope adds a single pass-through clause; codegen does the same. The
    multi-value detection sites (Statement.Return, Statement.Assign,
    Statement.Local, Expr.Call args, Expr.MethodCall args,
    Expr.Table last field) all pattern-match the bare struct, so the
    Paren wrapper naturally opts out of expansion.
  • Walker and pretty-printer learn about Paren (one clause each) to
    keep AST traversal and round-tripping honest.

Discoveries

  • Once §3.4 was fixed, constructs.lua advanced to a different
    failure at line 226: assert(debug.getinfo(1, "n").name == 'F').
    debug.getinfo's "n" field isn't wired up for the
    currently-executing closure. Past 226 the file also hits
    os.time (line 237, not implemented) and the load()-driven
    short-circuit harness (lines 287–298). These are separate concerns;
    the new skip range covers 225..313 with a triage note rather than
    expanding this PR's scope.
  • The codegen multi-value detection sites match Expr.Call /
    Expr.MethodCall / Expr.Vararg structurally, so a single
    gen_expr(%Expr.Paren{inner: inner}, ctx) -> gen_expr(inner, ctx)
    passthrough is enough — no per-site changes needed.

Verification

mix format
mix compile --warnings-as-errors
mix test                       # 2019 passed, 23 skipped (was 2008/24)
mix test --only lua53          # 13 passed, 16 skipped (was 12/17)
mix test test/lua/vm/paren_adjust_test.exs   # 10 passed
mix lua.suite --audit          # 0 stale entries, 0 promotion candidates

Out of scope (intentional)

  • Other §3.4 semantics (fixed-N adjustment is already correct).
  • Source-range tracking for the close-paren position.
  • The downstream constructs.lua failures past line 225 (covered by the
    new narrowed skip; future plans should triage individually).

Per Lua 5.3 §3.4, `(f())` and `(...)` must adjust to exactly one
result. Today the parser strips parens at parse time so multi-value
contexts (last RHS, last arg, `return`, last table field) expand
every result. Introduce `Expr.Paren` wrapping only `Call`,
`MethodCall`, and `Vararg` inners — codegen's call-detection sites
all match the bare structs, so the wrapper opts out of expansion.

Closes #254

Plan: A42
@davydog187 davydog187 merged commit 2489b95 into main May 28, 2026
5 checks passed
@davydog187 davydog187 deleted the fix/paren-adjust-single-value branch May 28, 2026 17:37
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.

Parenthesised call/vararg should adjust to a single value (§3.4)

1 participant