]> granicus.if.org Git - vim/commitdiff
patch 8.2.1667: local function name cannot shadow a global function name v8.2.1667
authorBram Moolenaar <Bram@vim.org>
Sat, 12 Sep 2020 16:32:34 +0000 (18:32 +0200)
committerBram Moolenaar <Bram@vim.org>
Sat, 12 Sep 2020 16:32:34 +0000 (18:32 +0200)
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)

src/proto/userfunc.pro
src/testdir/test_vim9_func.vim
src/userfunc.c
src/version.c
src/vim9compile.c

index e6acc18864f123804966140d6f51b0f9203dfbad..4e3f2dbf4f7d97892b1747d6f9aa498f285d42a0 100644 (file)
@@ -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);
index c80271626162d0a4b0e3628873543004e3944946..7a1385c74ec771f3a524d1c4d459cbeaa8852dee 100644 (file)
@@ -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
index d5bdadc1885bb98750b55c1d3e643e8d289853ce..d28125fdd205e331651e8511e238c879d42e3cef 100644 (file)
@@ -874,6 +874,15 @@ find_func(char_u *name, int is_global, cctx_T *cctx)
     return NULL;
 }
 
+/*
+ * 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.
@@ -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);
index 596f38d7ef593f94395b0e0c0aec81c510a4c135..b225f7406caa801a4d0615e310dbe6d83fa830e6 100644 (file)
@@ -750,6 +750,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1667,
 /**/
     1666,
 /**/
index 43d994e1a078a51ada5dba529a21c5a7cabdded7..4ef3fdccb2f4cf0606f82725546240190d102fb5 100644 (file)
@@ -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,