# HG changeset patch # User Bram Moolenaar # Date 1628426703 -7200 # Node ID 446f478d6fb165d0537c062ca4906ed545139dd4 # Parent a1127668cdfa84f57979022c256c1880b519fb50 patch 8.2.3314: behavior of exists() in a :def function is unpredictable Commit: https://github.com/vim/vim/commit/267359902c8792fed13543ddeb56c6df0ae74957 Author: Bram Moolenaar Date: Sun Aug 8 14:43:22 2021 +0200 patch 8.2.3314: behavior of exists() in a :def function is unpredictable Problem: Behavior of exists() in a :def function is unpredictable. Solution: Add exists_compiled(). diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2473,14 +2473,14 @@ browse({save}, {title}, {initdir}, {defa String put up a file requester browsedir({title}, {initdir}) String put up a directory requester bufadd({name}) Number add a buffer to the buffer list -bufexists({expr}) Number |TRUE| if buffer {expr} exists -buflisted({expr}) Number |TRUE| if buffer {expr} is listed -bufload({expr}) Number load buffer {expr} if not loaded yet -bufloaded({expr}) Number |TRUE| if buffer {expr} is loaded -bufname([{expr}]) String Name of the buffer {expr} -bufnr([{expr} [, {create}]]) Number Number of the buffer {expr} -bufwinid({expr}) Number window ID of buffer {expr} -bufwinnr({expr}) Number window number of buffer {expr} +bufexists({buf}) Number |TRUE| if buffer {buf} exists +buflisted({buf}) Number |TRUE| if buffer {buf} is listed +bufload({buf}) Number load buffer {buf} if not loaded yet +bufloaded({buf}) Number |TRUE| if buffer {buf} is loaded +bufname([{buf}]) String Name of the buffer {buf} +bufnr([{buf} [, {create}]]) Number Number of the buffer {buf} +bufwinid({buf}) Number window ID of buffer {buf} +bufwinnr({buf}) Number window number of buffer {buf} byte2line({byte}) Number line number at byte count {byte} byteidx({expr}, {nr}) Number byte index of {nr}'th char in {expr} byteidxcomp({expr}, {nr}) Number byte index of {nr}'th char in {expr} @@ -2562,6 +2562,7 @@ executable({expr}) Number 1 if executab execute({command}) String execute {command} and get the output exepath({expr}) String full path of the command {expr} exists({expr}) Number |TRUE| if {expr} exists +exists_compiled({expr}) Number |TRUE| if {expr} exists at compile time exp({expr}) Float exponential of {expr} expand({expr} [, {nosuf} [, {list}]]) any expand special keywords in {expr} @@ -4442,8 +4443,10 @@ exepath({expr}) *exepath()* *exists()* exists({expr}) The result is a Number, which is |TRUE| if {expr} is defined, zero otherwise. - Note: In a compiled |:def| function local variables and - arguments are not visible to `exists()`. + + Note: In a compiled |:def| function the evaluation is done at + runtime. Use `exists_compiled()` to evaluate the expression + at compile time. For checking for a supported feature use |has()|. For checking if a file exists use |filereadable()|. @@ -4534,8 +4537,23 @@ exists({expr}) The result is a Number, w Can also be used as a |method|: > Varname()->exists() - -exp({expr}) *exp()* +< + +exists_compiled({expr}) *exists()* + Like `exists()` but evaluated at compile time. This is useful + to skip a block where a function is used that would otherwise + give an error: > + if exists_compiled('*ThatFunction') + ThatFunction('works') + endif +< If `exists()` were used then a compilation error would be + given if ThatFunction() is not defined. + + {expr} must be a literal string. *E1232* + Can only be used in a |:def| function. *E1233* + + +exp({expr}) *exp()* Return the exponential of {expr} as a |Float| in the range [0, inf]. {expr} must evaluate to a |Float| or a |Number|. @@ -6434,7 +6452,7 @@ has({feature} [, {check}]) features that have been abandoned will not be known by the current Vim version. - Also see |exists()|. + Also see |exists()| and |exists_compiled()|. Note that to skip code that has a syntax error when the feature is not available, Vim may skip the rest of the line diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -1182,6 +1182,7 @@ Various: *various-functions* state() get current busy state visualmode() last visual mode used exists() check if a variable, function, etc. exists + exists_compiled() like exists() but check at compile time has() check if a feature is supported in Vim changenr() return number of most recent change cscope_connection() check if a cscope connection exists diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -646,3 +646,7 @@ EXTERN char e_encryption_sodium_mlock_fa INIT(= N_("E1230: Encryption: sodium_mlock() failed")); EXTERN char e_cannot_use_bar_to_separate_commands_here_str[] INIT(= N_("E1231: Cannot use a bar to separate commands here: %s")); +EXTERN char e_argument_of_exists_compiled_must_be_literal_string[] + INIT(= N_("E1232: Argument of exists_compiled() must be a literal string")); +EXTERN char e_exists_compiled_can_only_be_used_in_def_function[] + INIT(= N_("E1233: exists_compiled() can only be used in a :def function")); diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -49,6 +49,7 @@ static void f_escape(typval_T *argvars, static void f_eval(typval_T *argvars, typval_T *rettv); static void f_eventhandler(typval_T *argvars, typval_T *rettv); static void f_execute(typval_T *argvars, typval_T *rettv); +static void f_exists_compiled(typval_T *argvars, typval_T *rettv); static void f_expand(typval_T *argvars, typval_T *rettv); static void f_expandcmd(typval_T *argvars, typval_T *rettv); static void f_feedkeys(typval_T *argvars, typval_T *rettv); @@ -1329,6 +1330,8 @@ static funcentry_T global_functions[] = ret_string, f_exepath}, {"exists", 1, 1, FEARG_1, arg1_string, ret_number_bool, f_exists}, + {"exists_compiled", 1, 1, FEARG_1, arg1_string, + ret_number_bool, f_exists_compiled}, {"exp", 1, 1, FEARG_1, arg1_float_or_nr, ret_float, FLOAT_FUNC(f_exp)}, {"expand", 1, 3, FEARG_1, arg3_string_bool_bool, @@ -3626,6 +3629,12 @@ f_exists(typval_T *argvars, typval_T *re rettv->vval.v_number = n; } + static void +f_exists_compiled(typval_T *argvars UNUSED, typval_T *rettv UNUSED) +{ + emsg(_(e_exists_compiled_can_only_be_used_in_def_function)); +} + /* * "expand()" function */ diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim --- a/src/testdir/test_vim9_builtin.vim +++ b/src/testdir/test_vim9_builtin.vim @@ -793,42 +793,57 @@ def Test_exists() CheckDefAndScriptFailure2(['exists(10)'], 'E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1') call assert_equal(1, exists('&tabstop')) - if exists('+newoption') + var lines =<< trim END + if exists('+newoption') + if &newoption == 'ok' + endif + endif + END + CheckDefFailure(lines, 'E113:') + CheckScriptSuccess(lines) +enddef + +def Test_exists_compiled() + call assert_equal(1, exists_compiled('&tabstop')) + CheckDefAndScriptFailure2(['exists_compiled(10)'], 'E1232:', 'E1233:') + CheckDefAndScriptFailure2(['exists_compiled(v:progname)'], 'E1232:', 'E1233:') + + if exists_compiled('+newoption') if &newoption == 'ok' endif endif - if exists('&newoption') + if exists_compiled('&newoption') if &newoption == 'ok' endif endif - if exists('+tabstop') - assert_equal(8, &tabstop) - else - assert_report('tabstop option not existing?') - endif - if exists('&tabstop') + if exists_compiled('+tabstop') assert_equal(8, &tabstop) else assert_report('tabstop option not existing?') endif - - if exists(':DoSomeCommand') >= 2 + if exists_compiled('&tabstop') + assert_equal(8, &tabstop) + else + assert_report('tabstop option not existing?') + endif + + if exists_compiled(':DoSomeCommand') >= 2 DoSomeCommand endif assert_equal(4, g:didSomeCommand) - if exists(':NoSuchCommand') >= 2 + if exists_compiled(':NoSuchCommand') >= 2 NoSuchCommand endif var found = false - if exists('*CheckScriptSuccess') + if exists_compiled('*CheckScriptSuccess') found = true endif assert_true(found) - if exists('*NoSuchFunction') + if exists_compiled('*NoSuchFunction') NoSuchFunction() endif - if exists('*no_such_function') + if exists_compiled('*no_such_function') no_such_function() endif enddef diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -756,6 +756,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 3314, +/**/ 3313, /**/ 3312, diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -3415,9 +3415,9 @@ compile_call( int is_searchpair; // We can evaluate "has('name')" at compile time. - // We can evaluate some "exists()" values at compile time. + // We always evaluate "exists_compiled()" at compile time. if ((varlen == 3 && STRNCMP(*arg, "has", 3) == 0) - || (varlen == 6 && STRNCMP(*arg, "exists", 6) == 0)) + || (varlen == 15 && STRNCMP(*arg, "exists_compiled", 6) == 0)) { char_u *s = skipwhite(*arg + varlen + 1); typval_T argvars[2]; @@ -3431,8 +3431,7 @@ compile_call( s = skipwhite(s); if (*s == ')' && argvars[0].v_type == VAR_STRING && ((is_has && !dynamic_feature(argvars[0].vval.v_string)) - || (!is_has && vim_strchr((char_u *)"+&:*", - *argvars[0].vval.v_string)))) + || !is_has)) { typval_T *tv = &ppconst->pp_tv[ppconst->pp_used]; @@ -3449,6 +3448,11 @@ compile_call( return OK; } clear_tv(&argvars[0]); + if (!is_has) + { + emsg(_(e_argument_of_exists_compiled_must_be_literal_string)); + return FAIL; + } } if (generate_ppconst(cctx, ppconst) == FAIL)