Skip to content

fix(compiler): capture _ENV only in closures that use a global - #433

Open
dbernheisel wants to merge 1 commit into
mainfrom
fix/debug-setupvalue
Open

dbernheisel wants to merge 1 commit into
mainfrom
fix/debug-setupvalue

Conversation

@dbernheisel

Copy link
Copy Markdown

Summary

  • Stop the eager allocation of an _ENV upvalue at the entry of each nested function in Lua.Compiler.Scope.
  • A closure now captures _ENV only at its first reference to a global. The existing upvalue chain (resolve_env_ref/1 and ensure_upvalue/4) does this.
  • Upvalue numbers now match PUC-Lua. A closure over local c has c in slot 1. A closure that reads c and then print has c in slot 1 and _ENV in slot 2.
  • debug.setupvalue(f, 1, v) now changes c. Before, it changed _ENV.
  • Update the tests that pinned the old numbering. A real lua binary gave the new expected values.
  • Add tests for first-use order, for _ENV forwarding through a parent that does not use globals, for an _ENV parameter, and for debug.setupvalue in a later evaluation.
  • Update two comments that described the eager capture.

Evidence

iex
# main (3f2aead)
iex(1)> {_, lua} = Lua.eval!(Lua.new(sandboxed: []), "local c = 1 function getc() return c end")
iex(2)> {name, lua} = Lua.eval!(lua, "return debug.setupvalue(getc, 1, 99)")
{["_ENV"], #Lua<>}
iex(3)> Lua.eval!(lua, "return getc()") |> elem(0)
[1]

# this branch
iex(1)> {_, lua} = Lua.eval!(Lua.new(sandboxed: []), "local c = 1 function getc() return c end")
iex(2)> {name, lua} = Lua.eval!(lua, "return debug.setupvalue(getc, 1, 99)")
{["c"], #Lua<>}
iex(3)> Lua.eval!(lua, "return getc()") |> elem(0) |> hd()
99

PUC-Lua 5.5.1 gives the same result for the same source: debug.setupvalue(getc, 1, 99) returns c, and getc() returns 99.

Every nested function took _ENV as upvalue 1, even when it referenced
no global. debug.getupvalue and debug.setupvalue indices were therefore
off by one against PUC-Lua, and debug.setupvalue(f, 1, v) on a closure
over a local overwrote _ENV instead of that local.

Scope resolution no longer allocates _ENV at function entry. The first
free-name reference allocates it through the existing upvalue chain,
so numbering follows first use, as in PUC-Lua.
@dbernheisel
dbernheisel marked this pull request as ready for review October 19, 2026 17:04
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