Skip to content

Commit 2673599

Browse files
committed
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().
1 parent 9e2fa4b commit 2673599

7 files changed

Lines changed: 78 additions & 25 deletions

File tree

runtime/doc/eval.txt

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2473,14 +2473,14 @@ browse({save}, {title}, {initdir}, {default})
24732473
String put up a file requester
24742474
browsedir({title}, {initdir}) String put up a directory requester
24752475
bufadd({name}) Number add a buffer to the buffer list
2476-
bufexists({expr}) Number |TRUE| if buffer {expr} exists
2477-
buflisted({expr}) Number |TRUE| if buffer {expr} is listed
2478-
bufload({expr}) Number load buffer {expr} if not loaded yet
2479-
bufloaded({expr}) Number |TRUE| if buffer {expr} is loaded
2480-
bufname([{expr}]) String Name of the buffer {expr}
2481-
bufnr([{expr} [, {create}]]) Number Number of the buffer {expr}
2482-
bufwinid({expr}) Number window ID of buffer {expr}
2483-
bufwinnr({expr}) Number window number of buffer {expr}
2476+
bufexists({buf}) Number |TRUE| if buffer {buf} exists
2477+
buflisted({buf}) Number |TRUE| if buffer {buf} is listed
2478+
bufload({buf}) Number load buffer {buf} if not loaded yet
2479+
bufloaded({buf}) Number |TRUE| if buffer {buf} is loaded
2480+
bufname([{buf}]) String Name of the buffer {buf}
2481+
bufnr([{buf} [, {create}]]) Number Number of the buffer {buf}
2482+
bufwinid({buf}) Number window ID of buffer {buf}
2483+
bufwinnr({buf}) Number window number of buffer {buf}
24842484
byte2line({byte}) Number line number at byte count {byte}
24852485
byteidx({expr}, {nr}) Number byte index of {nr}'th char in {expr}
24862486
byteidxcomp({expr}, {nr}) Number byte index of {nr}'th char in {expr}
@@ -2562,6 +2562,7 @@ executable({expr}) Number 1 if executable {expr} exists
25622562
execute({command}) String execute {command} and get the output
25632563
exepath({expr}) String full path of the command {expr}
25642564
exists({expr}) Number |TRUE| if {expr} exists
2565+
exists_compiled({expr}) Number |TRUE| if {expr} exists at compile time
25652566
exp({expr}) Float exponential of {expr}
25662567
expand({expr} [, {nosuf} [, {list}]])
25672568
any expand special keywords in {expr}
@@ -4442,8 +4443,10 @@ exepath({expr}) *exepath()*
44424443
*exists()*
44434444
exists({expr}) The result is a Number, which is |TRUE| if {expr} is defined,
44444445
zero otherwise.
4445-
Note: In a compiled |:def| function local variables and
4446-
arguments are not visible to `exists()`.
4446+
4447+
Note: In a compiled |:def| function the evaluation is done at
4448+
runtime. Use `exists_compiled()` to evaluate the expression
4449+
at compile time.
44474450

44484451
For checking for a supported feature use |has()|.
44494452
For checking if a file exists use |filereadable()|.
@@ -4534,8 +4537,23 @@ exists({expr}) The result is a Number, which is |TRUE| if {expr} is defined,
45344537

45354538
Can also be used as a |method|: >
45364539
Varname()->exists()
4540+
<
4541+
4542+
exists_compiled({expr}) *exists()*
4543+
Like `exists()` but evaluated at compile time. This is useful
4544+
to skip a block where a function is used that would otherwise
4545+
give an error: >
4546+
if exists_compiled('*ThatFunction')
4547+
ThatFunction('works')
4548+
endif
4549+
< If `exists()` were used then a compilation error would be
4550+
given if ThatFunction() is not defined.
4551+
4552+
{expr} must be a literal string. *E1232*
4553+
Can only be used in a |:def| function. *E1233*
4554+
45374555

4538-
exp({expr}) *exp()*
4556+
exp({expr}) *exp()*
45394557
Return the exponential of {expr} as a |Float| in the range
45404558
[0, inf].
45414559
{expr} must evaluate to a |Float| or a |Number|.
@@ -6434,7 +6452,7 @@ has({feature} [, {check}])
64346452
features that have been abandoned will not be known by the
64356453
current Vim version.
64366454

6437-
Also see |exists()|.
6455+
Also see |exists()| and |exists_compiled()|.
64386456

64396457
Note that to skip code that has a syntax error when the
64406458
feature is not available, Vim may skip the rest of the line

runtime/doc/usr_41.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,6 +1182,7 @@ Various: *various-functions*
11821182
state() get current busy state
11831183
visualmode() last visual mode used
11841184
exists() check if a variable, function, etc. exists
1185+
exists_compiled() like exists() but check at compile time
11851186
has() check if a feature is supported in Vim
11861187
changenr() return number of most recent change
11871188
cscope_connection() check if a cscope connection exists

src/errors.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,3 +646,7 @@ EXTERN char e_encryption_sodium_mlock_failed[]
646646
INIT(= N_("E1230: Encryption: sodium_mlock() failed"));
647647
EXTERN char e_cannot_use_bar_to_separate_commands_here_str[]
648648
INIT(= N_("E1231: Cannot use a bar to separate commands here: %s"));
649+
EXTERN char e_argument_of_exists_compiled_must_be_literal_string[]
650+
INIT(= N_("E1232: Argument of exists_compiled() must be a literal string"));
651+
EXTERN char e_exists_compiled_can_only_be_used_in_def_function[]
652+
INIT(= N_("E1233: exists_compiled() can only be used in a :def function"));

src/evalfunc.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ static void f_escape(typval_T *argvars, typval_T *rettv);
4949
static void f_eval(typval_T *argvars, typval_T *rettv);
5050
static void f_eventhandler(typval_T *argvars, typval_T *rettv);
5151
static void f_execute(typval_T *argvars, typval_T *rettv);
52+
static void f_exists_compiled(typval_T *argvars, typval_T *rettv);
5253
static void f_expand(typval_T *argvars, typval_T *rettv);
5354
static void f_expandcmd(typval_T *argvars, typval_T *rettv);
5455
static void f_feedkeys(typval_T *argvars, typval_T *rettv);
@@ -1329,6 +1330,8 @@ static funcentry_T global_functions[] =
13291330
ret_string, f_exepath},
13301331
{"exists", 1, 1, FEARG_1, arg1_string,
13311332
ret_number_bool, f_exists},
1333+
{"exists_compiled", 1, 1, FEARG_1, arg1_string,
1334+
ret_number_bool, f_exists_compiled},
13321335
{"exp", 1, 1, FEARG_1, arg1_float_or_nr,
13331336
ret_float, FLOAT_FUNC(f_exp)},
13341337
{"expand", 1, 3, FEARG_1, arg3_string_bool_bool,
@@ -3626,6 +3629,12 @@ f_exists(typval_T *argvars, typval_T *rettv)
36263629
rettv->vval.v_number = n;
36273630
}
36283631

3632+
static void
3633+
f_exists_compiled(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
3634+
{
3635+
emsg(_(e_exists_compiled_can_only_be_used_in_def_function));
3636+
}
3637+
36293638
/*
36303639
* "expand()" function
36313640
*/

src/testdir/test_vim9_builtin.vim

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -793,42 +793,57 @@ def Test_exists()
793793
CheckDefAndScriptFailure2(['exists(10)'], 'E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1')
794794
call assert_equal(1, exists('&tabstop'))
795795

796-
if exists('+newoption')
796+
var lines =<< trim END
797+
if exists('+newoption')
798+
if &newoption == 'ok'
799+
endif
800+
endif
801+
END
802+
CheckDefFailure(lines, 'E113:')
803+
CheckScriptSuccess(lines)
804+
enddef
805+
806+
def Test_exists_compiled()
807+
call assert_equal(1, exists_compiled('&tabstop'))
808+
CheckDefAndScriptFailure2(['exists_compiled(10)'], 'E1232:', 'E1233:')
809+
CheckDefAndScriptFailure2(['exists_compiled(v:progname)'], 'E1232:', 'E1233:')
810+
811+
if exists_compiled('+newoption')
797812
if &newoption == 'ok'
798813
endif
799814
endif
800-
if exists('&newoption')
815+
if exists_compiled('&newoption')
801816
if &newoption == 'ok'
802817
endif
803818
endif
804-
if exists('+tabstop')
819+
if exists_compiled('+tabstop')
805820
assert_equal(8, &tabstop)
806821
else
807822
assert_report('tabstop option not existing?')
808823
endif
809-
if exists('&tabstop')
824+
if exists_compiled('&tabstop')
810825
assert_equal(8, &tabstop)
811826
else
812827
assert_report('tabstop option not existing?')
813828
endif
814829

815-
if exists(':DoSomeCommand') >= 2
830+
if exists_compiled(':DoSomeCommand') >= 2
816831
DoSomeCommand
817832
endif
818833
assert_equal(4, g:didSomeCommand)
819-
if exists(':NoSuchCommand') >= 2
834+
if exists_compiled(':NoSuchCommand') >= 2
820835
NoSuchCommand
821836
endif
822837

823838
var found = false
824-
if exists('*CheckScriptSuccess')
839+
if exists_compiled('*CheckScriptSuccess')
825840
found = true
826841
endif
827842
assert_true(found)
828-
if exists('*NoSuchFunction')
843+
if exists_compiled('*NoSuchFunction')
829844
NoSuchFunction()
830845
endif
831-
if exists('*no_such_function')
846+
if exists_compiled('*no_such_function')
832847
no_such_function()
833848
endif
834849
enddef

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,8 @@ static char *(features[]) =
755755

756756
static int included_patches[] =
757757
{ /* Add new patch number below this line */
758+
/**/
759+
3314,
758760
/**/
759761
3313,
760762
/**/

src/vim9compile.c

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3415,9 +3415,9 @@ compile_call(
34153415
int is_searchpair;
34163416

34173417
// We can evaluate "has('name')" at compile time.
3418-
// We can evaluate some "exists()" values at compile time.
3418+
// We always evaluate "exists_compiled()" at compile time.
34193419
if ((varlen == 3 && STRNCMP(*arg, "has", 3) == 0)
3420-
|| (varlen == 6 && STRNCMP(*arg, "exists", 6) == 0))
3420+
|| (varlen == 15 && STRNCMP(*arg, "exists_compiled", 6) == 0))
34213421
{
34223422
char_u *s = skipwhite(*arg + varlen + 1);
34233423
typval_T argvars[2];
@@ -3431,8 +3431,7 @@ compile_call(
34313431
s = skipwhite(s);
34323432
if (*s == ')' && argvars[0].v_type == VAR_STRING
34333433
&& ((is_has && !dynamic_feature(argvars[0].vval.v_string))
3434-
|| (!is_has && vim_strchr((char_u *)"+&:*",
3435-
*argvars[0].vval.v_string))))
3434+
|| !is_has))
34363435
{
34373436
typval_T *tv = &ppconst->pp_tv[ppconst->pp_used];
34383437

@@ -3449,6 +3448,11 @@ compile_call(
34493448
return OK;
34503449
}
34513450
clear_tv(&argvars[0]);
3451+
if (!is_has)
3452+
{
3453+
emsg(_(e_argument_of_exists_compiled_must_be_literal_string));
3454+
return FAIL;
3455+
}
34523456
}
34533457

34543458
if (generate_ppconst(cctx, ppconst) == FAIL)

0 commit comments

Comments
 (0)