# HG changeset patch # User Bram Moolenaar # Date 1618344005 -7200 # Node ID d2f9bdd938fad820e3339c88be02debd3a449fb5 # Parent 21feee770b969f451f35d3b92ee5879b119330b2 patch 8.2.2760: Vim9: no error for changing a for loop variable Commit: https://github.com/vim/vim/commit/f6a8d420a8d2924737f713de046947dcb487550c Author: Bram Moolenaar Date: Tue Apr 13 21:48:03 2021 +0200 patch 8.2.2760: Vim9: no error for changing a for loop variable Problem: Vim9: no error for changing a for loop variable. Solution: Make the loop variable read-only. (issue https://github.com/vim/vim/issues/8102) diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -1351,7 +1351,8 @@ set_var_lval( { typval_T tv; - if (flags & (ASSIGN_CONST | ASSIGN_FINAL)) + if ((flags & (ASSIGN_CONST | ASSIGN_FINAL)) + && (flags & ASSIGN_FOR_LOOP) == 0) { emsg(_(e_cannot_mod)); *endp = cc; @@ -1390,7 +1391,8 @@ set_var_lval( listitem_T *ll_li = lp->ll_li; int ll_n1 = lp->ll_n1; - if (flags & (ASSIGN_CONST | ASSIGN_FINAL)) + if ((flags & (ASSIGN_CONST | ASSIGN_FINAL)) + && (flags & ASSIGN_FOR_LOOP) == 0) { emsg(_("E996: Cannot lock a range")); return; @@ -1449,7 +1451,8 @@ set_var_lval( /* * Assign to a List or Dictionary item. */ - if (flags & (ASSIGN_CONST | ASSIGN_FINAL)) + if ((flags & (ASSIGN_CONST | ASSIGN_FINAL)) + && (flags & ASSIGN_FOR_LOOP) == 0) { emsg(_("E996: Cannot lock a list or dict")); return; @@ -1775,7 +1778,9 @@ next_for_item(void *fi_void, char_u *arg { forinfo_T *fi = (forinfo_T *)fi_void; int result; - int flag = in_vim9script() ? ASSIGN_DECL : 0; + int flag = ASSIGN_FOR_LOOP | (in_vim9script() + ? (ASSIGN_FINAL | ASSIGN_DECL | ASSIGN_NO_MEMBER_TYPE) + : 0); listitem_T *item; if (fi->fi_blob != NULL) diff --git a/src/evalvars.c b/src/evalvars.c --- a/src/evalvars.c +++ b/src/evalvars.c @@ -1315,7 +1315,8 @@ ex_let_one( // ":let $VAR = expr": Set environment variable. if (*arg == '$') { - if (flags & (ASSIGN_CONST | ASSIGN_FINAL)) + if ((flags & (ASSIGN_CONST | ASSIGN_FINAL)) + && (flags & ASSIGN_FOR_LOOP) == 0) { emsg(_("E996: Cannot lock an environment variable")); return NULL; @@ -1365,9 +1366,11 @@ ex_let_one( // ":let &option = expr": Set option value. // ":let &l:option = expr": Set local option value. // ":let &g:option = expr": Set global option value. + // ":for &ts in range(8)": Set option value for for loop else if (*arg == '&') { - if (flags & (ASSIGN_CONST | ASSIGN_FINAL)) + if ((flags & (ASSIGN_CONST | ASSIGN_FINAL)) + && (flags & ASSIGN_FOR_LOOP) == 0) { emsg(_(e_const_option)); return NULL; @@ -1466,7 +1469,8 @@ ex_let_one( // ":let @r = expr": Set register contents. else if (*arg == '@') { - if (flags & (ASSIGN_CONST | ASSIGN_FINAL)) + if ((flags & (ASSIGN_CONST | ASSIGN_FINAL)) + && (flags & ASSIGN_FOR_LOOP) == 0) { emsg(_("E996: Cannot lock a register")); return NULL; @@ -3158,7 +3162,7 @@ set_var_const( type_T *type, typval_T *tv_arg, int copy, // make copy of value in "tv" - int flags, // ASSIGN_CONST, ASSIGN_FINAL, etc. + int flags_arg, // ASSIGN_CONST, ASSIGN_FINAL, etc. int var_idx) // index for ":let [a, b] = list" { typval_T *tv = tv_arg; @@ -3169,6 +3173,7 @@ set_var_const( int is_script_local; int vim9script = in_vim9script(); int var_in_vim9script; + int flags = flags_arg; ht = find_var_ht(name, &varname); if (ht == NULL || *varname == NUL) @@ -3187,6 +3192,11 @@ set_var_const( vim9_declare_error(name); goto failed; } + if ((flags & ASSIGN_FOR_LOOP) && name[1] == ':' + && vim_strchr((char_u *)"gwbt", name[0]) != NULL) + // Do not make g:var, w:var, b:var or t:var final. + flags &= ~ASSIGN_FINAL; + var_in_vim9script = is_script_local && current_script_is_vim9(); if (var_in_vim9script && name[0] == '_' && name[1] == NUL) { @@ -3220,7 +3230,8 @@ set_var_const( // Item already exists. Allowed to replace when reloading. if ((di->di_flags & DI_FLAGS_RELOAD) == 0) { - if (flags & (ASSIGN_CONST | ASSIGN_FINAL)) + if ((flags & (ASSIGN_CONST | ASSIGN_FINAL)) + && (flags & ASSIGN_FOR_LOOP) == 0) { emsg(_(e_cannot_mod)); goto failed; @@ -3255,7 +3266,8 @@ set_var_const( // A Vim9 script-local variable is also present in sn_all_vars and // sn_var_vals. It may set "type" from "tv". if (var_in_vim9script) - update_vim9_script_var(FALSE, di, flags, tv, &type); + update_vim9_script_var(FALSE, di, flags, tv, &type, + (flags & ASSIGN_NO_MEMBER_TYPE) == 0); } // existing variable, need to clear the value @@ -3353,7 +3365,8 @@ set_var_const( // A Vim9 script-local variable is also added to sn_all_vars and // sn_var_vals. It may set "type" from "tv". if (var_in_vim9script) - update_vim9_script_var(TRUE, di, flags, tv, &type); + update_vim9_script_var(TRUE, di, flags, tv, &type, + (flags & ASSIGN_NO_MEMBER_TYPE) == 0); } if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim --- a/src/testdir/test_vim9_script.vim +++ b/src/testdir/test_vim9_script.vim @@ -2393,6 +2393,14 @@ def Test_for_loop_fails() g:adict = {a: 1} CheckDefExecFailure(['for i in g:adict', 'echo 3', 'endfor'], 'E1177: For loop on dict not supported') unlet g:adict + + var lines =<< trim END + var d: list> = [{a: 0}] + for e in d + e = {a: 0, b: ''} + endfor + END + CheckDefAndScriptFailure2(lines, 'E1018:', 'E46:', 3) enddef def Test_for_loop_script_var() diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -751,6 +751,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 2760, +/**/ 2759, /**/ 2758, diff --git a/src/vim.h b/src/vim.h --- a/src/vim.h +++ b/src/vim.h @@ -2158,6 +2158,7 @@ typedef enum { #define ASSIGN_DECL 0x08 // may declare variable if it does not exist #define ASSIGN_UNPACK 0x10 // using [a, b] = list #define ASSIGN_NO_MEMBER_TYPE 0x20 // use "any" for list and dict member type +#define ASSIGN_FOR_LOOP 0x40 // assigning to loop variable #include "ex_cmds.h" // Ex command defines #include "spell.h" // spell checking stuff diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -7590,7 +7590,7 @@ compile_for(char_u *arg_start, cctx_T *c // Reserve a variable to store "var". // TODO: check for type - var_lvar = reserve_local(cctx, arg, varlen, FALSE, &t_any); + var_lvar = reserve_local(cctx, arg, varlen, TRUE, &t_any); if (var_lvar == NULL) // out of memory or used as an argument goto failed;