changeset 27348:9a9c34c84cd4 v8.2.4202

patch 8.2.4202: Vim9: cannot export function that exists globally Commit: https://github.com/vim/vim/commit/acc4b5648b49ec13c4f35ee0bf552eda71b0c372 Author: Bram Moolenaar <Bram@vim.org> Date: Mon Jan 24 13:54:45 2022 +0000 patch 8.2.4202: Vim9: cannot export function that exists globally Problem: Vim9: cannot export function that exists globally. Solution: When checking if a function already exists only check for script-local functions. (closes #9615)
author Bram Moolenaar <Bram@vim.org>
date Mon, 24 Jan 2022 15:00:04 +0100
parents b43f497a7e4b
children b26e0d130e07
files src/proto/userfunc.pro src/testdir/test_vim9_import.vim src/userfunc.c src/version.c src/vim.h src/vim9compile.c src/vim9instr.c
diffstat 7 files changed, 58 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -8,7 +8,7 @@ char_u *deref_func_name(char_u *name, in
 void emsg_funcname(char *ermsg, char_u *name);
 int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, evalarg_T *evalarg, funcexe_T *funcexe);
 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);
+ufunc_T *find_func_even_dead(char_u *name, int flags);
 ufunc_T *find_func(char_u *name, int is_global);
 int func_is_global(ufunc_T *ufunc);
 int func_name_refcount(char_u *name);
--- a/src/testdir/test_vim9_import.vim
+++ b/src/testdir/test_vim9_import.vim
@@ -965,6 +965,37 @@ def Run_Test_import_in_spellsuggest_expr
   set nospell spellsuggest& verbose=0
 enddef
 
+def Test_export_shadows_global_function()
+  mkdir('Xdir/autoload', 'p')
+  var save_rtp = &rtp
+  exe 'set rtp^=' .. getcwd() .. '/Xdir'
+
+  var lines =<< trim END
+      vim9script
+      export def Shadow(): string
+        return 'Shadow()'
+      enddef
+  END
+  writefile(lines, 'Xdir/autoload/shadow.vim')
+
+  lines =<< trim END
+      vim9script
+
+      def g:Shadow(): string
+        return 'global'
+      enddef
+
+      import autoload 'shadow.vim'
+      assert_equal('Shadow()', shadow.Shadow())
+  END
+  CheckScriptSuccess(lines)
+
+  delfunc g:Shadow
+  bwipe!
+  delete('Xdir', 'rf')
+  &rtp = save_rtp
+enddef
+
 def Test_export_fails()
   CheckScriptFailure(['export var some = 123'], 'E1042:')
   CheckScriptFailure(['vim9script', 'export var g:some'], 'E1022:')
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -1941,16 +1941,18 @@ find_func_with_prefix(char_u *name, int 
 
 /*
  * Find a function by name, return pointer to it in ufuncs.
- * When "is_global" is true don't find script-local or imported functions.
+ * When "flags" has FFED_IS_GLOBAL don't find script-local or imported
+ * functions.
+ * When "flags" has "FFED_NO_GLOBAL" don't find global functions.
  * Return NULL for unknown function.
  */
     ufunc_T *
-find_func_even_dead(char_u *name, int is_global)
+find_func_even_dead(char_u *name, int flags)
 {
     hashitem_T	*hi;
     ufunc_T	*func;
 
-    if (!is_global)
+    if ((flags & FFED_IS_GLOBAL) == 0)
     {
 	int	find_script_local = in_vim9script() && eval_isnamec1(*name)
 					   && (name[1] != ':' || *name == 's');
@@ -1965,10 +1967,13 @@ find_func_even_dead(char_u *name, int is
 	}
     }
 
-    hi = hash_find(&func_hashtab,
+    if ((flags & FFED_NO_GLOBAL) == 0)
+    {
+	hi = hash_find(&func_hashtab,
 				STRNCMP(name, "g:", 2) == 0 ? name + 2 : name);
-    if (!HASHITEM_EMPTY(hi))
-	return HI2UF(hi);
+	if (!HASHITEM_EMPTY(hi))
+	    return HI2UF(hi);
+    }
 
     // Find autoload function if this is an autoload script.
     return find_func_with_prefix(name[0] == 's' && name[1] == ':'
@@ -1983,7 +1988,7 @@ find_func_even_dead(char_u *name, int is
     ufunc_T *
 find_func(char_u *name, int is_global)
 {
-    ufunc_T	*fp = find_func_even_dead(name, is_global);
+    ufunc_T	*fp = find_func_even_dead(name, is_global ? FFED_IS_GLOBAL : 0);
 
     if (fp != NULL && (fp->uf_flags & FC_DEAD) == 0)
 	return fp;
@@ -2354,7 +2359,7 @@ func_clear_free(ufunc_T *fp, int force)
     int
 copy_func(char_u *lambda, char_u *global, ectx_T *ectx)
 {
-    ufunc_T *ufunc = find_func_even_dead(lambda, TRUE);
+    ufunc_T *ufunc = find_func_even_dead(lambda, FFED_IS_GLOBAL);
     ufunc_T *fp = NULL;
 
     if (ufunc == NULL)
@@ -4464,6 +4469,7 @@ define_function(exarg_T *eap, char_u *na
 	hashtab_T	*ht;
 	char_u		*find_name = name;
 	int		var_conflict = FALSE;
+	int		ffed_flags = is_global ? FFED_IS_GLOBAL : 0;
 
 	v = find_var(name, &ht, TRUE);
 	if (v != NULL && (in_vim9script() || v->di_tv.v_type == VAR_FUNC))
@@ -4481,6 +4487,9 @@ define_function(exarg_T *eap, char_u *na
 		    v = find_var(find_name, &ht, TRUE);
 		    if (v != NULL)
 			var_conflict = TRUE;
+		    // Only check if the function already exists in the script,
+		    // global functions can be shadowed.
+		    ffed_flags |= FFED_NO_GLOBAL;
 		}
 		else
 		{
@@ -4508,7 +4517,7 @@ define_function(exarg_T *eap, char_u *na
 	    goto erret;
 	}
 
-	fp = find_func_even_dead(find_name, is_global);
+	fp = find_func_even_dead(find_name, ffed_flags);
 	if (vim9script)
 	{
 	    char_u *uname = untrans_function_name(name);
--- 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 */
 /**/
+    4202,
+/**/
     4201,
 /**/
     4200,
--- a/src/vim.h
+++ b/src/vim.h
@@ -2798,4 +2798,8 @@ long elapsed(DWORD start_tick);
 #define VSE_SHELL	1	// escape for a shell command
 #define VSE_BUFFER	2	// escape for a ":buffer" command
 
+// Flags used by find_func_even_dead()
+#define FFED_IS_GLOBAL	1	// "g:" was used
+#define FFED_NO_GLOBAL	2	// only check for script-local functions
+
 #endif // VIM__H
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -332,7 +332,7 @@ check_defined(char_u *p, size_t len, cct
 		&& (lookup_local(p, len, NULL, cctx) == OK
 		    || arg_exists(p, len, NULL, NULL, NULL, cctx) == OK))
 	    || find_imported(p, len, FALSE, cctx) != NULL
-	    || (ufunc = find_func_even_dead(p, FALSE)) != NULL)
+	    || (ufunc = find_func_even_dead(p, 0)) != NULL)
     {
 	// A local or script-local function can shadow a global function.
 	if (ufunc == NULL || ((ufunc->uf_flags & FC_DEAD) == 0
--- a/src/vim9instr.c
+++ b/src/vim9instr.c
@@ -2050,7 +2050,7 @@ delete_instr(isn_T *isn)
 	case ISN_NEWFUNC:
 	    {
 		char_u  *lambda = isn->isn_arg.newfunc.nf_lambda;
-		ufunc_T *ufunc = find_func_even_dead(lambda, TRUE);
+		ufunc_T *ufunc = find_func_even_dead(lambda, FFED_IS_GLOBAL);
 
 		if (ufunc != NULL)
 		{