changeset 21526:a7afee13873d v8.2.1313

patch 8.2.1313: Vim9 script: cannot assign to environment variable Commit: https://github.com/vim/vim/commit/b5ed266037dea49024e00c4e1f9c89f3a9ebaa60 Author: Bram Moolenaar <Bram@vim.org> Date: Tue Jul 28 22:38:37 2020 +0200 patch 8.2.1313: Vim9 script: cannot assign to environment variable Problem: Vim9 script: cannot assign to environment variable. Solution: Recognize environment variable assignment. (closes https://github.com/vim/vim/issues/6548) Also options and registers.
author Bram Moolenaar <Bram@vim.org>
date Tue, 28 Jul 2020 22:45:03 +0200
parents 8c78fe47b321
children 79aeae3b041f
files src/ex_docmd.c src/testdir/test_vim9_script.vim src/version.c
diffstat 3 files changed, 113 insertions(+), 72 deletions(-) [+]
line wrap: on
line diff
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -1710,7 +1710,7 @@ do_one_cmd(
     char_u	*cmd;
     int		starts_with_colon = FALSE;
 #ifdef FEAT_EVAL
-    int		starts_with_quote;
+    int		may_have_range;
     int		vim9script = in_vim9script();
 #endif
 
@@ -1773,8 +1773,9 @@ do_one_cmd(
  */
     cmd = ea.cmd;
 #ifdef FEAT_EVAL
-    starts_with_quote = vim9script && !starts_with_colon && *ea.cmd == '\'';
-    if (!starts_with_quote)
+    // In Vim9 script a colon is required before the range.
+    may_have_range = !vim9script || starts_with_colon;
+    if (may_have_range)
 #endif
 	ea.cmd = skip_range(ea.cmd, NULL);
     if (*ea.cmd == '*' && vim_strchr(p_cpo, CPO_STAR) == NULL)
@@ -1783,7 +1784,10 @@ do_one_cmd(
 #ifdef FEAT_EVAL
     if (vim9script && !starts_with_colon)
     {
-	if (ea.cmd > cmd)
+	if (ea.cmd == cmd + 1 && *cmd == '$')
+	    // should be "$VAR = val"
+	    --ea.cmd;
+	else if (ea.cmd > cmd)
 	{
 	    emsg(_(e_colon_required));
 	    goto doend;
@@ -1876,7 +1880,7 @@ do_one_cmd(
 
     ea.cmd = cmd;
 #ifdef FEAT_EVAL
-    if (!starts_with_quote)
+    if (may_have_range)
 #endif
 	if (parse_cmd_address(&ea, &errormsg, FALSE) == FAIL)
 	    goto doend;
@@ -3267,81 +3271,91 @@ find_ex_command(
      * "lvar = value", "lvar(arg)", "[1, 2 3]->Func()"
      */
     p = eap->cmd;
-    if (lookup != NULL && (vim_strchr((char_u *)"{('[", *p) != NULL
-	       || ((p = to_name_const_end(eap->cmd)) > eap->cmd
-		   && *p != NUL)))
-    {
-	int oplen;
-	int heredoc;
-
-	if (
-	    // "(..." is an expression.
-	    // "funcname(" is always a function call.
-	    *p == '('
-		|| (p == eap->cmd
-		    ? (
-			// "{..." is an dict expression.
-			*eap->cmd == '{'
-			// "'string'->func()" is an expression.
-		     || *eap->cmd == '\''
-			// "g:varname" is an expression.
-		     || eap->cmd[1] == ':'
-			)
-		    : (
-			// "varname[]" is an expression.
-			*p == '['
-			// "varname->func()" is an expression.
-		     || (*p == '-' && p[1] == '>')
-			// "varname.expr" is an expression.
-		     || (*p == '.' && ASCII_ISALPHA(p[1]))
-		     )))
-	{
-	    eap->cmdidx = CMD_eval;
-	    return eap->cmd;
-	}
-
-	// "[...]->Method()" is a list expression, but "[a, b] = Func()" is
-	// an assignment.
-	// If there is no line break inside the "[...]" then "p" is advanced to
-	// after the "]" by to_name_const_end(): check if a "=" follows.
-	// If "[...]" has a line break "p" still points at the "[" and it can't
-	// be an assignment.
-	if (*eap->cmd == '[')
-	{
-	    p = to_name_const_end(eap->cmd);
-	    if (p == eap->cmd || *skipwhite(p) != '=')
+    if (lookup != NULL)
+    {
+	// Skip over first char for "&opt = val", "$ENV = val" and "@r = val".
+	char_u *pskip = (*eap->cmd == '&' || *eap->cmd == '$'
+				|| *eap->cmd == '@') ? eap->cmd + 1 : eap->cmd;
+
+	if (vim_strchr((char_u *)"{('[", *p) != NULL
+	       || ((p = to_name_const_end(pskip)) > eap->cmd && *p != NUL))
+	{
+	    int oplen;
+	    int heredoc;
+
+	    if (
+		// "(..." is an expression.
+		// "funcname(" is always a function call.
+		*p == '('
+		    || (p == eap->cmd
+			? (
+			    // "{..." is an dict expression.
+			    *eap->cmd == '{'
+			    // "'string'->func()" is an expression.
+			 || *eap->cmd == '\''
+			    // "g:varname" is an expression.
+			 || eap->cmd[1] == ':'
+			    )
+			: (
+			    // "varname[]" is an expression.
+			    *p == '['
+			    // "varname->func()" is an expression.
+			 || (*p == '-' && p[1] == '>')
+			    // "varname.expr" is an expression.
+			 || (*p == '.' && ASCII_ISALPHA(p[1]))
+			 )))
 	    {
 		eap->cmdidx = CMD_eval;
 		return eap->cmd;
 	    }
-	    if (p > eap->cmd && *skipwhite(p) == '=')
+
+	    // "[...]->Method()" is a list expression, but "[a, b] = Func()" is
+	    // an assignment.
+	    // If there is no line break inside the "[...]" then "p" is
+	    // advanced to after the "]" by to_name_const_end(): check if a "="
+	    // follows.
+	    // If "[...]" has a line break "p" still points at the "[" and it
+	    // can't be an assignment.
+	    if (*eap->cmd == '[')
 	    {
-		eap->cmdidx = CMD_let;
+		p = to_name_const_end(eap->cmd);
+		if (p == eap->cmd || *skipwhite(p) != '=')
+		{
+		    eap->cmdidx = CMD_eval;
+		    return eap->cmd;
+		}
+		if (p > eap->cmd && *skipwhite(p) == '=')
+		{
+		    eap->cmdidx = CMD_let;
+		    return eap->cmd;
+		}
+	    }
+
+	    // Recognize an assignment if we recognize the variable name:
+	    // "g:var = expr"
+	    // "var = expr"  where "var" is a local var name.
+	    oplen = assignment_len(skipwhite(p), &heredoc);
+	    if (oplen > 0)
+	    {
+		if (((p - eap->cmd) > 2 && eap->cmd[1] == ':')
+			|| *eap->cmd == '&'
+			|| *eap->cmd == '$'
+			|| *eap->cmd == '@'
+			|| lookup(eap->cmd, p - eap->cmd, cctx) != NULL)
+		{
+		    eap->cmdidx = CMD_let;
+		    return eap->cmd;
+		}
+	    }
+
+	    // Recognize using a type for a w:, b:, t: or g: variable:
+	    // "w:varname: number = 123".
+	    if (eap->cmd[1] == ':' && *p == ':')
+	    {
+		eap->cmdidx = CMD_eval;
 		return eap->cmd;
 	    }
 	}
-
-	// Recognize an assignment if we recognize the variable name:
-	// "g:var = expr"
-	// "var = expr"  where "var" is a local var name.
-	oplen = assignment_len(skipwhite(p), &heredoc);
-	if (oplen > 0)
-	{
-	    if (((p - eap->cmd) > 2 && eap->cmd[1] == ':')
-		    || lookup(eap->cmd, p - eap->cmd, cctx) != NULL)
-	    {
-		eap->cmdidx = CMD_let;
-		return eap->cmd;
-	    }
-	}
-
-	// Recognize using a type for a w:, b:, t: or g: variable:
-	// "w:varname: number = 123".
-	if (eap->cmd[1] == ':' && *p == ':')
-	{
-	    eap->cmdidx = CMD_eval;
-	    return eap->cmd;
-	}
     }
 #endif
 
--- a/src/testdir/test_vim9_script.vim
+++ b/src/testdir/test_vim9_script.vim
@@ -61,6 +61,14 @@ def Test_assignment()
   assert_equal('foobar', $ENVVAR)
   $ENVVAR = ''
 
+  let lines =<< trim END
+    vim9script
+    $ENVVAR = 'barfoo'
+    assert_equal('barfoo', $ENVVAR)
+    $ENVVAR = ''
+  END
+  call CheckScriptSuccess(lines)
+
   s:appendToMe ..= 'yyy'
   assert_equal('xxxyyy', s:appendToMe)
   s:addToMe += 222
@@ -80,6 +88,15 @@ def Test_assignment()
   set ts=10
   &ts %= 4
   assert_equal(2, &ts)
+
+  lines =<< trim END
+    vim9script
+    &ts = 6
+    &ts += 3
+    assert_equal(9, &ts)
+  END
+  call CheckScriptSuccess(lines)
+
   call CheckDefFailure(['&notex += 3'], 'E113:')
   call CheckDefFailure(['&ts ..= "xxx"'], 'E1019:')
   call CheckDefFailure(['&ts = [7]'], 'E1013:')
@@ -106,6 +123,14 @@ def Test_assignment()
   call CheckDefFailure(['@a += "more"'], 'E1013:')
   call CheckDefFailure(['@a += 123'], 'E1013:')
 
+  lines =<< trim END
+    vim9script
+    @c = 'areg'
+    @c ..= 'add'
+    assert_equal('aregadd', @c)
+  END
+  call CheckScriptSuccess(lines)
+
   v:errmsg = 'none'
   v:errmsg ..= 'again'
   assert_equal('noneagain', v:errmsg)
--- a/src/version.c
+++ b/src/version.c
@@ -755,6 +755,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1313,
+/**/
     1312,
 /**/
     1311,