# HG changeset patch # User Bram Moolenaar # Date 1554395405 -7200 # Node ID abb67309c1cae7809c58c66399804b1b0fa86cf6 # Parent 5a55ab44b5a110922007cd5ac3817015409f23ab patch 8.1.1116: cannot enforce a Vim script style commit https://github.com/vim/vim/commit/558ca4ae55096f8763ab8515a304cda9c57f18a7 Author: Bram Moolenaar Date: Thu Apr 4 18:15:38 2019 +0200 patch 8.1.1116: cannot enforce a Vim script style Problem: Cannot enforce a Vim script style. Solution: Add the :scriptversion command. (closes https://github.com/vim/vim/issues/3857) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -27,10 +27,11 @@ 6. Curly braces names |curly-braces-na 7. Commands |expression-commands| 8. Exception handling |exception-handling| 9. Examples |eval-examples| -10. No +eval feature |no-eval-feature| -11. The sandbox |eval-sandbox| -12. Textlock |textlock| -13. Testing |testing| +10. Vim script version |vimscript-version| +11. No +eval feature |no-eval-feature| +12. The sandbox |eval-sandbox| +13. Textlock |textlock| +14. Testing |testing| {Vi does not have any of these commands} @@ -1037,6 +1038,7 @@ result is a new list with the two lists For String concatenation ".." is preferred, since "." is ambiguous, it is also used for |Dict| member access and floating point numbers. +When |vimscript-version| is 2 or higher, using "." is not allowed. expr7 * expr7 Number multiplication *expr-star* expr7 / expr7 Number division *expr-/* @@ -10476,6 +10478,8 @@ vertsplit Compiled with vertically spli vim_starting True while initial source'ing takes place. |startup| *vim_starting* viminfo Compiled with viminfo support. +vimscript-1 Compiled Vim script version 1 support +vimscript-2 Compiled Vim script version 2 support virtualedit Compiled with 'virtualedit' option. (always true) visual Compiled with Visual mode. (always true) visualextra Compiled with extra Visual mode commands. (always @@ -10966,16 +10970,19 @@ 7. Commands *expression-commands* When the selected range of items is partly past the end of the list, items will be added. - *:let+=* *:let-=* *:letstar=* - *:let/=* *:let%=* *:let.=* *E734* + *:let+=* *:let-=* *:letstar=* + *:let/=* *:let%=* *:let.=* *:let..=* *E734* *E985* :let {var} += {expr1} Like ":let {var} = {var} + {expr1}". :let {var} -= {expr1} Like ":let {var} = {var} - {expr1}". :let {var} *= {expr1} Like ":let {var} = {var} * {expr1}". :let {var} /= {expr1} Like ":let {var} = {var} / {expr1}". :let {var} %= {expr1} Like ":let {var} = {var} % {expr1}". :let {var} .= {expr1} Like ":let {var} = {var} . {expr1}". +:let {var} ..= {expr1} Like ":let {var} = {var} .. {expr1}". These fail if {var} was not set yet and when the type of {var} and {expr1} don't fit the operator. + `.=` is not supported with Vim script version 2 and + later, see |vimscript-version|. :let ${env-name} = {expr1} *:let-environment* *:let-$* @@ -12609,7 +12616,34 @@ code can be used: > unlet scriptnames_output ============================================================================== -10. No +eval feature *no-eval-feature* +10. Vim script versions *vimscript-version* *vimscript-versions* + +Over time many features have been added to Vim script. This includes Ex +commands, functions, variable types, etc. Each individual feature can be +checked with the |has()| and |exists()| functions. + +Sometimes old syntax of functionality gets in the way of making Vim better. +When support is taken away this will break older Vim scripts. To make this +explicit the |:scriptversion| command can be used. When a Vim script is not +compatible with older versions of Vim this will give an explicit error, +instead of failing in mysterious ways. > + + :scriptversion 1 +< This is the original Vim script, same as not using a |:scriptversion| + command. Can be used to go back to old syntax for a range of lines. + Test for support with: > + has('vimscript-1') + + :scriptversion 2 +< String concatenation with "." is not supported, use ".." instead. + This avoids the ambiguity using "." for Dict member access and + floating point numbers. Now ".5" means the number 0.5. + Test for support with: > + has('vimscript-2') + + +============================================================================== +11. No +eval feature *no-eval-feature* When the |+eval| feature was disabled at compile time, none of the expression evaluation commands are available. To prevent this from causing Vim scripts @@ -12640,7 +12674,7 @@ When the |+eval| feature is available th silently ignored, and the command is executed. ============================================================================== -11. The sandbox *eval-sandbox* *sandbox* *E48* +12. The sandbox *eval-sandbox* *sandbox* *E48* The 'foldexpr', 'formatexpr', 'includeexpr', 'indentexpr', 'statusline' and 'foldtext' options may be evaluated in a sandbox. This means that you are @@ -12679,7 +12713,7 @@ Note that when in the sandbox and saving option will still be marked as it was set in the sandbox. ============================================================================== -12. Textlock *textlock* +13. Textlock *textlock* In a few situations it is not allowed to change the text in the buffer, jump to another window and some other things that might confuse or break what Vim @@ -12695,7 +12729,7 @@ This is not allowed when the textlock is - etc. ============================================================================== -13. Testing *testing* +14. Testing *testing* Vim can be tested after building it, usually with "make test". The tests are located in the directory "src/testdir". diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt --- a/runtime/doc/repeat.txt +++ b/runtime/doc/repeat.txt @@ -1,4 +1,4 @@ -*repeat.txt* For Vim version 8.1. Last change: 2018 Dec 18 +*repeat.txt* For Vim version 8.1. Last change: 2019 Apr 04 VIM REFERENCE MANUAL by Bram Moolenaar @@ -325,6 +325,17 @@ For writing a Vim script, see chapter 41 < {not in Vi} +:scriptv[ersion] {version} *:scriptv* *:scriptversion* + *E999* *E984* + Specify the version of Vim for the lines that follow. + Does not apply to sourced scripts. + + If {version} is higher than what the current Vim + version supports E999 will be given. You either need + to rewrite the script to make it work with an older + Vim version, or update Vim to a newer version. See + |vimscript-version| for what changed between versions. + *:scr* *:scriptnames* :scr[iptnames] List all sourced script names, in the order they were first sourced. The number is used for the script ID diff --git a/src/buffer.c b/src/buffer.c --- a/src/buffer.c +++ b/src/buffer.c @@ -5493,6 +5493,7 @@ chk_modeline( current_sctx.sc_sid = SID_MODELINE; current_sctx.sc_seq = 0; current_sctx.sc_lnum = 0; + current_sctx.sc_version = 1; #endif // Make sure no risky things are executed as a side effect. secure = 1; diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -1249,6 +1249,7 @@ ex_let(exarg_T *eap) char_u op[2]; char_u *argend; int first = TRUE; + int concat; argend = skip_var_list(arg, &var_count, &semicolon); if (argend == NULL) @@ -1256,14 +1257,19 @@ ex_let(exarg_T *eap) if (argend > arg && argend[-1] == '.') // for var.='str' --argend; expr = skipwhite(argend); - if (*expr != '=' && !((vim_strchr((char_u *)"+-*/%.", *expr) != NULL - && expr[1] == '=') || STRNCMP(expr, "..=", 3) == 0)) + concat = expr[0] == '.' + && ((expr[1] == '=' && current_sctx.sc_version < 2) + || (expr[1] == '.' && expr[2] == '=')); + if (*expr != '=' && !((vim_strchr((char_u *)"+-*/%", *expr) != NULL + && expr[1] == '=') || concat)) { /* * ":let" without "=": list variables */ if (*arg == '[') emsg(_(e_invarg)); + else if (expr[0] == '.') + emsg(_("E985: .= is not supported with script version 2")); else if (!ends_excmd(*arg)) /* ":let var1 var2" */ arg = list_arg_vars(eap, arg, &first); @@ -3817,7 +3823,7 @@ eval4(char_u **arg, typval_T *rettv, int * Handle fourth level expression: * + number addition * - number subtraction - * . string concatenation + * . string concatenation (if script version is 1) * .. string concatenation * * "arg" must point to the first non-white of the expression. @@ -3838,6 +3844,7 @@ eval5(char_u **arg, typval_T *rettv, int char_u *s1, *s2; char_u buf1[NUMBUFLEN], buf2[NUMBUFLEN]; char_u *p; + int concat; /* * Get the first variable. @@ -3850,8 +3857,11 @@ eval5(char_u **arg, typval_T *rettv, int */ for (;;) { + // "." is only string concatenation when scriptversion is 1 op = **arg; - if (op != '+' && op != '-' && op != '.') + concat = op == '.' + && (*(*arg + 1) == '.' || current_sctx.sc_version < 2); + if (op != '+' && op != '-' && !concat) break; if ((op != '+' || (rettv->v_type != VAR_LIST @@ -4224,6 +4234,17 @@ eval7( *arg = skipwhite(*arg + 1); end_leader = *arg; + if (**arg == '.' && (!isdigit(*(*arg + 1)) +#ifdef FEAT_FLOAT + || current_sctx.sc_version < 2 +#endif + )) + { + semsg(_(e_invexpr2), *arg); + ++*arg; + return FAIL; + } + switch (**arg) { /* @@ -4239,16 +4260,23 @@ eval7( case '7': case '8': case '9': + case '.': { #ifdef FEAT_FLOAT - char_u *p = skipdigits(*arg + 1); + char_u *p; int get_float = FALSE; /* We accept a float when the format matches * "[0-9]\+\.[0-9]\+\([eE][+-]\?[0-9]\+\)\?". This is very * strict to avoid backwards compatibility problems. + * With script version 2 and later the leading digit can be + * omitted. * Don't look for a float after the "." operator, so that * ":let vers = 1.2.3" doesn't fail. */ + if (**arg == '.') + p = *arg; + else + p = skipdigits(*arg + 1); if (!want_string && p[0] == '.' && vim_isdigit(p[1])) { get_float = TRUE; diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -6631,10 +6631,12 @@ f_has(typval_T *argvars, typval_T *rettv #ifdef FEAT_VARTABS "vartabs", #endif + "vertsplit", #ifdef FEAT_VIMINFO "viminfo", #endif - "vertsplit", + "vimscript-1", + "vimscript-2", "virtualedit", "visual", "visualextra", diff --git a/src/ex_cmdidxs.h b/src/ex_cmdidxs.h --- a/src/ex_cmdidxs.h +++ b/src/ex_cmdidxs.h @@ -24,13 +24,13 @@ static const unsigned short cmdidxs1[26] /* q */ 348, /* r */ 351, /* s */ 371, - /* t */ 438, - /* u */ 481, - /* v */ 492, - /* w */ 510, - /* x */ 524, - /* y */ 533, - /* z */ 534 + /* t */ 439, + /* u */ 482, + /* v */ 493, + /* w */ 511, + /* x */ 525, + /* y */ 534, + /* z */ 535 }; /* @@ -59,7 +59,7 @@ static const unsigned char cmdidxs2[26][ /* p */ { 1, 0, 3, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 9, 0, 0, 16, 17, 26, 0, 27, 0, 28, 0 }, /* q */ { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* r */ { 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 19, 0, 0, 0, 0 }, - /* s */ { 2, 6, 15, 0, 18, 22, 0, 24, 25, 0, 0, 28, 30, 34, 38, 40, 0, 48, 0, 49, 0, 61, 62, 0, 63, 0 }, + /* s */ { 2, 6, 15, 0, 19, 23, 0, 25, 26, 0, 0, 29, 31, 35, 39, 41, 0, 49, 0, 50, 0, 62, 63, 0, 64, 0 }, /* t */ { 2, 0, 19, 0, 22, 24, 0, 25, 0, 26, 0, 27, 31, 34, 36, 37, 0, 38, 40, 0, 41, 0, 0, 0, 0, 0 }, /* u */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* v */ { 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 9, 12, 0, 0, 0, 0, 15, 0, 16, 0, 0, 0, 0, 0 }, @@ -69,4 +69,4 @@ static const unsigned char cmdidxs2[26][ /* z */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }; -static const int command_count = 547; +static const int command_count = 548; diff --git a/src/ex_cmds.h b/src/ex_cmds.h --- a/src/ex_cmds.h +++ b/src/ex_cmds.h @@ -1269,6 +1269,9 @@ EX(CMD_scriptnames, "scriptnames", ex_sc EX(CMD_scriptencoding, "scriptencoding", ex_scriptencoding, WORD1|TRLBAR|CMDWIN, ADDR_LINES), +EX(CMD_scriptversion, "scriptversion", ex_scriptversion, + WORD1|TRLBAR|CMDWIN, + ADDR_LINES), EX(CMD_scscope, "scscope", ex_scscope, EXTRA|NOTRLCOM, ADDR_LINES), diff --git a/src/ex_cmds2.c b/src/ex_cmds2.c --- a/src/ex_cmds2.c +++ b/src/ex_cmds2.c @@ -2321,7 +2321,7 @@ check_changed_any( else #endif /* Try auto-writing the buffer. If this fails but the buffer no - * longer exists it's not changed, that's OK. */ + * longer exists it's not changed, that's OK. */ if (check_changed(buf, (p_awa ? CCGD_AW : 0) | CCGD_MULTWIN | CCGD_ALLBUF) && bufref_valid(&bufref)) @@ -4501,12 +4501,14 @@ do_source( * Also starts profiling timer for nested script. */ save_funccal(&funccalp_entry); + save_current_sctx = current_sctx; + current_sctx.sc_lnum = 0; + current_sctx.sc_version = 1; + // Check if this script was sourced before to finds its SID. // If it's new, generate a new SID. // Always use a new sequence number. - save_current_sctx = current_sctx; current_sctx.sc_seq = ++last_current_SID_seq; - current_sctx.sc_lnum = 0; # ifdef UNIX stat_ok = (mch_stat((char *)fname_exp, &st) >= 0); # endif @@ -5077,10 +5079,9 @@ script_line_end(void) /* * ":scriptencoding": Set encoding conversion for a sourced script. - * Without the multi-byte feature it's simply ignored. */ void -ex_scriptencoding(exarg_T *eap UNUSED) +ex_scriptencoding(exarg_T *eap) { struct source_cookie *sp; char_u *name; @@ -5108,6 +5109,29 @@ ex_scriptencoding(exarg_T *eap UNUSED) vim_free(name); } +/* + * ":scriptversion": Set Vim script version for a sourced script. + */ + void +ex_scriptversion(exarg_T *eap UNUSED) +{ + int nr; + + if (!getline_equal(eap->getline, eap->cookie, getsourceline)) + { + emsg(_("E984: :scriptversion used outside of a sourced file")); + return; + } + + nr = getdigits(&eap->arg); + if (nr == 0 || *eap->arg != NUL) + emsg(_(e_invarg)); + else if (nr > 2) + semsg(_("E999: scriptversion not supported: %d"), nr); + else + current_sctx.sc_version = nr; +} + #if defined(FEAT_EVAL) || defined(PROTO) /* * ":finish": Mark a sourced file as finished. diff --git a/src/main.c b/src/main.c --- a/src/main.c +++ b/src/main.c @@ -3166,6 +3166,7 @@ process_env( current_sctx.sc_sid = SID_ENV; current_sctx.sc_seq = 0; current_sctx.sc_lnum = 0; + current_sctx.sc_version = 1; #endif do_cmdline_cmd(initstr); sourcing_name = save_sourcing_name; diff --git a/src/option.c b/src/option.c --- a/src/option.c +++ b/src/option.c @@ -407,7 +407,7 @@ struct vimoption char_u *def_val[2]; // default values for variable (vi and vim) #ifdef FEAT_EVAL sctx_T script_ctx; // script context where the option was last set -# define SCTX_INIT , {0, 0, 0} +# define SCTX_INIT , {0, 0, 0, 1} #else # define SCTX_INIT #endif @@ -5911,6 +5911,7 @@ set_string_option_direct( script_ctx.sc_sid = set_sid; script_ctx.sc_seq = 0; script_ctx.sc_lnum = 0; + script_ctx.sc_version = 1; } set_option_sctx_idx(idx, opt_flags, script_ctx); } diff --git a/src/proto/ex_cmds2.pro b/src/proto/ex_cmds2.pro --- a/src/proto/ex_cmds2.pro +++ b/src/proto/ex_cmds2.pro @@ -98,6 +98,7 @@ void script_line_start(void); void script_line_exec(void); void script_line_end(void); void ex_scriptencoding(exarg_T *eap); +void ex_scriptversion(exarg_T *eap); void ex_finish(exarg_T *eap); void do_finish(exarg_T *eap, int reanimate); int source_finished(char_u *(*fgetline)(int, void *, int), void *cookie); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -79,6 +79,7 @@ typedef struct { scid_T sc_sid; // script ID int sc_seq; // sourcing sequence number linenr_T sc_lnum; // line number + int sc_version; // :scriptversion } sctx_T; /* diff --git a/src/testdir/test_eval_stuff.vim b/src/testdir/test_eval_stuff.vim --- a/src/testdir/test_eval_stuff.vim +++ b/src/testdir/test_eval_stuff.vim @@ -123,3 +123,39 @@ func Test_string_concatenation() let a..=b call assert_equal('ab', a) endfunc + +scriptversion 2 +func Test_string_concat_scriptversion2() + let a = 'a' + let b = 'b' + + call assert_fails('echo a . b', 'E15:') + call assert_fails('let a .= b', 'E985:') + call assert_fails('let vers = 1.2.3', 'E15:') + + if has('float') + let f = .5 + call assert_equal(0.5, f) + endif +endfunc + +scriptversion 1 +func Test_string_concat_scriptversion1() + let a = 'a' + let b = 'b' + + echo a . b + let a .= b + let vers = 1.2.3 + call assert_equal('123', vers) + + if has('float') + call assert_fails('let f = .5', 'E15:') + endif +endfunc + +func Test_scriptversion() + call writefile(['scriptversion 9'], 'Xversionscript') + call assert_fails('source Xversionscript', 'E999:') + call delete('Xversionscript') +endfunc diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -772,6 +772,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1116, +/**/ 1115, /**/ 1114,