diff options
author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2018-12-04 15:01:42 -0200 |
---|---|---|
committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2018-12-04 15:01:42 -0200 |
commit | 28d829c86712ce5bc453feccc5129a32f78d80c0 (patch) | |
tree | 58197e763ca643bbe4a042372927bf81092b141c | |
parent | 6d04537ea660fd12fc16c328366b701fabaf4919 (diff) | |
download | lua-github-28d829c86712ce5bc453feccc5129a32f78d80c0.tar.gz |
Calls cannot be tail in the scope of a to-be-closed variable
A to-be-closed variable must be closed when a block ends, so even
a 'return foo()' cannot directly returns the results of 'foo'; the
function must close the scope before returning.
-rw-r--r-- | lparser.c | 5 | ||||
-rw-r--r-- | lvm.c | 2 | ||||
-rw-r--r-- | manual/manual.of | 17 | ||||
-rw-r--r-- | testes/locals.lua | 13 |
4 files changed, 21 insertions, 16 deletions
@@ -53,6 +53,7 @@ typedef struct BlockCnt { lu_byte nactvar; /* # active locals outside the block */ lu_byte upval; /* true if some variable in the block is an upvalue */ lu_byte isloop; /* true if 'block' is a loop */ + lu_byte insidetbc; /* true if inside the scope of a to-be-closed var. */ } BlockCnt; @@ -510,6 +511,7 @@ static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isloop) { bl->firstlabel = fs->ls->dyd->label.n; bl->firstgoto = fs->ls->dyd->gt.n; bl->upval = 0; + bl->insidetbc = (fs->bl != NULL && fs->bl->insidetbc); bl->previous = fs->bl; fs->bl = bl; lua_assert(fs->freereg == fs->nactvar); @@ -1631,6 +1633,7 @@ static void tocloselocalstat (LexState *ls) { checknext(ls, '='); exp1(ls, 0); markupval(fs, fs->nactvar); + fs->bl->insidetbc = 1; /* in the scope of a to-be-closed variable */ adjustlocalvars(ls, 1); luaK_codeABC(fs, OP_TBC, fs->nactvar - 1, 0, 0); } @@ -1701,7 +1704,7 @@ static void retstat (LexState *ls) { nret = explist(ls, &e); /* optional return values */ if (hasmultret(e.k)) { luaK_setmultret(fs, &e); - if (e.k == VCALL && nret == 1) { /* tail call? */ + if (e.k == VCALL && nret == 1 && !fs->bl->insidetbc) { /* tail call? */ SET_OPCODE(getinstruction(fs,&e), OP_TAILCALL); lua_assert(GETARG_A(getinstruction(fs,&e)) == fs->nactvar); } @@ -1565,7 +1565,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) { if (nparams1) /* vararg function? */ delta = ci->u.l.nextraargs + nparams1; /* close upvalues from current call */ - ProtectNT(luaF_close(L, base, LUA_OK)); + luaF_close(L, base, -1); /* (no to-be-closed vars. here) */ updatestack(ci); } if (!ttisfunction(s2v(ra))) { /* not a function? */ diff --git a/manual/manual.of b/manual/manual.of index 3902f2f3..8b5e5d93 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -1538,9 +1538,6 @@ except that its value is @emph{closed} whenever the variable goes out of scope, including normal block termination, exiting its block by @Rw{break}/@Rw{goto}/@Rw{return}, or exiting by an error. -If a block ends in a tail call @see{functioncall}, -all variables of the caller function go out of scope -before the start of the callee function. To \emph{close} a value has the following meaning here: If the value of the variable when it goes out of scope is a function, @@ -2038,8 +2035,8 @@ A call of the form @T{f'@rep{string}'} is syntactic sugar for @T{f('@rep{string}')}; that is, the argument list is a single literal string. -A call of the form @T{return @rep{functioncall}} is called -a @def{tail call}. +A call of the form @T{return @rep{functioncall}} not in the +scope of a to-be-closed variable is called a @def{tail call}. Lua implements @def{proper tail calls} (or @emph{proper tail recursion}): in a tail call, @@ -2049,13 +2046,15 @@ a program can execute. However, a tail call erases any debug information about the calling function. Note that a tail call only happens with a particular syntax, -where the @Rw{return} has one single function call as argument; -this syntax makes the calling function return exactly -the returns of the called function. +where the @Rw{return} has one single function call as argument, +and it is outside the scope of any to-be-closed variable. +This syntax makes the calling function return exactly +the returns of the called function, +without any intervening action. So, none of the following examples are tail calls: @verbatim{ return (f(x)) -- results adjusted to 1 -return 2 * f(x) +return 2 * f(x) -- result multiplied by 2 return x, f(x) -- additional results f(x); return -- results discarded return x or f(x) -- results adjusted to 1 diff --git a/testes/locals.lua b/testes/locals.lua index 24681dd9..340af61c 100644 --- a/testes/locals.lua +++ b/testes/locals.lua @@ -225,21 +225,24 @@ end do - -- to-be-closed variables must be closed in tail calls + -- calls cannot be tail in the scope of to-be-closed variables local X, Y local function foo () local *toclose _ = function () Y = 10 end - assert(X == 20 and Y == nil) + assert(X == true and Y == nil) -- 'X' not closed yet return 1,2,3 end local function bar () - local *toclose _ = function () X = 20 end - return foo() + local *toclose _ = function () X = false end + X = true + do + return foo() -- not a tail call! + end end local a, b, c, d = bar() - assert(a == 1 and b == 2 and c == 3 and X == 20 and Y == 10 and d == nil) + assert(a == 1 and b == 2 and c == 3 and X == false and Y == 10 and d == nil) end |