# HG changeset patch # User Bram Moolenaar # Date 1599929104 -7200 # Node ID 3d0632b260fd0d8e705161ba1a931a231556bc2a # Parent 79200ddde03672e71fda485468daf5bf581580da patch 8.2.1667: local function name cannot shadow a global function name Commit: https://github.com/vim/vim/commit/0f769815c82bf93812842e1ad56fcc52c10ff3e5 Author: Bram Moolenaar 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) diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro --- 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); diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim --- 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 --- 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, ""); STRCAT(buf, fp->uf_name + 3); diff --git a/src/version.c b/src/version.c --- 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 --- 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,