Control variables in for loops are read only

This commit is contained in:
Roberto Ierusalimschy 2022-12-21 12:04:59 -03:00
parent 540d805226
commit b2f7b3b79f
5 changed files with 40 additions and 37 deletions

View File

@ -187,10 +187,10 @@ static int registerlocalvar (LexState *ls, FuncState *fs, TString *varname) {
/*
** Create a new local variable with the given 'name'. Return its index
** in the function.
** Create a new local variable with the given 'name' and given 'kind'.
** Return its index in the function.
*/
static int new_localvar (LexState *ls, TString *name) {
static int new_localvarkind (LexState *ls, TString *name, int kind) {
lua_State *L = ls->L;
FuncState *fs = ls->fs;
Dyndata *dyd = ls->dyd;
@ -200,11 +200,19 @@ static int new_localvar (LexState *ls, TString *name) {
luaM_growvector(L, dyd->actvar.arr, dyd->actvar.n + 1,
dyd->actvar.size, Vardesc, USHRT_MAX, "local variables");
var = &dyd->actvar.arr[dyd->actvar.n++];
var->vd.kind = VDKREG; /* default */
var->vd.kind = kind; /* default */
var->vd.name = name;
return dyd->actvar.n - 1 - fs->firstlocal;
}
/*
** Create a new local variable with the given 'name' and regular kind.
*/
static int new_localvar (LexState *ls, TString *name) {
return new_localvarkind(ls, name, VDKREG);
}
#define new_localvarliteral(ls,v) \
new_localvar(ls, \
luaX_newstring(ls, "" v, (sizeof(v)/sizeof(char)) - 1));
@ -1573,7 +1581,7 @@ static void fornum (LexState *ls, TString *varname, int line) {
new_localvarliteral(ls, "(for state)");
new_localvarliteral(ls, "(for state)");
new_localvarliteral(ls, "(for state)");
new_localvar(ls, varname);
new_localvarkind(ls, varname, RDKCONST); /* control variable */
checknext(ls, '=');
exp1(ls); /* initial value */
checknext(ls, ',');
@ -1601,8 +1609,8 @@ static void forlist (LexState *ls, TString *indexname) {
new_localvarliteral(ls, "(for state)");
new_localvarliteral(ls, "(for state)");
new_localvarliteral(ls, "(for state)");
/* create declared variables */
new_localvar(ls, indexname);
new_localvarkind(ls, indexname, RDKCONST); /* control variable */
/* other declared variables */
while (testnext(ls, ',')) {
new_localvar(ls, str_checkname(ls));
nvars++;
@ -1728,14 +1736,14 @@ static void localstat (LexState *ls) {
FuncState *fs = ls->fs;
int toclose = -1; /* index of to-be-closed variable (if any) */
Vardesc *var; /* last variable */
int vidx, kind; /* index and kind of last variable */
int vidx; /* index of last variable */
int nvars = 0;
int nexps;
expdesc e;
do {
vidx = new_localvar(ls, str_checkname(ls));
kind = getlocalattribute(ls);
getlocalvardesc(fs, vidx)->vd.kind = kind;
TString *vname = str_checkname(ls);
int kind = getlocalattribute(ls);
vidx = new_localvarkind(ls, vname, kind);
if (kind == RDKTOCLOSE) { /* to-be-closed? */
if (toclose != -1) /* one already present? */
luaK_semerror(ls, "multiple to-be-closed variables in local list");

View File

@ -1467,7 +1467,7 @@ It has the following syntax:
exp @bnfter{,} exp @bnfopt{@bnfter{,} exp} @Rw{do} block @Rw{end}}
}
The given identifier (@bnfNter{Name}) defines the control variable,
which is a new variable local to the loop body (@emph{block}).
which is a new read-only variable local to the loop body (@emph{block}).
The loop starts by evaluating once the three control expressions.
Their values are called respectively
@ -1499,11 +1499,6 @@ For integer loops,
the control variable never wraps around;
instead, the loop ends in case of an overflow.
You should not change the value of the control variable
during the loop.
If you need its value after the loop,
assign it to another variable before exiting the loop.
}
@sect4{@title{The generic @Rw{for} loop}
@ -1526,7 +1521,8 @@ for @rep{var_1}, @Cdots, @rep{var_n} in @rep{explist} do @rep{body} end
works as follows.
The names @rep{var_i} declare loop variables local to the loop body.
The first of these variables is the @emph{control variable}.
The first of these variables is the @emph{control variable},
which is a read-only variable.
The loop starts by evaluating @rep{explist}
to produce four values:
@ -1550,9 +1546,6 @@ to-be-closed variable @see{to-be-closed},
which can be used to release resources when the loop ends.
Otherwise, it does not interfere with the loop.
You should not change the value of the control variable
during the loop.
}
}
@ -9156,6 +9149,9 @@ change between versions.
@itemize{
@item{
The control variable in @Rw{for} loops are read only.
If you need to change it,
declare a local variable with the same name in the loop body.
}
}

View File

@ -236,7 +236,7 @@ package.path = oldpath
local fname = "file_does_not_exist2"
local m, err = pcall(require, fname)
for t in string.gmatch(package.path..";"..package.cpath, "[^;]+") do
t = string.gsub(t, "?", fname)
local t = string.gsub(t, "?", fname)
assert(string.find(err, t, 1, true))
end

View File

@ -60,32 +60,29 @@ end
-- testing closures with 'for' control variable
a = {}
for i=1,10 do
a[i] = {set = function(x) i=x end, get = function () return i end}
a[i] = function () return i end
if i == 3 then break end
end
assert(a[4] == undef)
a[1].set(10)
assert(a[2].get() == 2)
a[2].set('a')
assert(a[3].get() == 3)
assert(a[2].get() == 'a')
assert(a[2]() == 2)
assert(a[3]() == 3)
a = {}
local t = {"a", "b"}
for i = 1, #t do
local k = t[i]
a[i] = {set = function(x, y) i=x; k=y end,
a[i] = {set = function(x) k=x end,
get = function () return i, k end}
if i == 2 then break end
end
a[1].set(10, 20)
a[1].set(10)
local r,s = a[2].get()
assert(r == 2 and s == 'b')
r,s = a[1].get()
assert(r == 10 and s == 20)
a[2].set('a', 'b')
assert(r == 1 and s == 10)
a[2].set('a')
r,s = a[2].get()
assert(r == "a" and s == "b")
assert(r == 2 and s == "a")
-- testing closures with 'for' control variable x break

View File

@ -609,10 +609,12 @@ do
a = 0; for i=1.0, 0.99999, -1 do a=a+1 end; assert(a==1)
end
do -- changing the control variable
local a
a = 0; for i = 1, 10 do a = a + 1; i = "x" end; assert(a == 10)
a = 0; for i = 10.0, 1, -1 do a = a + 1; i = "x" end; assert(a == 10)
do -- attempt to change the control variable
local st, msg = load "for i = 1, 10 do i = 10 end"
assert(not st and string.find(msg, "assign to const variable 'i'"))
local st, msg = load "for v, k in pairs{} do v = 10 end"
assert(not st and string.find(msg, "assign to const variable 'v'"))
end
-- conversion