summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2020-07-31 22:05:04 +0200
committerBram Moolenaar <Bram@vim.org>2020-07-31 22:05:04 +0200
commit38ddf333f6b2806b0ea2dd052ee1cd50dd7f4525 (patch)
tree5174c1e94e06de25435c40113f6d793eb97659c7 /src
parent4d4d1cd5c8b61ef0296bd6190ca2a0b2d6d96ba7 (diff)
patch 8.2.1329: Vim9: cannot define global function inside :def functionv8.2.1329
Problem: Vim9: cannot define global function inside :def function. Solution: Assign to global variable instead of local. (closes #6584)
Diffstat (limited to 'src')
-rw-r--r--src/misc2.c35
-rw-r--r--src/proto/misc2.pro1
-rw-r--r--src/proto/userfunc.pro1
-rw-r--r--src/structs.h2
-rw-r--r--src/testdir/test_vim9_disassemble.vim18
-rw-r--r--src/testdir/test_vim9_func.vim22
-rw-r--r--src/userfunc.c83
-rw-r--r--src/version.c2
-rw-r--r--src/vim9.h8
-rw-r--r--src/vim9compile.c56
-rw-r--r--src/vim9execute.c21
11 files changed, 237 insertions, 12 deletions
diff --git a/src/misc2.c b/src/misc2.c
index 7ef6cdf44d..3e16c3633d 100644
--- 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().
*/
diff --git a/src/proto/misc2.pro b/src/proto/misc2.pro
index e1e20aaab1..d55fc31c39 100644
--- a/src/proto/misc2.pro
+++ b/src/proto/misc2.pro
@@ -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);
diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro
index 9c9eb2df7a..f30ac2f219 100644
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -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);
diff --git a/src/structs.h b/src/structs.h
index ba9c98db0f..7e34d80998 100644
--- 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
diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim
index 98a9e207e5..ea012b744e 100644
--- 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'
diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim
index 2546f1369f..dae64429d9 100644
--- 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'))
diff --git a/src/userfunc.c b/src/userfunc.c
index de0cdb7ea5..de7034df82 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -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 *
diff --git a/src/version.c b/src/version.c
index 6a939f4c9b..75232bf37c 100644
--- 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,
diff --git a/src/vim9.h b/src/vim9.h
index 0d51c98b5d..77f3427128 100644
--- 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;
diff --git a/src/vim9compile.c b/src/vim9compile.c
index cf08e95584..b9b2b6fa52 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -1523,6 +1523,27 @@ generate_FUNCREF(cctx_T *cctx, int dfunc_idx)
}
/*
+ * 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, 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:
diff --git a/src/vim9execute.c b/src/vim9execute.c
index e52df61ac1..9bfec3af12 100644
--- 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 = "?";