# HG changeset patch # User Bram Moolenaar # Date 1610054105 -3600 # Node ID 7f0fc2ab90e3a2674fc6c3b9f41a89efbfeda81e # Parent 708e07fe1fb469c3a65756f8fc8f2e65e86948b7 patch 8.2.2311: Vim9: cannot assign to variable that shadows command modifier Commit: https://github.com/vim/vim/commit/17126b13969c3b91516a8e9ff80fb6a1f6924d40 Author: Bram Moolenaar Date: Thu Jan 7 22:03:02 2021 +0100 patch 8.2.2311: Vim9: cannot assign to variable that shadows command modifier Problem: Vim9: cannot assign to a variable that shadows a command modifier. Solution: Check for assignment after possible command modifier. (closes #7632) diff --git a/src/ex_docmd.c b/src/ex_docmd.c --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -2738,6 +2738,25 @@ parse_command_modifiers( } p = skip_range(eap->cmd, TRUE, NULL); + + // In Vim9 script a variable can shadow a command modifier: + // verbose = 123 + // verbose += 123 + // silent! verbose = func() + // verbose.member = 2 + // verbose[expr] = 2 + if (in_vim9script()) + { + char_u *s; + + for (s = p; ASCII_ISALPHA(*s); ++s) + ; + s = skipwhite(s); + if (vim_strchr((char_u *)".[=", *s) != NULL + || (*s != NUL && s[1] == '=')) + break; + } + switch (*p) { // When adding an entry, also modify cmd_exists(). diff --git a/src/testdir/test_vim9_assign.vim b/src/testdir/test_vim9_assign.vim --- a/src/testdir/test_vim9_assign.vim +++ b/src/testdir/test_vim9_assign.vim @@ -1464,5 +1464,26 @@ def Test_unlet() assert_equal('', $ENVVAR) enddef +def Test_assign_command_modifier() + var lines =<< trim END + var verbose = 0 + verbose = 1 + assert_equal(1, verbose) + silent verbose = 2 + assert_equal(2, verbose) + silent verbose += 2 + assert_equal(4, verbose) + silent verbose -= 1 + assert_equal(3, verbose) + + var topleft = {one: 1} + sandbox topleft.one = 3 + assert_equal({one: 3}, topleft) + leftabove topleft[' '] = 4 + assert_equal({one: 3, ' ': 4}, topleft) + END + CheckDefAndScriptSuccess(lines) +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker 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 */ /**/ + 2311, +/**/ 2310, /**/ 2309, diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -6217,6 +6217,77 @@ theend: } /* + * Check for an assignment at "eap->cmd", compile it if found. + * Return NOTDONE if there is none, FAIL for failure, OK if done. + */ + static int +may_compile_assignment(exarg_T *eap, char_u **line, cctx_T *cctx) +{ + char_u *pskip; + char_u *p; + + // Assuming the command starts with a variable or function name, + // find what follows. + // Skip over "var.member", "var[idx]" and the like. + // Also "&opt = val", "$ENV = val" and "@r = val". + pskip = (*eap->cmd == '&' || *eap->cmd == '$' || *eap->cmd == '@') + ? eap->cmd + 1 : eap->cmd; + p = to_name_end(pskip, TRUE); + if (p > eap->cmd && *p != NUL) + { + char_u *var_end; + int oplen; + int heredoc; + + if (eap->cmd[0] == '@') + var_end = eap->cmd + 2; + else + var_end = find_name_end(pskip, NULL, NULL, + FNE_CHECK_START | FNE_INCL_BR); + oplen = assignment_len(skipwhite(var_end), &heredoc); + if (oplen > 0) + { + size_t len = p - eap->cmd; + + // Recognize an assignment if we recognize the variable + // name: + // "g:var = expr" + // "local = expr" where "local" is a local var. + // "script = expr" where "script" is a script-local var. + // "import = expr" where "import" is an imported var + // "&opt = expr" + // "$ENV = expr" + // "@r = expr" + if (*eap->cmd == '&' + || *eap->cmd == '$' + || *eap->cmd == '@' + || ((len) > 2 && eap->cmd[1] == ':') + || lookup_local(eap->cmd, len, NULL, cctx) == OK + || arg_exists(eap->cmd, len, NULL, NULL, NULL, cctx) == OK + || script_var_exists(eap->cmd, len, FALSE, cctx) == OK + || find_imported(eap->cmd, len, cctx) != NULL) + { + *line = compile_assignment(eap->cmd, eap, CMD_SIZE, cctx); + if (*line == NULL || *line == eap->cmd) + return FAIL; + return OK; + } + } + } + + if (*eap->cmd == '[') + { + // [var, var] = expr + *line = compile_assignment(eap->cmd, eap, CMD_SIZE, cctx); + if (*line == NULL) + return FAIL; + if (*line != eap->cmd) + return OK; + } + return NOTDONE; +} + +/* * Check if "name" can be "unlet". */ int @@ -7838,68 +7909,14 @@ compile_def_function(ufunc_T *ufunc, int if (!starts_with_colon) { - char_u *pskip; - - // Assuming the command starts with a variable or function name, - // find what follows. - // Skip over "var.member", "var[idx]" and the like. - // Also "&opt = val", "$ENV = val" and "@r = val". - pskip = (*ea.cmd == '&' || *ea.cmd == '$' || *ea.cmd == '@') - ? ea.cmd + 1 : ea.cmd; - p = to_name_end(pskip, TRUE); - if (p > ea.cmd && *p != NUL) - { - char_u *var_end; - int oplen; - int heredoc; - - if (ea.cmd[0] == '@') - var_end = ea.cmd + 2; - else - var_end = find_name_end(pskip, NULL, NULL, - FNE_CHECK_START | FNE_INCL_BR); - oplen = assignment_len(skipwhite(var_end), &heredoc); - if (oplen > 0) - { - size_t len = p - ea.cmd; - - // Recognize an assignment if we recognize the variable - // name: - // "g:var = expr" - // "local = expr" where "local" is a local var. - // "script = expr" where "script" is a script-local var. - // "import = expr" where "import" is an imported var - // "&opt = expr" - // "$ENV = expr" - // "@r = expr" - if (*ea.cmd == '&' - || *ea.cmd == '$' - || *ea.cmd == '@' - || ((len) > 2 && ea.cmd[1] == ':') - || lookup_local(ea.cmd, len, NULL, &cctx) == OK - || arg_exists(ea.cmd, len, NULL, NULL, - NULL, &cctx) == OK - || script_var_exists(ea.cmd, len, - FALSE, &cctx) == OK - || find_imported(ea.cmd, len, &cctx) != NULL) - { - line = compile_assignment(ea.cmd, &ea, CMD_SIZE, &cctx); - if (line == NULL || line == ea.cmd) - goto erret; - goto nextline; - } - } - } - - if (*ea.cmd == '[') - { - // [var, var] = expr - line = compile_assignment(ea.cmd, &ea, CMD_SIZE, &cctx); - if (line == NULL) - goto erret; - if (line != ea.cmd) - goto nextline; - } + int assign; + + // Check for assignment after command modifiers. + assign = may_compile_assignment(&ea, &line, &cctx); + if (assign == OK) + goto nextline; + if (assign == FAIL) + goto erret; } /*