]> granicus.if.org Git - vim/commitdiff
patch 8.2.2311: Vim9: cannot assign to variable that shadows command modifier v8.2.2311
authorBram Moolenaar <Bram@vim.org>
Thu, 7 Jan 2021 21:03:02 +0000 (22:03 +0100)
committerBram Moolenaar <Bram@vim.org>
Thu, 7 Jan 2021 21:03:02 +0000 (22:03 +0100)
Problem:    Vim9: cannot assign to a variable that shadows a command modifier.
Solution:   Check for assignment after possible command modifier.
            (closes #7632)

src/ex_docmd.c
src/testdir/test_vim9_assign.vim
src/version.c
src/vim9compile.c

index 5a3a37abcbe2211e265d00372edcf337db368917..9659c20b1f574ff0166702999524cfb2ecacb72d 100644 (file)
@@ -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().
index 2d137a328142cd8774989407d1ec8b1e6c95efcd..55b25ea174af0cc77a98d837d6cb11e503324523 100644 (file)
@@ -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
index 0d9816bc91681cdf0e10d7aadfbd61a9b2165180..c79b2197011c228baff578d900dc31f46b119689 100644 (file)
@@ -750,6 +750,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    2311,
 /**/
     2310,
 /**/
index f66b27ff50d960a05e0a530d1caa27df41a04af4..7497e9cc09d61f1dfe8bc2e5f9be473d4e5748cd 100644 (file)
@@ -6216,6 +6216,77 @@ theend:
     return ret;
 }
 
+/*
+ * 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".
  */
@@ -7838,68 +7909,14 @@ compile_def_function(ufunc_T *ufunc, int check_return_type, cctx_T *outer_cctx)
 
        if (!starts_with_colon)
        {
-           char_u *pskip;
+           int     assign;
 
-           // 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;
-           }
+           // Check for assignment after command modifiers.
+           assign = may_compile_assignment(&ea, &line, &cctx);
+           if (assign == OK)
+               goto nextline;
+           if (assign == FAIL)
+               goto erret;
        }
 
        /*