mirror of
https://github.com/vim/vim.git
synced 2025-09-23 03:43:49 -04:00
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 #6584)
This commit is contained in:
35
src/misc2.c
35
src/misc2.c
@@ -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().
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
|
@@ -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'
|
||||
|
@@ -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'))
|
||||
|
@@ -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 *
|
||||
|
@@ -754,6 +754,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
1329,
|
||||
/**/
|
||||
1328,
|
||||
/**/
|
||||
|
@@ -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;
|
||||
|
@@ -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:
|
||||
|
@@ -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 = "?";
|
||||
|
Reference in New Issue
Block a user