New syntax for to-be-closed variables

The new syntax is <local *toclose x = f()>. The mark '*' allows other
attributes to be added later without the need of new keywords; it
also allows better error messages.  The API function was also renamed
('lua_tobeclosed' -> 'lua_toclose').
This commit is contained in:
Roberto Ierusalimschy
2018-11-07 10:03:05 -02:00
parent 5e76a4fd31
commit b8fed93215
8 changed files with 48 additions and 47 deletions

2
lapi.c
View File

@@ -1207,7 +1207,7 @@ LUA_API int lua_next (lua_State *L, int idx) {
} }
LUA_API void lua_tobeclosed (lua_State *L) { LUA_API void lua_toclose (lua_State *L) {
int nresults = L->ci->nresults; int nresults = L->ci->nresults;
luaF_newtbcupval(L, L->top - 1); /* create new to-be-closed upvalue */ luaF_newtbcupval(L, L->top - 1); /* create new to-be-closed upvalue */
if (!hastocloseCfunc(nresults)) /* function not marked yet? */ if (!hastocloseCfunc(nresults)) /* function not marked yet? */

View File

@@ -1546,16 +1546,15 @@ static void localfunc (LexState *ls) {
} }
static void commonlocalstat (LexState *ls, TString *firstvar) { static void commonlocalstat (LexState *ls) {
/* stat -> LOCAL NAME {',' NAME} ['=' explist] */ /* stat -> LOCAL NAME {',' NAME} ['=' explist] */
int nvars = 1; int nvars = 0;
int nexps; int nexps;
expdesc e; expdesc e;
new_localvar(ls, firstvar); do {
while (testnext(ls, ',')) {
new_localvar(ls, str_checkname(ls)); new_localvar(ls, str_checkname(ls));
nvars++; nvars++;
} } while (testnext(ls, ','));
if (testnext(ls, '=')) if (testnext(ls, '='))
nexps = explist(ls, &e); nexps = explist(ls, &e);
else { else {
@@ -1567,8 +1566,12 @@ static void commonlocalstat (LexState *ls, TString *firstvar) {
} }
static void scopedlocalstat (LexState *ls) { static void tocloselocalstat (LexState *ls) {
FuncState *fs = ls->fs; FuncState *fs = ls->fs;
TString *attr = str_checkname(ls);
if (strcmp(getstr(attr), "toclose") != 0)
luaK_semerror(ls,
luaO_pushfstring(ls->L, "unknown attribute '%s'", getstr(attr)));
new_localvar(ls, str_checkname(ls)); new_localvar(ls, str_checkname(ls));
checknext(ls, '='); checknext(ls, '=');
exp1(ls, 0); exp1(ls, 0);
@@ -1580,13 +1583,11 @@ static void scopedlocalstat (LexState *ls) {
static void localstat (LexState *ls) { static void localstat (LexState *ls) {
/* stat -> LOCAL NAME {',' NAME} ['=' explist] /* stat -> LOCAL NAME {',' NAME} ['=' explist]
| LOCAL SCOPED NAME '=' exp */ | LOCAL *toclose NAME '=' exp */
TString *firstvar = str_checkname(ls); if (testnext(ls, '*'))
if (ls->t.token == TK_NAME && tocloselocalstat(ls);
eqshrstr(firstvar, luaS_newliteral(ls->L, "scoped")))
scopedlocalstat(ls);
else else
commonlocalstat(ls, firstvar); commonlocalstat(ls);
} }

View File

@@ -1550,8 +1550,8 @@ static struct X { int x; } x;
int i = getindex; int i = getindex;
return lua_yieldk(L1, nres, i, Cfunck); return lua_yieldk(L1, nres, i, Cfunck);
} }
else if EQ("tobeclosed") { else if EQ("toclose") {
lua_tobeclosed(L); lua_toclose(L);
} }
else luaL_error(L, "unknown instruction %s", buff); else luaL_error(L, "unknown instruction %s", buff);
} }

2
lua.h
View File

@@ -333,7 +333,7 @@ LUA_API size_t (lua_stringtonumber) (lua_State *L, const char *s);
LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud); LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud);
LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud); LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud);
LUA_API void (lua_tobeclosed) (lua_State *L); LUA_API void (lua_toclose) (lua_State *L);
/* /*

View File

@@ -987,7 +987,7 @@ do
local a = T.testC([[ local a = T.testC([[
call 0 1 # create resource call 0 1 # create resource
tobeclosed # mark it to be closed toclose # mark it to be closed
return 1 return 1
]], newresource) ]], newresource)
assert(a[1] == 11) assert(a[1] == 11)
@@ -996,7 +996,7 @@ do
-- repeat the test, but calling function in a 'multret' context -- repeat the test, but calling function in a 'multret' context
local a = {T.testC([[ local a = {T.testC([[
call 0 1 # create resource call 0 1 # create resource
tobeclosed # mark it to be closed toclose # mark it to be closed
return 2 return 2
]], newresource)} ]], newresource)}
assert(type(a[1]) == "string" and a[2][1] == 11) assert(type(a[1]) == "string" and a[2][1] == 11)
@@ -1005,7 +1005,7 @@ do
-- error -- error
local a, b = pcall(T.testC, [[ local a, b = pcall(T.testC, [[
call 0 1 # create resource call 0 1 # create resource
tobeclosed # mark it to be closed toclose # mark it to be closed
error # resource is the error object error # resource is the error object
]], newresource) ]], newresource)
assert(a == false and b[1] == 11) assert(a == false and b[1] == 11)
@@ -1019,10 +1019,10 @@ do
local a = T.testC([[ local a = T.testC([[
pushvalue 2 pushvalue 2
call 0 1 # create resource call 0 1 # create resource
tobeclosed # mark it to be closed toclose # mark it to be closed
pushvalue 2 pushvalue 2
call 0 1 # create another resource call 0 1 # create another resource
tobeclosed # mark it to be closed toclose # mark it to be closed
pushvalue 3 pushvalue 3
pushint 2 # there should be two open resources pushint 2 # there should be two open resources
call 1 0 call 1 0
@@ -1102,7 +1102,7 @@ end)
testamem("to-be-closed variables", function() testamem("to-be-closed variables", function()
local flag local flag
do do
local scoped x = function () flag = true end local *toclose x = function () flag = true end
flag = false flag = false
local x = {} local x = {}
end end

View File

@@ -125,7 +125,7 @@ do
-- closing file by scope -- closing file by scope
local F = nil local F = nil
do do
local scoped f = assert(io.open(file, "w")) local *toclose f = assert(io.open(file, "w"))
F = f F = f
end end
assert(tostring(F) == "file (closed)") assert(tostring(F) == "file (closed)")
@@ -135,7 +135,7 @@ assert(os.remove(file))
do do
-- test writing/reading numbers -- test writing/reading numbers
local scoped f = assert(io.open(file, "w")) local *toclose f = assert(io.open(file, "w"))
f:write(maxint, '\n') f:write(maxint, '\n')
f:write(string.format("0X%x\n", maxint)) f:write(string.format("0X%x\n", maxint))
f:write("0xABCp-3", '\n') f:write("0xABCp-3", '\n')
@@ -158,7 +158,7 @@ assert(os.remove(file))
-- testing multiple arguments to io.read -- testing multiple arguments to io.read
do do
local scoped f = assert(io.open(file, "w")) local *toclose f = assert(io.open(file, "w"))
f:write[[ f:write[[
a line a line
another line another line

View File

@@ -258,7 +258,7 @@ do
::L2:: goto L3 ::L2:: goto L3
::L1:: do ::L1:: do
local scoped a = function () X = true end local *toclose a = function () X = true end
assert(X == nil) assert(X == nil)
if a then goto L2 end -- jumping back out of scope of 'a' if a then goto L2 end -- jumping back out of scope of 'a'
end end

View File

@@ -181,9 +181,9 @@ local function stack(n) n = ((n == 0) or stack(n - 1)) end
do do
local a = {} local a = {}
do do
local scoped x = setmetatable({"x"}, {__close = function (self) local *toclose x = setmetatable({"x"}, {__close = function (self)
a[#a + 1] = self[1] end}) a[#a + 1] = self[1] end})
local scoped y = function (x) assert(x == nil); a[#a + 1] = "y" end local *toclose y = function (x) assert(x == nil); a[#a + 1] = "y" end
a[#a + 1] = "in" a[#a + 1] = "in"
end end
a[#a + 1] = "out" a[#a + 1] = "out"
@@ -197,7 +197,7 @@ do
-- closing functions do not corrupt returning values -- closing functions do not corrupt returning values
local function foo (x) local function foo (x)
local scoped _ = closescope local *toclose _ = closescope
return x, X, 23 return x, X, 23
end end
@@ -206,7 +206,7 @@ do
X = false X = false
foo = function (x) foo = function (x)
local scoped _ = closescope local *toclose _ = closescope
local y = 15 local y = 15
return y return y
end end
@@ -215,7 +215,7 @@ do
X = false X = false
foo = function () foo = function ()
local scoped x = closescope local *toclose x = closescope
return x return x
end end
@@ -228,13 +228,13 @@ do
-- to-be-closed variables must be closed in tail calls -- to-be-closed variables must be closed in tail calls
local X, Y local X, Y
local function foo () local function foo ()
local scoped _ = function () Y = 10 end local *toclose _ = function () Y = 10 end
assert(X == 20 and Y == nil) assert(X == 20 and Y == nil)
return 1,2,3 return 1,2,3
end end
local function bar () local function bar ()
local scoped _ = function () X = 20 end local *toclose _ = function () X = 20 end
return foo() return foo()
end end
@@ -245,11 +245,11 @@ end
do -- errors in __close do -- errors in __close
local log = {} local log = {}
local function foo (err) local function foo (err)
local scoped x = function (msg) log[#log + 1] = msg; error(1) end local *toclose x = function (msg) log[#log + 1] = msg; error(1) end
local scoped x1 = function (msg) log[#log + 1] = msg; end local *toclose x1 = function (msg) log[#log + 1] = msg; end
local scoped gc = function () collectgarbage() end local *toclose gc = function () collectgarbage() end
local scoped y = function (msg) log[#log + 1] = msg; error(2) end local *toclose y = function (msg) log[#log + 1] = msg; error(2) end
local scoped z = function (msg) log[#log + 1] = msg or 10; error(3) end local *toclose z = function (msg) log[#log + 1] = msg or 10; error(3) end
if err then error(4) end if err then error(4) end
end end
local stat, msg = pcall(foo, false) local stat, msg = pcall(foo, false)
@@ -267,8 +267,8 @@ end
if rawget(_G, "T") then if rawget(_G, "T") then
-- memory error inside closing function -- memory error inside closing function
local function foo () local function foo ()
local scoped y = function () T.alloccount() end local *toclose y = function () T.alloccount() end
local scoped x = setmetatable({}, {__close = function () local *toclose x = setmetatable({}, {__close = function ()
T.alloccount(0); local x = {} -- force a memory error T.alloccount(0); local x = {} -- force a memory error
end}) end})
error("a") -- common error inside the function's body error("a") -- common error inside the function's body
@@ -294,7 +294,7 @@ if rawget(_G, "T") then
end end
local function test () local function test ()
local scoped x = enter(0) -- set a memory limit local *toclose x = enter(0) -- set a memory limit
-- creation of previous upvalue will raise a memory error -- creation of previous upvalue will raise a memory error
os.exit(false) -- should not run os.exit(false) -- should not run
end end
@@ -309,14 +309,14 @@ if rawget(_G, "T") then
-- repeat test with extra closing upvalues -- repeat test with extra closing upvalues
local function test () local function test ()
local scoped xxx = function (msg) local *toclose xxx = function (msg)
assert(msg == "not enough memory"); assert(msg == "not enough memory");
error(1000) -- raise another error error(1000) -- raise another error
end end
local scoped xx = function (msg) local *toclose xx = function (msg)
assert(msg == "not enough memory"); assert(msg == "not enough memory");
end end
local scoped x = enter(0) -- set a memory limit local *toclose x = enter(0) -- set a memory limit
-- creation of previous upvalue will raise a memory error -- creation of previous upvalue will raise a memory error
os.exit(false) -- should not run os.exit(false) -- should not run
end end
@@ -333,9 +333,9 @@ do
local x = false local x = false
local y = false local y = false
local co = coroutine.create(function () local co = coroutine.create(function ()
local scoped xv = function () x = true end local *toclose xv = function () x = true end
do do
local scoped yv = function () y = true end local *toclose yv = function () y = true end
coroutine.yield(100) -- yield doesn't close variable coroutine.yield(100) -- yield doesn't close variable
end end
coroutine.yield(200) -- yield doesn't close variable coroutine.yield(200) -- yield doesn't close variable
@@ -353,7 +353,7 @@ end
-- a suspended coroutine should not close its variables when collected -- a suspended coroutine should not close its variables when collected
local co local co
co = coroutine.wrap(function() co = coroutine.wrap(function()
local scoped x = function () os.exit(false) end -- should not run local *toclose x = function () os.exit(false) end -- should not run
co = nil co = nil
coroutine.yield() coroutine.yield()
end) end)