]> granicus.if.org Git - vim/commitdiff
patch 8.2.3314: behavior of exists() in a :def function is unpredictable v8.2.3314
authorBram Moolenaar <Bram@vim.org>
Sun, 8 Aug 2021 12:43:22 +0000 (14:43 +0200)
committerBram Moolenaar <Bram@vim.org>
Sun, 8 Aug 2021 12:43:22 +0000 (14:43 +0200)
Problem:    Behavior of exists() in a :def function is unpredictable.
Solution:   Add exists_compiled().

runtime/doc/eval.txt
runtime/doc/usr_41.txt
src/errors.h
src/evalfunc.c
src/testdir/test_vim9_builtin.vim
src/version.c
src/vim9compile.c

index 5f047e6bdbcce4136b96bf574d132fa75924ff13..4e258c57731cdf484ad59a71ca72279b6b849c6f 100644 (file)
@@ -2473,14 +2473,14 @@ browse({save}, {title}, {initdir}, {default})
                                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 executable {expr} exists
 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, which is |TRUE| if {expr} is defined,
 
                Can also be used as a |method|: >
                        Varname()->exists()
+<
+
+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()*
+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
index 7765bea1661361e4523bdc2b6dbd0c192bffa387..93388b2993cb5493a9e82e11b3fa2cfefd545a35 100644 (file)
@@ -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
index 24420e43efd9e6d5492562cc37e329139dffb2cd..3b15d105fccef3bd7fc1c398bd94c7ea10ed3142 100644 (file)
@@ -646,3 +646,7 @@ EXTERN char e_encryption_sodium_mlock_failed[]
        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"));
index 7006afa498335b4304bcf171eefb35a93c0b1581..95ae211eebacdac2cb35bd03c0ac28ae71c1da9a 100644 (file)
@@ -49,6 +49,7 @@ static void f_escape(typval_T *argvars, typval_T *rettv);
 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 *rettv)
     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
  */
index 4fb1df342d983ab0d8e74e58a1f2e45356f2f5b8..6ca6c7b0c0043e3bb595c6b798424e61c499546b 100644 (file)
@@ -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')
+  if exists_compiled('+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(':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
index db75af720bd14f2aebd40adc1a0993ef31edbf20..06c89b32d167dc21f6e67a02df6379c5c243ae0e 100644 (file)
@@ -755,6 +755,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    3314,
 /**/
     3313,
 /**/
index 5effa5c719de79319946d085937e4f376a974103..c08aad63424a7993c3688cf292b540dae26591d6 100644 (file)
@@ -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)