changeset 22236:3d0632b260fd v8.2.1667

patch 8.2.1667: local function name cannot shadow a global function name Commit: https://github.com/vim/vim/commit/0f769815c82bf93812842e1ad56fcc52c10ff3e5 Author: Bram Moolenaar <Bram@vim.org> Date: Sat Sep 12 18:32:34 2020 +0200 patch 8.2.1667: local function name cannot shadow a global function name Problem: Local function name cannot shadow a global function name. Solution: Ignore global functions when checking a script-local or scoped function name. (closes #6926)
author Bram Moolenaar <Bram@vim.org>
date Sat, 12 Sep 2020 18:45:04 +0200
parents 79200ddde036
children 27c33305cb9c
files src/proto/userfunc.pro src/testdir/test_vim9_func.vim src/userfunc.c src/version.c src/vim9compile.c
diffstat 5 files changed, 83 insertions(+), 20 deletions(-) [+]
line wrap: on
line diff
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -11,6 +11,7 @@ int get_func_tv(char_u *name, int len, t
 char_u *fname_trans_sid(char_u *name, char_u *fname_buf, char_u **tofree, int *error);
 ufunc_T *find_func_even_dead(char_u *name, int is_global, cctx_T *cctx);
 ufunc_T *find_func(char_u *name, int is_global, cctx_T *cctx);
+int func_is_global(ufunc_T *ufunc);
 void copy_func(char_u *lambda, char_u *global);
 int call_user_func_check(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rettv, funcexe_T *funcexe, dict_T *selfdict);
 void save_funccal(funccal_entry_T *entry);
--- a/src/testdir/test_vim9_func.vim
+++ b/src/testdir/test_vim9_func.vim
@@ -232,6 +232,36 @@ def Test_global_local_function()
   CheckScriptFailure(lines, 'E117:')
 enddef
 
+def Test_local_function_shadows_global()
+  let lines =<< trim END
+      vim9script
+      def g:Gfunc(): string
+        return 'global'
+      enddef
+      def AnotherFunc(): number
+        let Gfunc = function('len')
+        return Gfunc('testing')
+      enddef
+      g:Gfunc()->assert_equal('global')
+      AnotherFunc()->assert_equal(7)
+      delfunc g:Gfunc
+  END
+  CheckScriptSuccess(lines)
+
+  lines =<< trim END
+      vim9script
+      def g:Func(): string
+        return 'global'
+      enddef
+      def AnotherFunc()
+        g:Func = function('len')
+      enddef
+      AnotherFunc()
+  END
+  CheckScriptFailure(lines, 'E705:')
+  delfunc g:Func
+enddef
+
 func TakesOneArg(arg)
   echo a:arg
 endfunc
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -875,6 +875,15 @@ find_func(char_u *name, int is_global, c
 }
 
 /*
+ * Return TRUE if "ufunc" is a global function.
+ */
+    int
+func_is_global(ufunc_T *ufunc)
+{
+    return ufunc->uf_name[0] != K_SPECIAL;
+}
+
+/*
  * Copy the function name of "fp" to buffer "buf".
  * "buf" must be able to hold the function name plus three bytes.
  * Takes care of script-local function names.
@@ -882,7 +891,7 @@ find_func(char_u *name, int is_global, c
     static void
 cat_func_name(char_u *buf, ufunc_T *fp)
 {
-    if (fp->uf_name[0] == K_SPECIAL)
+    if (!func_is_global(fp))
     {
 	STRCPY(buf, "<SNR>");
 	STRCAT(buf, fp->uf_name + 3);
--- 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 */
 /**/
+    1667,
+/**/
     1666,
 /**/
     1665,
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -292,12 +292,14 @@ lookup_script(char_u *name, size_t len, 
 /*
  * Check if "p[len]" is already defined, either in script "import_sid" or in
  * compilation context "cctx".
+ * Does not check the global namespace.
  * Return FAIL and give an error if it defined.
  */
     int
 check_defined(char_u *p, size_t len, cctx_T *cctx)
 {
-    int c = p[len];
+    int		c = p[len];
+    ufunc_T	*ufunc = NULL;
 
     p[len] = NUL;
     if (lookup_script(p, len, FALSE) == OK
@@ -305,11 +307,16 @@ check_defined(char_u *p, size_t len, cct
 		&& (lookup_local(p, len, cctx) != NULL
 		    || lookup_arg(p, len, NULL, NULL, NULL, cctx) == OK))
 	    || find_imported(p, len, cctx) != NULL
-	    || find_func_even_dead(p, FALSE, cctx) != NULL)
-    {
-	p[len] = c;
-	semsg(_(e_name_already_defined_str), p);
-	return FAIL;
+	    || (ufunc = find_func_even_dead(p, FALSE, cctx)) != NULL)
+    {
+	// A local or script-local function can shadow a global function.
+	if (ufunc == NULL || !func_is_global(ufunc)
+		|| (p[0] == 'g' && p[1] == ':'))
+	{
+	    p[len] = c;
+	    semsg(_(e_name_already_defined_str), p);
+	    return FAIL;
+	}
     }
     p[len] = c;
     return OK;
@@ -2114,10 +2121,16 @@ generate_funcref(cctx_T *cctx, char_u *n
 /*
  * Compile a variable name into a load instruction.
  * "end" points to just after the name.
+ * "is_expr" is TRUE when evaluating an expression, might be a funcref.
  * When "error" is FALSE do not give an error when not found.
  */
     static int
-compile_load(char_u **arg, char_u *end_arg, cctx_T *cctx, int error)
+compile_load(
+	char_u **arg,
+	char_u *end_arg,
+	cctx_T	*cctx,
+	int	is_expr,
+	int	error)
 {
     type_T	*type;
     char_u	*name = NULL;
@@ -2214,10 +2227,11 @@ compile_load(char_u **arg, char_u *end_a
 			|| find_imported(name, 0, cctx) != NULL)
 		   res = compile_load_scriptvar(cctx, name, *arg, &end, FALSE);
 
-		// When the name starts with an uppercase letter or "x:" it
-		// can be a user defined function.
+		// When evaluating an expression and the name starts with an
+		// uppercase letter or "x:" it can be a user defined function.
 		// TODO: this is just guessing
-		if (res == FAIL && (ASCII_ISUPPER(*name) || name[1] == ':'))
+		if (res == FAIL && is_expr
+				   && (ASCII_ISUPPER(*name) || name[1] == ':'))
 		    res = generate_funcref(cctx, name);
 	    }
 	}
@@ -2368,8 +2382,9 @@ compile_call(
     }
 
     // If we can find the function by name generate the right call.
+    // Skip global functions here, a local funcref takes precedence.
     ufunc = find_func(name, FALSE, cctx);
-    if (ufunc != NULL)
+    if (ufunc != NULL && !func_is_global(ufunc))
     {
 	res = generate_CALL(cctx, ufunc, argcount);
 	goto theend;
@@ -2380,7 +2395,7 @@ compile_call(
     // Not for eome#Func(), it will be loaded later.
     p = namebuf;
     if (STRNCMP(namebuf, "g:", 2) != 0 && !is_autoload
-	    && compile_load(&p, namebuf + varlen, cctx, FALSE) == OK)
+	    && compile_load(&p, namebuf + varlen, cctx, FALSE, FALSE) == OK)
     {
 	garray_T    *stack = &cctx->ctx_type_stack;
 	type_T	    *type;
@@ -2390,6 +2405,13 @@ compile_call(
 	goto theend;
     }
 
+    // If we can find a global function by name generate the right call.
+    if (ufunc != NULL)
+    {
+	res = generate_CALL(cctx, ufunc, argcount);
+	goto theend;
+    }
+
     // A global function may be defined only later.  Need to figure out at
     // runtime.  Also handles a FuncRef at runtime.
     if (STRNCMP(namebuf, "g:", 2) == 0 || is_autoload)
@@ -3548,7 +3570,7 @@ compile_expr7(
 	{
 	    if (generate_ppconst(cctx, ppconst) == FAIL)
 		return FAIL;
-	    r = compile_load(arg, p, cctx, TRUE);
+	    r = compile_load(arg, p, cctx, TRUE, TRUE);
 	}
 	if (r == FAIL)
 	    return FAIL;
@@ -5002,6 +5024,11 @@ compile_assignment(char_u *arg, exarg_T 
 			      : ((type_T **)stack->ga_data)[stack->ga_len - 1];
 		if (lvar != NULL && (is_decl || !has_type))
 		{
+		    if ((stacktype->tt_type == VAR_FUNC
+				|| stacktype->tt_type == VAR_PARTIAL)
+			    && var_wrong_func_name(name, TRUE))
+			goto theend;
+
 		    if (new_local && !has_type)
 		    {
 			if (stacktype->tt_type == VAR_VOID)
@@ -5009,12 +5036,6 @@ compile_assignment(char_u *arg, exarg_T 
 			    emsg(_(e_cannot_use_void_value));
 			    goto theend;
 			}
-			else if ((stacktype->tt_type == VAR_FUNC
-				    || stacktype->tt_type == VAR_PARTIAL)
-					    && var_wrong_func_name(name, TRUE))
-			{
-			    goto theend;
-			}
 			else
 			{
 			    // An empty list or dict has a &t_void member,