Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
fix: preserve call function environments in functors
Prefer a functor's call field over its __call dispatcher when reading or
setting its environment. This keeps shared dispatchers unchanged and applies
the environment to the function that executes the wrapped code.

Backport to the maintained stdlib release branch, adapting the implementation
to lib/std/debug.lua and the regression tests to specs/debug_spec.yaml.
Current stdlib master delegates these functions to std.normalize.

(cherry picked from commit 8b877b249c6f2353cae570647a17fc25464d73c5)
  • Loading branch information
khaneliman committed Oct 16, 2026
commit 4737dbaf7b1df32cee2897381a39fdbde55754e6
8 changes: 4 additions & 4 deletions lib/std/debug.lua
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ if debug_setfenv then
return setfenv(n, env), nil
end
if type(fn) ~= 'function' then
fn =(getmetatable(fn) or {}).__call or fn
fn = fn.call or (getmetatable(fn) or {}).__call or fn
end
return debug_setfenv(fn, env)
end
Expand All @@ -135,7 +135,7 @@ else
end
fn = debug_getinfo(n, 'f').func
elseif type(fn) ~= 'function' then
fn =(getmetatable(fn) or {}).__call or fn
fn = fn.call or (getmetatable(fn) or {}).__call or fn
end

local up, name = 0
Expand Down Expand Up @@ -174,7 +174,7 @@ if debug_getfenv then
-- No need to recurse because Lua doesn't support nested functors.
-- __call can only (sensibly) be a function, so no need to adjust
-- stack frame offset either.
fn =(getmetatable(fn) or {}).__call or fn
fn = fn.call or (getmetatable(fn) or {}).__call or fn
end

-- In Lua 5.1, only debug.getfenv works on C functions; but it
Expand All @@ -193,7 +193,7 @@ else
if n then
fn = debug_getinfo(n + 1, 'f').func
elseif type(fn) ~= 'function' then
fn = (getmetatable(fn) or {}).__call or fn
fn = fn.call or (getmetatable(fn) or {}).__call or fn
end

local name, env
Expand Down
23 changes: 23 additions & 0 deletions specs/debug_spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1051,6 +1051,17 @@ specify std.debug:
- it understands functables:
M.setfenv (ft, env)
expect (f (ft)).to_be (env)
- it prefers a functable call field environment: |
expect (luaproc [[
local M = require "std.debug"
local env = {tostring = tostring}
local fn = function (x) return tostring (x) end
local ft = setmetatable ({call = fn}, {
__call = function (self, ...) return self.call (...) end,
})
M.setfenv (fn, env)
assert (M.getfenv (ft) == env)
]]).to_succeed_with ""

- describe setfenv:
- before:
Expand All @@ -1063,6 +1074,18 @@ specify std.debug:
- it understands functables:
f (ft, env)
expect (fn (5)).to_be '"5"'
- it sets a functable call field environment: |
expect (luaproc [[
local M = require "std.debug"
local env = {tostring = function (x) return '"' .. tostring (x) .. '"' end}
local fn = function (x) return tostring (x) end
local ft = setmetatable ({call = fn}, {
__call = function (self, ...) return self.call (...) end,
})
M.setfenv (ft, env)
assert (M.getfenv (fn) == env)
assert (ft (42) == '"42"')
]]).to_succeed_with ""


- describe say:
Expand Down