]> granicus.if.org Git - vim/commitdiff
patch 8.2.1329: Vim9: cannot define global function inside :def function v8.2.1329
authorBram Moolenaar <Bram@vim.org>
Fri, 31 Jul 2020 20:05:04 +0000 (22:05 +0200)
committerBram Moolenaar <Bram@vim.org>
Fri, 31 Jul 2020 20:05:04 +0000 (22:05 +0200)
Problem:    Vim9: cannot define global function inside :def function.
Solution:   Assign to global variable instead of local. (closes #6584)

src/misc2.c
src/proto/misc2.pro
src/proto/userfunc.pro
src/structs.h
src/testdir/test_vim9_disassemble.vim
src/testdir/test_vim9_func.vim
src/userfunc.c
src/version.c
src/vim9.h
src/vim9compile.c
src/vim9execute.c

index 7ef6cdf44d03f8cc8ebc029199983461ee139f64..3e16c3633d941a0fe5b3ee97223fafbec5dc526c 100644 (file)
@@ -2027,6 +2027,41 @@ ga_clear_strings(garray_T *gap)
     ga_clear(gap);
 }
 
+/*
+ * Copy a growing array that contains a list of strings.
+ */
+    int
+ga_copy_strings(garray_T *from, garray_T *to)
+{
+    int                i;
+
+    ga_init2(to, sizeof(char_u *), 1);
+    if (ga_grow(to, from->ga_len) == FAIL)
+       return FAIL;
+
+    for (i = 0; i < from->ga_len; ++i)
+    {
+       char_u *orig = ((char_u **)from->ga_data)[i];
+       char_u *copy;
+
+       if (orig == NULL)
+           copy = NULL;
+       else
+       {
+           copy = vim_strsave(orig);
+           if (copy == NULL)
+           {
+               to->ga_len = i;
+               ga_clear_strings(to);
+               return FAIL;
+           }
+       }
+       ((char_u **)to->ga_data)[i] = copy;
+    }
+    to->ga_len = from->ga_len;
+    return OK;
+}
+
 /*
  * Initialize a growing array. Don't forget to set ga_itemsize and
  * ga_growsize!  Or use ga_init2().
index e1e20aaab17ce5999bf3867630980e8aaafa520e..d55fc31c399577767e3b03307a633716e52f3041 100644 (file)
@@ -56,6 +56,7 @@ char_u *vim_strrchr(char_u *string, int c);
 int vim_isspace(int x);
 void ga_clear(garray_T *gap);
 void ga_clear_strings(garray_T *gap);
+int ga_copy_strings(garray_T *from, garray_T *to);
 void ga_init(garray_T *gap);
 void ga_init2(garray_T *gap, int itemsize, int growsize);
 int ga_grow(garray_T *gap, int n);
index 9c9eb2df7a832b1b9ff53b015fcab1df460d5495..f30ac2f219c00829c35fefd2038209df680126fb 100644 (file)
@@ -5,6 +5,7 @@ int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, garray_T
 char_u *get_lambda_name(void);
 char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state);
 int get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg);
+void copy_func(char_u *lambda, char_u *global);
 char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload);
 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);
index ba9c98db0f5d0e9c78ff5717e8e541bafbc77e4b..7e34d809981bde7577e51b0af743d80a701668d7 100644 (file)
@@ -1546,6 +1546,7 @@ typedef enum {
 
 /*
  * Structure to hold info for a user function.
+ * When adding a field check copy_func().
  */
 typedef struct
 {
@@ -1618,6 +1619,7 @@ typedef struct
 #define FC_NOARGS   0x200      // no a: variables in lambda
 #define FC_VIM9            0x400       // defined in vim9 script file
 #define FC_CFUNC    0x800      // defined as Lua C func
+#define FC_COPY            0x1000      // copy of another function by copy_func()
 
 #define MAX_FUNC_ARGS  20      // maximum number of function arguments
 #define VAR_SHORT_LEN  20      // short variable name length
index 98a9e207e5a80df548ebfa52a612c20d3167182d..ea012b744e98424c4010b6d9421c9c919c9f091c 100644 (file)
@@ -699,6 +699,24 @@ def Test_disassemble_lambda()
         instr)
 enddef
 
+def NestedOuter()
+  def g:Inner()
+    echomsg "inner"
+  enddef
+enddef
+
+def Test_nested_func()
+   let instr = execute('disassemble NestedOuter')
+   assert_match('NestedOuter\_s*' ..
+        'def g:Inner()\_s*' ..
+        'echomsg "inner"\_s*' ..
+        'enddef\_s*' ..
+        '\d NEWFUNC <lambda>\d\+ Inner\_s*' ..
+        '\d PUSHNR 0\_s*' ..
+        '\d RETURN',
+        instr)
+enddef
+
 def AndOr(arg: any): string
   if arg == 1 && arg != 2 || arg == 4
     return 'yes'
index 2546f1369fdda8cf1f416f2d9c6331e6fdc24f09..dae64429d9b369bad1f49b58821462e05ee0e10e 100644 (file)
@@ -133,6 +133,28 @@ def Test_nested_function()
   CheckDefFailure(['func Nested()', 'endfunc'], 'E1086:')
 enddef
 
+def Test_nested_global_function()
+  let lines =<< trim END
+      vim9script
+      def Outer()
+          def g:Inner(): string
+              return 'inner'
+          enddef
+      enddef
+      disass Outer
+      Outer()
+      assert_equal('inner', g:Inner())
+      delfunc g:Inner
+      Outer()
+      assert_equal('inner', g:Inner())
+      delfunc g:Inner
+      Outer()
+      assert_equal('inner', g:Inner())
+      delfunc g:Inner
+  END
+  CheckScriptSuccess(lines)
+enddef
+
 func Test_call_default_args_from_func()
   call assert_equal('string', MyDefaultArgs())
   call assert_equal('one', MyDefaultArgs('one'))
index de0cdb7ea51c9842d15cadcca5d3ea2026c1c03c..de7034df8213914259f4758253236f567016c3ae 100644 (file)
@@ -366,7 +366,7 @@ register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state)
     if (fp == NULL)
        return NULL;
 
-    fp->uf_dfunc_idx = UF_NOT_COMPILED;
+    fp->uf_def_status = UF_NOT_COMPILED;
     fp->uf_refcount = 1;
     fp->uf_varargs = TRUE;
     fp->uf_flags = FC_CFUNC;
@@ -1069,7 +1069,8 @@ func_remove(ufunc_T *fp)
     {
        // When there is a def-function index do not actually remove the
        // function, so we can find the index when defining the function again.
-       if (fp->uf_def_status == UF_COMPILED)
+       // Do remove it when it's a copy.
+       if (fp->uf_def_status == UF_COMPILED && (fp->uf_flags & FC_COPY) == 0)
            fp->uf_flags |= FC_DEAD;
        else
            hash_remove(&func_hashtab, hi);
@@ -1122,7 +1123,8 @@ func_clear(ufunc_T *fp, int force)
     // clear this function
     func_clear_items(fp);
     funccal_unref(fp->uf_scoped, fp, force);
-    clear_def_function(fp);
+    if ((fp->uf_flags & FC_COPY) == 0)
+       clear_def_function(fp);
 }
 
 /*
@@ -1150,12 +1152,83 @@ func_free(ufunc_T *fp, int force)
 func_clear_free(ufunc_T *fp, int force)
 {
     func_clear(fp, force);
-    if (force || fp->uf_dfunc_idx == 0)
+    if (force || fp->uf_dfunc_idx == 0 || (fp->uf_flags & FC_COPY))
        func_free(fp, force);
     else
        fp->uf_flags |= FC_DEAD;
 }
 
+/*
+ * Copy already defined function "lambda" to a new function with name "global".
+ * This is for when a compiled function defines a global function.
+ */
+    void
+copy_func(char_u *lambda, char_u *global)
+{
+    ufunc_T *ufunc = find_func_even_dead(lambda, TRUE, NULL);
+    ufunc_T *fp;
+
+    if (ufunc == NULL)
+       semsg(_("E1102: lambda function not found: %s"), lambda);
+    else
+    {
+       // TODO: handle ! to overwrite
+       fp = find_func(global, TRUE, NULL);
+       if (fp != NULL)
+       {
+           semsg(_(e_funcexts), global);
+           return;
+       }
+
+       fp = alloc_clear(offsetof(ufunc_T, uf_name) + STRLEN(global) + 1);
+       if (fp == NULL)
+           return;
+
+       fp->uf_varargs = ufunc->uf_varargs;
+       fp->uf_flags = (ufunc->uf_flags & ~FC_VIM9) | FC_COPY;
+       fp->uf_def_status = ufunc->uf_def_status;
+       fp->uf_dfunc_idx = ufunc->uf_dfunc_idx;
+       if (ga_copy_strings(&fp->uf_args, &ufunc->uf_args) == FAIL
+               || ga_copy_strings(&fp->uf_def_args, &ufunc->uf_def_args)
+                                                                       == FAIL
+               || ga_copy_strings(&fp->uf_lines, &ufunc->uf_lines) == FAIL)
+           goto failed;
+
+       fp->uf_name_exp = ufunc->uf_name_exp == NULL ? NULL
+                                            : vim_strsave(ufunc->uf_name_exp);
+       if (ufunc->uf_arg_types != NULL)
+       {
+           fp->uf_arg_types = ALLOC_MULT(type_T *, fp->uf_args.ga_len);
+           if (fp->uf_arg_types == NULL)
+               goto failed;
+           mch_memmove(fp->uf_arg_types, ufunc->uf_arg_types,
+                                       sizeof(type_T *) * fp->uf_args.ga_len);
+       }
+       if (ufunc->uf_def_arg_idx != NULL)
+       {
+           fp->uf_def_arg_idx = ALLOC_MULT(int, fp->uf_def_args.ga_len + 1);
+           if (fp->uf_def_arg_idx == NULL)
+               goto failed;
+           mch_memmove(fp->uf_def_arg_idx, ufunc->uf_def_arg_idx,
+                                    sizeof(int) * fp->uf_def_args.ga_len + 1);
+       }
+       if (ufunc->uf_va_name != NULL)
+       {
+           fp->uf_va_name = vim_strsave(ufunc->uf_va_name);
+           if (fp->uf_va_name == NULL)
+               goto failed;
+       }
+
+       fp->uf_refcount = 1;
+       STRCPY(fp->uf_name, global);
+       hash_add(&func_hashtab, UF2HIKEY(fp));
+    }
+    return;
+
+failed:
+    func_clear_free(fp, TRUE);
+}
+
 
 /*
  * Call a user function.
@@ -2521,6 +2594,8 @@ list_functions(regmatch_T *regmatch)
 
 /*
  * ":function" also supporting nested ":def".
+ * When "name_arg" is not NULL this is a nested function, using "name_arg" for
+ * the function name.
  * Returns a pointer to the function or NULL if no function defined.
  */
     ufunc_T *
index 6a939f4c9b24d7f5e9cae87de1ff4e766f977f84..75232bf37c8383f3d19f8374360a6cb18ed6cb26 100644 (file)
@@ -754,6 +754,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1329,
 /**/
     1328,
 /**/
index 0d51c98b5d35dbf1e19c75d8bb2a2a53c7384469..77f34271284ade5da03414062ee7d33f8ea831de 100644 (file)
@@ -79,6 +79,7 @@ typedef enum {
     ISN_PCALL_END,  // cleanup after ISN_PCALL with cpf_top set
     ISN_RETURN,            // return, result is on top of stack
     ISN_FUNCREF,    // push a function ref to dfunc isn_arg.funcref
+    ISN_NEWFUNC,    // create a global function from a lambda function
 
     // expression operations
     ISN_JUMP,      // jump if condition is matched isn_arg.jump
@@ -237,6 +238,12 @@ typedef struct {
     int                fr_var_idx;     // variable to store partial
 } funcref_T;
 
+// arguments to ISN_NEWFUNC
+typedef struct {
+    char_u     *nf_lambda;     // name of the lambda already defined
+    char_u     *nf_global;     // name of the global function to be created
+} newfunc_T;
+
 // arguments to ISN_CHECKLEN
 typedef struct {
     int                cl_min_len;     // minimum length
@@ -281,6 +288,7 @@ struct isn_S {
        script_T            script;
        unlet_T             unlet;
        funcref_T           funcref;
+       newfunc_T           newfunc;
        checklen_T          checklen;
        shuffle_T           shuffle;
     } isn_arg;
index cf08e955844135dd2d517832a7213356074e13fa..b9b2b6fa526cb342447db350bcf445c1f04fce1e 100644 (file)
@@ -1522,6 +1522,27 @@ generate_FUNCREF(cctx_T *cctx, int dfunc_idx)
     return OK;
 }
 
+/*
+ * Generate an ISN_NEWFUNC instruction.
+ */
+    static int
+generate_NEWFUNC(cctx_T *cctx, char_u *lambda_name, char_u *func_name)
+{
+    isn_T      *isn;
+    char_u     *name;
+
+    RETURN_OK_IF_SKIP(cctx);
+    name = vim_strsave(lambda_name);
+    if (name == NULL)
+       return FAIL;
+    if ((isn = generate_instr(cctx, ISN_NEWFUNC)) == NULL)
+       return FAIL;
+    isn->isn_arg.newfunc.nf_lambda = name;
+    isn->isn_arg.newfunc.nf_global = func_name;
+
+    return OK;
+}
+
 /*
  * Generate an ISN_JUMP instruction.
  */
@@ -4875,11 +4896,13 @@ exarg_getline(
     static char_u *
 compile_nested_function(exarg_T *eap, cctx_T *cctx)
 {
+    int                is_global = *eap->arg == 'g' && eap->arg[1] == ':';
     char_u     *name_start = eap->arg;
-    char_u     *name_end = to_name_end(eap->arg, FALSE);
+    char_u     *name_end = to_name_end(eap->arg, is_global);
     char_u     *name = get_lambda_name();
     lvar_T     *lvar;
     ufunc_T    *ufunc;
+    int                r;
 
     eap->arg = name_end;
     eap->getline = exarg_getline;
@@ -4894,16 +4917,28 @@ compile_nested_function(exarg_T *eap, cctx_T *cctx)
            && compile_def_function(ufunc, TRUE, cctx) == FAIL)
        return NULL;
 
-    // Define a local variable for the function reference.
-    lvar = reserve_local(cctx, name_start, name_end - name_start,
-                                                   TRUE, ufunc->uf_func_type);
+    if (is_global)
+    {
+       char_u *func_name = vim_strnsave(name_start + 2,
+                                                   name_end - name_start - 2);
 
-    if (generate_FUNCREF(cctx, ufunc->uf_dfunc_idx) == FAIL
-           || generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL) == FAIL)
-       return NULL;
+       if (func_name == NULL)
+           r = FAIL;
+       else
+           r = generate_NEWFUNC(cctx, name, func_name);
+    }
+    else
+    {
+       // Define a local variable for the function reference.
+       lvar = reserve_local(cctx, name_start, name_end - name_start,
+                                                   TRUE, ufunc->uf_func_type);
+       if (generate_FUNCREF(cctx, ufunc->uf_dfunc_idx) == FAIL)
+           return NULL;
+       r = generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL);
+    }
 
     // TODO: warning for trailing text?
-    return (char_u *)"";
+    return r == FAIL ? NULL : (char_u *)"";
 }
 
 /*
@@ -7641,6 +7676,11 @@ delete_instr(isn_T *isn)
            }
            break;
 
+       case ISN_NEWFUNC:
+           vim_free(isn->isn_arg.newfunc.nf_lambda);
+           vim_free(isn->isn_arg.newfunc.nf_global);
+           break;
+
        case ISN_2BOOL:
        case ISN_2STRING:
        case ISN_ADDBLOB:
index e52df61ac136d13818bbca21f3cd8323c05cbaca..9bfec3af121375aaa450aa5329b86fb36e911c1a 100644 (file)
@@ -723,7 +723,10 @@ call_def_function(
        dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
                                                         + ufunc->uf_dfunc_idx;
        if (dfunc->df_instr == NULL)
+       {
+           iemsg("using call_def_function() on not compiled function");
            return FAIL;
+       }
     }
 
     CLEAR_FIELD(ectx);
@@ -1726,6 +1729,15 @@ call_def_function(
                }
                break;
 
+           // Create a global function from a lambda.
+           case ISN_NEWFUNC:
+               {
+                   newfunc_T   *newfunc = &iptr->isn_arg.newfunc;
+
+                   copy_func(newfunc->nf_lambda, newfunc->nf_global);
+               }
+               break;
+
            // jump if a condition is met
            case ISN_JUMP:
                {
@@ -2912,6 +2924,15 @@ ex_disassemble(exarg_T *eap)
                }
                break;
 
+           case ISN_NEWFUNC:
+               {
+                   newfunc_T   *newfunc = &iptr->isn_arg.newfunc;
+
+                   smsg("%4d NEWFUNC %s %s", current,
+                                      newfunc->nf_lambda, newfunc->nf_global);
+               }
+               break;
+
            case ISN_JUMP:
                {
                    char *when = "?";