changeset 21558:1c4d4aa22b37 v8.2.1329

patch 8.2.1329: Vim9: cannot define global function inside :def function Commit: https://github.com/vim/vim/commit/38ddf333f6b2806b0ea2dd052ee1cd50dd7f4525 Author: Bram Moolenaar <Bram@vim.org> Date: Fri Jul 31 22:05:04 2020 +0200 patch 8.2.1329: Vim9: cannot define global function inside :def function Problem: Vim9: cannot define global function inside :def function. Solution: Assign to global variable instead of local. (closes https://github.com/vim/vim/issues/6584)
author Bram Moolenaar <Bram@vim.org>
date Fri, 31 Jul 2020 22:15:04 +0200
parents 00c9f8522652
children aa76456cbc57
files 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
diffstat 11 files changed, 237 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/src/misc2.c
+++ b/src/misc2.c
@@ -2028,6 +2028,41 @@ ga_clear_strings(garray_T *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().
  */
--- a/src/proto/misc2.pro
+++ b/src/proto/misc2.pro
@@ -56,6 +56,7 @@ char_u *vim_strrchr(char_u *string, int 
 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);
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -5,6 +5,7 @@ int get_function_args(char_u **argp, cha
 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);
--- a/src/structs.h
+++ b/src/structs.h
@@ -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
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -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'
--- a/src/testdir/test_vim9_func.vim
+++ b/src/testdir/test_vim9_func.vim
@@ -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'))
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -366,7 +366,7 @@ register_cfunc(cfunc_T cb, cfunc_free_T 
     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 *
--- a/src/version.c
+++ b/src/version.c
@@ -755,6 +755,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1329,
+/**/
     1328,
 /**/
     1327,
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -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;
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -1523,6 +1523,27 @@ generate_FUNCREF(cctx_T *cctx, int dfunc
 }
 
 /*
+ * 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.
  */
     static int
@@ -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, cc
 	    && 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,
+    if (is_global)
+    {
+	char_u *func_name = vim_strnsave(name_start + 2,
+						    name_end - name_start - 2);
+
+	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
-	    || generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL) == FAIL)
-	return NULL;
+	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:
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -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 = "?";