Backporting _ENV to Lua 5.1
Lua 5.2 has replaced setfenv(stacklevel_or_func, env) and getfenv(stacklevel_or_func) with assignments to / accesses of the implicit _ENV upvalue of each function:
The statement _ENV = env in a function is equivalent to setfenv(1, env), changing the current function environment to env, and the expression _ENV is equivalent to getfenv(1), getting the current function environment.
By using setfenv and debug.sethook, this "feature" can be (superficially) added to all Lua functions: Whenever a function is called, the environment is hooked using a proxy table to provide special treatment for assigning to (__newindex) or getting (__index) _ENV.
Additionally, setfenv and getfenv must be overidden to not get rid of the environment, but instead provide a metatable for it (alternatively, just don't use them if you want to use _ENV).
Putting it all together:
local function envify(_ENV)
rawset(_ENV, "_ENV", _ENV)
return setmetatable({}, {
__index = function(_, name)
return _ENV[name]
end,
__newindex = function(_, name, value)
if name == "_ENV" then
-- ensure self reference
setfenv(2, envify(value))
return
end
_ENV[name] = value
end
})
end
local function add_env(stacklevel)
stacklevel = (stacklevel or 1) + 1
local _ENV = getfenv(stacklevel)
-- Set the environment of the calling function
setfenv(stacklevel, envify(_ENV))
end
local function try_add_env()
if not getfenv(4)._ENV then
add_env(4)
end
end
local function sethook()
debug.sethook(function()
-- Catch & silence errors when attempting to change the environment of Lua builtins
pcall(try_add_env)
end, "c")
end
sethook()
function test()
_ENV = {_G = _G, assert = assert}
assert(not math)
assert(_ENV.assert)
_ENV = {assert = assert}
assert(_ENV and not _G)
end
test()
See also the other way around: Implementing setfenv in Lua 5.2 and above by Leafo.
Limitations
It is not possible to "create" an _ENV upvalue for a given function after it has been loaded.
Thus code working with the _ENV upvalue through the debug library
such as Leafo's port of setfenv and getfenv to Lua 5.2+ won't work.
This is nothing but a demonstration, and not in the slightest suitable for serious programming; it is likely to introduce confusion since it messes with environments, and it severely reduces performance, hooking every (env.) variable access and function call.