Przeglądaj źródła

Control variables in for loops are read only

Roberto Ierusalimschy 2 lat temu
rodzic
commit
b2f7b3b79f
5 zmienionych plików z 40 dodań i 37 usunięć
  1. 19 11
      lparser.c
  2. 6 10
      manual/manual.of
  3. 1 1
      testes/attrib.lua
  4. 8 11
      testes/closure.lua
  5. 6 4
      testes/nextvar.lua

+ 19 - 11
lparser.c

@@ -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;
   lua_State *L = ls->L;
   FuncState *fs = ls->fs;
   FuncState *fs = ls->fs;
   Dyndata *dyd = ls->dyd;
   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,
   luaM_growvector(L, dyd->actvar.arr, dyd->actvar.n + 1,
                   dyd->actvar.size, Vardesc, USHRT_MAX, "local variables");
                   dyd->actvar.size, Vardesc, USHRT_MAX, "local variables");
   var = &dyd->actvar.arr[dyd->actvar.n++];
   var = &dyd->actvar.arr[dyd->actvar.n++];
-  var->vd.kind = VDKREG;  /* default */
+  var->vd.kind = kind;  /* default */
   var->vd.name = name;
   var->vd.name = name;
   return dyd->actvar.n - 1 - fs->firstlocal;
   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) \
 #define new_localvarliteral(ls,v) \
     new_localvar(ls,  \
     new_localvar(ls,  \
       luaX_newstring(ls, "" v, (sizeof(v)/sizeof(char)) - 1));
       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_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, '=');
   checknext(ls, '=');
   exp1(ls);  /* initial value */
   exp1(ls);  /* initial value */
   checknext(ls, ',');
   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)");
   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, ',')) {
   while (testnext(ls, ',')) {
     new_localvar(ls, str_checkname(ls));
     new_localvar(ls, str_checkname(ls));
     nvars++;
     nvars++;
@@ -1728,14 +1736,14 @@ static void localstat (LexState *ls) {
   FuncState *fs = ls->fs;
   FuncState *fs = ls->fs;
   int toclose = -1;  /* index of to-be-closed variable (if any) */
   int toclose = -1;  /* index of to-be-closed variable (if any) */
   Vardesc *var;  /* last variable */
   Vardesc *var;  /* last variable */
-  int vidx, kind;  /* index and kind of last variable */
+  int vidx;  /* index of last variable */
   int nvars = 0;
   int nvars = 0;
   int nexps;
   int nexps;
   expdesc e;
   expdesc e;
   do {
   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 (kind == RDKTOCLOSE) {  /* to-be-closed? */
       if (toclose != -1)  /* one already present? */
       if (toclose != -1)  /* one already present? */
         luaK_semerror(ls, "multiple to-be-closed variables in local list");
         luaK_semerror(ls, "multiple to-be-closed variables in local list");

+ 6 - 10
manual/manual.of

@@ -1467,7 +1467,7 @@ It has the following syntax:
   exp @bnfter{,} exp @bnfopt{@bnfter{,} exp} @Rw{do} block @Rw{end}}
   exp @bnfter{,} exp @bnfopt{@bnfter{,} exp} @Rw{do} block @Rw{end}}
 }
 }
 The given identifier (@bnfNter{Name}) defines the control variable,
 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.
 The loop starts by evaluating once the three control expressions.
 Their values are called respectively
 Their values are called respectively
@@ -1499,11 +1499,6 @@ For integer loops,
 the control variable never wraps around;
 the control variable never wraps around;
 instead, the loop ends in case of an overflow.
 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}
 @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.
 works as follows.
 
 
 The names @rep{var_i} declare loop variables local to the loop body.
 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}
 The loop starts by evaluating @rep{explist}
 to produce four values:
 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.
 which can be used to release resources when the loop ends.
 Otherwise, it does not interfere with the loop.
 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{
 @itemize{
 
 
 @item{
 @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.
 }
 }
 
 
 }
 }

+ 1 - 1
testes/attrib.lua

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

+ 8 - 11
testes/closure.lua

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

+ 6 - 4
testes/nextvar.lua

@@ -609,10 +609,12 @@ do
   a = 0; for i=1.0, 0.99999, -1 do a=a+1 end; assert(a==1)
   a = 0; for i=1.0, 0.99999, -1 do a=a+1 end; assert(a==1)
 end
 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
 end
 
 
 -- conversion
 -- conversion