# HG changeset patch # User Bram Moolenaar # Date 1595969103 -7200 # Node ID a7afee13873d7b999e73f9aa4ded3abc5343a791 # Parent 8c78fe47b32120fea9150b061466b1b0f9b06a96 patch 8.2.1313: Vim9 script: cannot assign to environment variable Commit: https://github.com/vim/vim/commit/b5ed266037dea49024e00c4e1f9c89f3a9ebaa60 Author: Bram Moolenaar 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. diff --git a/src/ex_docmd.c b/src/ex_docmd.c --- 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 diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim --- 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(['¬ex += 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) diff --git a/src/version.c b/src/version.c --- 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,