summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2020-09-12 18:32:34 +0200
committerBram Moolenaar <Bram@vim.org>2020-09-12 18:32:34 +0200
commit0f769815c82bf93812842e1ad56fcc52c10ff3e5 (patch)
tree73144939f04e2212e621e0fa59959a1d1c66a57f
parentb00ef0508b22905379953a164bdb4300015d3705 (diff)
downloadvim-git-0f769815c82bf93812842e1ad56fcc52c10ff3e5.tar.gz
patch 8.2.1667: local function name cannot shadow a global function namev8.2.1667
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)
-rw-r--r--src/proto/userfunc.pro1
-rw-r--r--src/testdir/test_vim9_func.vim30
-rw-r--r--src/userfunc.c11
-rw-r--r--src/version.c2
-rw-r--r--src/vim9compile.c57
5 files changed, 82 insertions, 19 deletions
diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro
index e6acc1886..4e3f2dbf4 100644
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -11,6 +11,7 @@ int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, evalarg_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);
diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim
index c80271626..7a1385c74 100644
--- 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
diff --git a/src/userfunc.c b/src/userfunc.c
index d5bdadc18..d28125fdd 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -875,6 +875,15 @@ find_func(char_u *name, int is_global, cctx_T *cctx)
}
/*
+ * 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, cctx_T *cctx)
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);
diff --git a/src/version.c b/src/version.c
index 596f38d7e..b225f7406 100644
--- 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,
diff --git a/src/vim9compile.c b/src/vim9compile.c
index 43d994e1a..4ef3fdccb 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -292,12 +292,14 @@ lookup_script(char_u *name, size_t len, int vim9script)
/*
* 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, cctx_T *cctx)
&& (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)
+ || (ufunc = find_func_even_dead(p, FALSE, cctx)) != NULL)
{
- p[len] = c;
- semsg(_(e_name_already_defined_str), p);
- return FAIL;
+ // 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 *name)
/*
* 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_arg, cctx_T *cctx, int error)
|| 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 *eap, cmdidx_T cmdidx, cctx_T *cctx)
: ((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 *eap, cmdidx_T cmdidx, cctx_T *cctx)
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,