Skip to content

Commit 17126b1

Browse files
committed
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)
1 parent 43b69b3 commit 17126b1

4 files changed

Lines changed: 120 additions & 61 deletions

File tree

src/ex_docmd.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2738,6 +2738,25 @@ parse_command_modifiers(
27382738
}
27392739

27402740
p = skip_range(eap->cmd, TRUE, NULL);
2741+
2742+
// In Vim9 script a variable can shadow a command modifier:
2743+
// verbose = 123
2744+
// verbose += 123
2745+
// silent! verbose = func()
2746+
// verbose.member = 2
2747+
// verbose[expr] = 2
2748+
if (in_vim9script())
2749+
{
2750+
char_u *s;
2751+
2752+
for (s = p; ASCII_ISALPHA(*s); ++s)
2753+
;
2754+
s = skipwhite(s);
2755+
if (vim_strchr((char_u *)".[=", *s) != NULL
2756+
|| (*s != NUL && s[1] == '='))
2757+
break;
2758+
}
2759+
27412760
switch (*p)
27422761
{
27432762
// When adding an entry, also modify cmd_exists().

src/testdir/test_vim9_assign.vim

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1464,5 +1464,26 @@ def Test_unlet()
14641464
assert_equal('', $ENVVAR)
14651465
enddef
14661466

1467+
def Test_assign_command_modifier()
1468+
var lines =<< trim END
1469+
var verbose = 0
1470+
verbose = 1
1471+
assert_equal(1, verbose)
1472+
silent verbose = 2
1473+
assert_equal(2, verbose)
1474+
silent verbose += 2
1475+
assert_equal(4, verbose)
1476+
silent verbose -= 1
1477+
assert_equal(3, verbose)
1478+
1479+
var topleft = {one: 1}
1480+
sandbox topleft.one = 3
1481+
assert_equal({one: 3}, topleft)
1482+
leftabove topleft[' '] = 4
1483+
assert_equal({one: 3, ' ': 4}, topleft)
1484+
END
1485+
CheckDefAndScriptSuccess(lines)
1486+
enddef
1487+
14671488

14681489
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,8 @@ static char *(features[]) =
750750

751751
static int included_patches[] =
752752
{ /* Add new patch number below this line */
753+
/**/
754+
2311,
753755
/**/
754756
2310,
755757
/**/

src/vim9compile.c

Lines changed: 78 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -6216,6 +6216,77 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
62166216
return ret;
62176217
}
62186218

6219+
/*
6220+
* Check for an assignment at "eap->cmd", compile it if found.
6221+
* Return NOTDONE if there is none, FAIL for failure, OK if done.
6222+
*/
6223+
static int
6224+
may_compile_assignment(exarg_T *eap, char_u **line, cctx_T *cctx)
6225+
{
6226+
char_u *pskip;
6227+
char_u *p;
6228+
6229+
// Assuming the command starts with a variable or function name,
6230+
// find what follows.
6231+
// Skip over "var.member", "var[idx]" and the like.
6232+
// Also "&opt = val", "$ENV = val" and "@r = val".
6233+
pskip = (*eap->cmd == '&' || *eap->cmd == '$' || *eap->cmd == '@')
6234+
? eap->cmd + 1 : eap->cmd;
6235+
p = to_name_end(pskip, TRUE);
6236+
if (p > eap->cmd && *p != NUL)
6237+
{
6238+
char_u *var_end;
6239+
int oplen;
6240+
int heredoc;
6241+
6242+
if (eap->cmd[0] == '@')
6243+
var_end = eap->cmd + 2;
6244+
else
6245+
var_end = find_name_end(pskip, NULL, NULL,
6246+
FNE_CHECK_START | FNE_INCL_BR);
6247+
oplen = assignment_len(skipwhite(var_end), &heredoc);
6248+
if (oplen > 0)
6249+
{
6250+
size_t len = p - eap->cmd;
6251+
6252+
// Recognize an assignment if we recognize the variable
6253+
// name:
6254+
// "g:var = expr"
6255+
// "local = expr" where "local" is a local var.
6256+
// "script = expr" where "script" is a script-local var.
6257+
// "import = expr" where "import" is an imported var
6258+
// "&opt = expr"
6259+
// "$ENV = expr"
6260+
// "@r = expr"
6261+
if (*eap->cmd == '&'
6262+
|| *eap->cmd == '$'
6263+
|| *eap->cmd == '@'
6264+
|| ((len) > 2 && eap->cmd[1] == ':')
6265+
|| lookup_local(eap->cmd, len, NULL, cctx) == OK
6266+
|| arg_exists(eap->cmd, len, NULL, NULL, NULL, cctx) == OK
6267+
|| script_var_exists(eap->cmd, len, FALSE, cctx) == OK
6268+
|| find_imported(eap->cmd, len, cctx) != NULL)
6269+
{
6270+
*line = compile_assignment(eap->cmd, eap, CMD_SIZE, cctx);
6271+
if (*line == NULL || *line == eap->cmd)
6272+
return FAIL;
6273+
return OK;
6274+
}
6275+
}
6276+
}
6277+
6278+
if (*eap->cmd == '[')
6279+
{
6280+
// [var, var] = expr
6281+
*line = compile_assignment(eap->cmd, eap, CMD_SIZE, cctx);
6282+
if (*line == NULL)
6283+
return FAIL;
6284+
if (*line != eap->cmd)
6285+
return OK;
6286+
}
6287+
return NOTDONE;
6288+
}
6289+
62196290
/*
62206291
* Check if "name" can be "unlet".
62216292
*/
@@ -7838,68 +7909,14 @@ compile_def_function(ufunc_T *ufunc, int check_return_type, cctx_T *outer_cctx)
78387909

78397910
if (!starts_with_colon)
78407911
{
7841-
char_u *pskip;
7912+
int assign;
78427913

7843-
// Assuming the command starts with a variable or function name,
7844-
// find what follows.
7845-
// Skip over "var.member", "var[idx]" and the like.
7846-
// Also "&opt = val", "$ENV = val" and "@r = val".
7847-
pskip = (*ea.cmd == '&' || *ea.cmd == '$' || *ea.cmd == '@')
7848-
? ea.cmd + 1 : ea.cmd;
7849-
p = to_name_end(pskip, TRUE);
7850-
if (p > ea.cmd && *p != NUL)
7851-
{
7852-
char_u *var_end;
7853-
int oplen;
7854-
int heredoc;
7855-
7856-
if (ea.cmd[0] == '@')
7857-
var_end = ea.cmd + 2;
7858-
else
7859-
var_end = find_name_end(pskip, NULL, NULL,
7860-
FNE_CHECK_START | FNE_INCL_BR);
7861-
oplen = assignment_len(skipwhite(var_end), &heredoc);
7862-
if (oplen > 0)
7863-
{
7864-
size_t len = p - ea.cmd;
7865-
7866-
// Recognize an assignment if we recognize the variable
7867-
// name:
7868-
// "g:var = expr"
7869-
// "local = expr" where "local" is a local var.
7870-
// "script = expr" where "script" is a script-local var.
7871-
// "import = expr" where "import" is an imported var
7872-
// "&opt = expr"
7873-
// "$ENV = expr"
7874-
// "@r = expr"
7875-
if (*ea.cmd == '&'
7876-
|| *ea.cmd == '$'
7877-
|| *ea.cmd == '@'
7878-
|| ((len) > 2 && ea.cmd[1] == ':')
7879-
|| lookup_local(ea.cmd, len, NULL, &cctx) == OK
7880-
|| arg_exists(ea.cmd, len, NULL, NULL,
7881-
NULL, &cctx) == OK
7882-
|| script_var_exists(ea.cmd, len,
7883-
FALSE, &cctx) == OK
7884-
|| find_imported(ea.cmd, len, &cctx) != NULL)
7885-
{
7886-
line = compile_assignment(ea.cmd, &ea, CMD_SIZE, &cctx);
7887-
if (line == NULL || line == ea.cmd)
7888-
goto erret;
7889-
goto nextline;
7890-
}
7891-
}
7892-
}
7893-
7894-
if (*ea.cmd == '[')
7895-
{
7896-
// [var, var] = expr
7897-
line = compile_assignment(ea.cmd, &ea, CMD_SIZE, &cctx);
7898-
if (line == NULL)
7899-
goto erret;
7900-
if (line != ea.cmd)
7901-
goto nextline;
7902-
}
7914+
// Check for assignment after command modifiers.
7915+
assign = may_compile_assignment(&ea, &line, &cctx);
7916+
if (assign == OK)
7917+
goto nextline;
7918+
if (assign == FAIL)
7919+
goto erret;
79037920
}
79047921

79057922
/*

0 commit comments

Comments
 (0)