Jelajahi Sumber

Control variables in for loops are read only

Roberto Ierusalimschy 2 tahun lalu
induk
melakukan
b2f7b3b79f
5 mengubah file dengan 40 tambahan dan 37 penghapusan
  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;
   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");

+ 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}}
 }
 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.
 }
 
 }

+ 1 - 1
testes/attrib.lua

@@ -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
 

+ 8 - 11
testes/closure.lua

@@ -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

+ 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)
 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