changeset 23537:7f0fc2ab90e3 v8.2.2311

patch 8.2.2311: Vim9: cannot assign to variable that shadows command modifier Commit: https://github.com/vim/vim/commit/17126b13969c3b91516a8e9ff80fb6a1f6924d40 Author: Bram Moolenaar <Bram@vim.org> 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)
author Bram Moolenaar <Bram@vim.org>
date Thu, 07 Jan 2021 22:15:05 +0100
parents 708e07fe1fb4
children 531a9a48bc10
files src/ex_docmd.c src/testdir/test_vim9_assign.vim src/version.c src/vim9compile.c
diffstat 4 files changed, 121 insertions(+), 62 deletions(-) [+]
line wrap: on
line diff
--- 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().
--- 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
--- 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,
--- 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;
 	}
 
 	/*