changeset 24124:f4061617c438 v8.2.2603

patch 8.2.2603: Vim9: no effect if user command is also a function Commit: https://github.com/vim/vim/commit/77b10ffad4ebad15022614be4db2745f6a90f405 Author: Bram Moolenaar <Bram@vim.org> Date: Sun Mar 14 13:21:35 2021 +0100 patch 8.2.2603: Vim9: no effect if user command is also a function Problem: Vim9: no effect if user command is also a function. Solution: Check for paren following. (closes https://github.com/vim/vim/issues/7960)
author Bram Moolenaar <Bram@vim.org>
date Sun, 14 Mar 2021 13:30:03 +0100
parents d97cddd9f357
children b616f39b8989
files src/evalvars.c src/ex_docmd.c src/proto/evalvars.pro src/proto/ex_docmd.pro src/testdir/test_vim9_cmd.vim src/version.c src/vim9compile.c
diffstat 7 files changed, 76 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/src/evalvars.c
+++ b/src/evalvars.c
@@ -2805,12 +2805,15 @@ get_script_local_ht(void)
 
 /*
  * Look for "name[len]" in script-local variables and functions.
+ * When "cmd" is TRUE it must look like a command, a function must be followed
+ * by "(" or "->".
  * Return OK when found, FAIL when not found.
  */
     int
 lookup_scriptitem(
 	char_u	*name,
 	size_t	len,
+	int	cmd,
 	cctx_T	*dummy UNUSED)
 {
     hashtab_T	*ht = get_script_local_ht();
@@ -2845,19 +2848,26 @@ lookup_scriptitem(
     if (p != buffer)
 	vim_free(p);
 
+    // Find a function, so that a following "->" works.
+    // When used as a command require "(" or "->" to follow, "Cmd" is a user
+    // command while "Cmd()" is a function call.
     if (res != OK)
     {
-	// Find a function, so that a following "->" works.  Skip "g:" before a
-	// function name.
-	// Do not check for an internal function, since it might also be a
-	// valid command, such as ":split" versuse "split()".
-	if (name[0] == 'g' && name[1] == ':')
+	p = skipwhite(name + len);
+
+	if (!cmd || name[len] == '(' || (p[0] == '-' && p[1] == '>'))
 	{
-	    is_global = TRUE;
-	    fname = name + 2;
+	    // Do not check for an internal function, since it might also be a
+	    // valid command, such as ":split" versus "split()".
+	    // Skip "g:" before a function name.
+	    if (name[0] == 'g' && name[1] == ':')
+	    {
+		is_global = TRUE;
+		fname = name + 2;
+	    }
+	    if (find_func(fname, is_global, NULL) != NULL)
+		res = OK;
 	}
-	if (find_func(fname, is_global, NULL) != NULL)
-	    res = OK;
     }
 
     return res;
@@ -3288,7 +3298,7 @@ set_var_const(
     {
 	// Item not found, check if a function already exists.
 	if (is_script_local && (flags & (ASSIGN_NO_DECL | ASSIGN_DECL)) == 0
-			  && lookup_scriptitem(name, STRLEN(name), NULL) == OK)
+		   && lookup_scriptitem(name, STRLEN(name), FALSE, NULL) == OK)
 	{
 	    semsg(_(e_redefining_script_item_str), name);
 	    goto failed;
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -3311,7 +3311,7 @@ skip_option_env_lead(char_u *start)
 find_ex_command(
 	exarg_T *eap,
 	int	*full UNUSED,
-	int	(*lookup)(char_u *, size_t, cctx_T *) UNUSED,
+	int	(*lookup)(char_u *, size_t, int cmd, cctx_T *) UNUSED,
 	cctx_T	*cctx UNUSED)
 {
     int		len;
@@ -3430,7 +3430,7 @@ find_ex_command(
 			|| *eap->cmd == '&'
 			|| *eap->cmd == '$'
 			|| *eap->cmd == '@'
-			|| lookup(eap->cmd, p - eap->cmd, cctx) == OK)
+			|| lookup(eap->cmd, p - eap->cmd, TRUE, cctx) == OK)
 		{
 		    eap->cmdidx = CMD_var;
 		    return eap->cmd;
@@ -3449,7 +3449,7 @@ find_ex_command(
 	// If it is an ID it might be a variable with an operator on the next
 	// line, if the variable exists it can't be an Ex command.
 	if (p > eap->cmd && ends_excmd(*skipwhite(p))
-		&& (lookup(eap->cmd, p - eap->cmd, cctx) == OK
+		&& (lookup(eap->cmd, p - eap->cmd, TRUE, cctx) == OK
 		    || (ASCII_ISALPHA(eap->cmd[0]) && eap->cmd[1] == ':')))
 	{
 	    eap->cmdidx = CMD_eval;
--- a/src/proto/evalvars.pro
+++ b/src/proto/evalvars.pro
@@ -61,7 +61,7 @@ void check_vars(char_u *name, int len);
 dictitem_T *find_var(char_u *name, hashtab_T **htp, int no_autoload);
 dictitem_T *find_var_in_ht(hashtab_T *ht, int htname, char_u *varname, int no_autoload);
 hashtab_T *get_script_local_ht(void);
-int lookup_scriptitem(char_u *name, size_t len, cctx_T *dummy);
+int lookup_scriptitem(char_u *name, size_t len, int cmd, cctx_T *dummy);
 hashtab_T *find_var_ht(char_u *name, char_u **varname);
 char_u *get_var_value(char_u *name);
 void new_script_vars(scid_T id);
--- a/src/proto/ex_docmd.pro
+++ b/src/proto/ex_docmd.pro
@@ -13,7 +13,7 @@ void undo_cmdmod(cmdmod_T *cmod);
 int parse_cmd_address(exarg_T *eap, char **errormsg, int silent);
 int checkforcmd(char_u **pp, char *cmd, int len);
 char_u *skip_option_env_lead(char_u *start);
-char_u *find_ex_command(exarg_T *eap, int *full, int (*lookup)(char_u *, size_t, cctx_T *), cctx_T *cctx);
+char_u *find_ex_command(exarg_T *eap, int *full, int (*lookup)(char_u *, size_t, int cmd, cctx_T *), cctx_T *cctx);
 int modifier_len(char_u *cmd);
 int cmd_exists(char_u *name);
 void f_fullcommand(typval_T *argvars, typval_T *rettv);
--- a/src/testdir/test_vim9_cmd.vim
+++ b/src/testdir/test_vim9_cmd.vim
@@ -364,9 +364,8 @@ def Test_method_call_linebreak()
         return F()
       enddef
       def Test()
-        Foo
-          ->Bar()
-          ->setline(1)
+        Foo  ->Bar()
+             ->setline(1)
       enddef
       Test()
       assert_equal('the text', getline(1))
@@ -401,8 +400,7 @@ def Test_method_call_linebreak()
           return F()
       enddef
 
-      Foo
-         ->Bar()
+      Foo->Bar()
          ->setline(1)
   END
   CheckScriptSuccess(lines)
@@ -424,6 +422,33 @@ def Test_method_call_whitespace()
   CheckDefAndScriptSuccess(lines)
 enddef
 
+def Test_method_and_user_command()
+  var lines =<< trim END
+      vim9script
+      def Cmd()
+        g:didFunc = 1
+      enddef
+      command Cmd g:didCmd = 1
+      Cmd
+      assert_equal(1, g:didCmd)
+      Cmd()
+      assert_equal(1, g:didFunc)
+      unlet g:didFunc
+      unlet g:didCmd
+
+      def InDefFunc()
+        Cmd
+        assert_equal(1, g:didCmd)
+        Cmd()
+        assert_equal(1, g:didFunc)
+        unlet g:didFunc
+        unlet g:didCmd
+      enddef
+      InDefFunc()
+  END
+  CheckScriptSuccess(lines)
+enddef
+
 def Test_skipped_expr_linebreak()
   if 0
     var x = []
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    2603,
+/**/
     2602,
 /**/
     2601,
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -391,19 +391,29 @@ variable_exists(char_u *name, size_t len
  * imported or function.
  */
     static int
-item_exists(char_u *name, size_t len, cctx_T *cctx)
+item_exists(char_u *name, size_t len, int cmd UNUSED, cctx_T *cctx)
 {
     int	    is_global;
+    char_u  *p;
 
     if (variable_exists(name, len, cctx))
 	return TRUE;
 
-    // Find a function, so that a following "->" works.  Skip "g:" before a
-    // function name.
-    // Do not check for an internal function, since it might also be a
-    // valid command, such as ":split" versuse "split()".
-    is_global = (name[0] == 'g' && name[1] == ':');
-    return find_func(is_global ? name + 2 : name, is_global, cctx) != NULL;
+    // This is similar to what is in lookup_scriptitem():
+    // Find a function, so that a following "->" works.
+    // Require "(" or "->" to follow, "Cmd" is a user command while "Cmd()" is
+    // a function call.
+    p = skipwhite(name + len);
+
+    if (name[len] == '(' || (p[0] == '-' && p[1] == '>'))
+    {
+	// Do not check for an internal function, since it might also be a
+	// valid command, such as ":split" versuse "split()".
+	// Skip "g:" before a function name.
+	is_global = (name[0] == 'g' && name[1] == ':');
+	return find_func(is_global ? name + 2 : name, is_global, cctx) != NULL;
+    }
+    return FALSE;
 }
 
 /*
@@ -8429,8 +8439,8 @@ compile_def_function(
 		}
 	    }
 	}
-	p = find_ex_command(&ea, NULL, starts_with_colon ? NULL
-		    : (int (*)(char_u *, size_t, cctx_T *))item_exists, &cctx);
+	p = find_ex_command(&ea, NULL, starts_with_colon
+						  ? NULL : item_exists, &cctx);
 
 	if (p == NULL)
 	{