Compare commits

..

2 Commits

Author SHA1 Message Date
3a53ec8bdd patch 8.2.1330: Github workflow takes longer than needed
Problem:    Github workflow takes longer than needed.
Solution:   Do two test runs in parallel instead of sequentially. (Ken Takata,
            closes #6579)
2020-07-31 22:17:32 +02:00
38ddf333f6 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)
2020-07-31 22:05:04 +02:00
12 changed files with 262 additions and 22 deletions

View File

@ -195,16 +195,29 @@ jobs:
echo. echo.
echo %COL_GREEN%vim version:%COL_RESET% echo %COL_GREEN%vim version:%COL_RESET%
.\vim --version || exit 1 .\vim --version || exit 1
cd testdir
mkdir ..\src2
xcopy testdir ..\src2\testdir\ /E > nul || exit 1
copy evalfunc.c ..\src2 > nul
echo %COL_GREEN%Start testing vim in background.%COL_RESET%
start cmd /c "cd ..\src2\testdir & nmake -nologo -f Make_dos.mak VIMPROG=..\..\src\vim > nul & echo done>done.txt"
echo %COL_GREEN%Test gvim:%COL_RESET% echo %COL_GREEN%Test gvim:%COL_RESET%
cd testdir
nmake -nologo -f Make_dos.mak VIMPROG=..\gvim || exit 1 nmake -nologo -f Make_dos.mak VIMPROG=..\gvim || exit 1
nmake -nologo -f Make_dos.mak clean cd ..
echo %COL_GREEN%Test vim:%COL_RESET%
if "${{ matrix.toolchain }}-${{ matrix.arch }}"=="msvc-x64" ( echo %COL_GREEN%Wait for vim tests to finish.%COL_RESET%
rem This test may hang up unless it is executed in a separate console. cd ..\src2\testdir
start /wait cmd /c "nmake -nologo -f Make_dos.mak VIMPROG=..\vim > nul" :: Wait about 5 minutes.
if exist messages type messages for /L %%i in (1,1,300) do (
nmake -nologo -f Make_dos.mak report || exit 1 if exist done.txt goto exitloop
) else ( ping -n 2 localhost > nul
nmake -nologo -f Make_dos.mak VIMPROG=..\vim || exit 1
) )
echo %COL_RED%Timed out.%COL_RESET%
:exitloop
echo %COL_GREEN%Test results of vim:%COL_RESET%
if exist messages type messages
nmake -nologo -f Make_dos.mak report VIMPROG=..\..\src\vim || exit 1

View File

@ -2027,6 +2027,41 @@ ga_clear_strings(garray_T *gap)
ga_clear(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 * Initialize a growing array. Don't forget to set ga_itemsize and
* ga_growsize! Or use ga_init2(). * ga_growsize! Or use ga_init2().

View File

@ -56,6 +56,7 @@ char_u *vim_strrchr(char_u *string, int c);
int vim_isspace(int x); int vim_isspace(int x);
void ga_clear(garray_T *gap); void ga_clear(garray_T *gap);
void ga_clear_strings(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_init(garray_T *gap);
void ga_init2(garray_T *gap, int itemsize, int growsize); void ga_init2(garray_T *gap, int itemsize, int growsize);
int ga_grow(garray_T *gap, int n); int ga_grow(garray_T *gap, int n);

View 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 *get_lambda_name(void);
char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state); 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); 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); char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload);
void emsg_funcname(char *ermsg, char_u *name); 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); int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, evalarg_T *evalarg, funcexe_T *funcexe);

View File

@ -1546,6 +1546,7 @@ typedef enum {
/* /*
* Structure to hold info for a user function. * Structure to hold info for a user function.
* When adding a field check copy_func().
*/ */
typedef struct typedef struct
{ {
@ -1618,6 +1619,7 @@ typedef struct
#define FC_NOARGS 0x200 // no a: variables in lambda #define FC_NOARGS 0x200 // no a: variables in lambda
#define FC_VIM9 0x400 // defined in vim9 script file #define FC_VIM9 0x400 // defined in vim9 script file
#define FC_CFUNC 0x800 // defined as Lua C func #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 MAX_FUNC_ARGS 20 // maximum number of function arguments
#define VAR_SHORT_LEN 20 // short variable name length #define VAR_SHORT_LEN 20 // short variable name length

View File

@ -699,6 +699,24 @@ def Test_disassemble_lambda()
instr) instr)
enddef 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 def AndOr(arg: any): string
if arg == 1 && arg != 2 || arg == 4 if arg == 1 && arg != 2 || arg == 4
return 'yes' return 'yes'

View File

@ -133,6 +133,28 @@ def Test_nested_function()
CheckDefFailure(['func Nested()', 'endfunc'], 'E1086:') CheckDefFailure(['func Nested()', 'endfunc'], 'E1086:')
enddef 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() func Test_call_default_args_from_func()
call assert_equal('string', MyDefaultArgs()) call assert_equal('string', MyDefaultArgs())
call assert_equal('one', MyDefaultArgs('one')) call assert_equal('one', MyDefaultArgs('one'))

View File

@ -366,7 +366,7 @@ register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state)
if (fp == NULL) if (fp == NULL)
return NULL; return NULL;
fp->uf_dfunc_idx = UF_NOT_COMPILED; fp->uf_def_status = UF_NOT_COMPILED;
fp->uf_refcount = 1; fp->uf_refcount = 1;
fp->uf_varargs = TRUE; fp->uf_varargs = TRUE;
fp->uf_flags = FC_CFUNC; 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 // When there is a def-function index do not actually remove the
// function, so we can find the index when defining the function again. // 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; fp->uf_flags |= FC_DEAD;
else else
hash_remove(&func_hashtab, hi); hash_remove(&func_hashtab, hi);
@ -1122,7 +1123,8 @@ func_clear(ufunc_T *fp, int force)
// clear this function // clear this function
func_clear_items(fp); func_clear_items(fp);
funccal_unref(fp->uf_scoped, fp, force); 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_free(ufunc_T *fp, int force)
{ {
func_clear(fp, 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); func_free(fp, force);
else else
fp->uf_flags |= FC_DEAD; 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. * Call a user function.
@ -2521,6 +2594,8 @@ list_functions(regmatch_T *regmatch)
/* /*
* ":function" also supporting nested ":def". * ":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. * Returns a pointer to the function or NULL if no function defined.
*/ */
ufunc_T * ufunc_T *

View File

@ -754,6 +754,10 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
1330,
/**/
1329,
/**/ /**/
1328, 1328,
/**/ /**/

View File

@ -79,6 +79,7 @@ typedef enum {
ISN_PCALL_END, // cleanup after ISN_PCALL with cpf_top set ISN_PCALL_END, // cleanup after ISN_PCALL with cpf_top set
ISN_RETURN, // return, result is on top of stack ISN_RETURN, // return, result is on top of stack
ISN_FUNCREF, // push a function ref to dfunc isn_arg.funcref ISN_FUNCREF, // push a function ref to dfunc isn_arg.funcref
ISN_NEWFUNC, // create a global function from a lambda function
// expression operations // expression operations
ISN_JUMP, // jump if condition is matched isn_arg.jump ISN_JUMP, // jump if condition is matched isn_arg.jump
@ -237,6 +238,12 @@ typedef struct {
int fr_var_idx; // variable to store partial int fr_var_idx; // variable to store partial
} funcref_T; } 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 // arguments to ISN_CHECKLEN
typedef struct { typedef struct {
int cl_min_len; // minimum length int cl_min_len; // minimum length
@ -281,6 +288,7 @@ struct isn_S {
script_T script; script_T script;
unlet_T unlet; unlet_T unlet;
funcref_T funcref; funcref_T funcref;
newfunc_T newfunc;
checklen_T checklen; checklen_T checklen;
shuffle_T shuffle; shuffle_T shuffle;
} isn_arg; } isn_arg;

View File

@ -1522,6 +1522,27 @@ generate_FUNCREF(cctx_T *cctx, int dfunc_idx)
return OK; 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. * Generate an ISN_JUMP instruction.
*/ */
@ -4875,11 +4896,13 @@ exarg_getline(
static char_u * static char_u *
compile_nested_function(exarg_T *eap, cctx_T *cctx) 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_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(); char_u *name = get_lambda_name();
lvar_T *lvar; lvar_T *lvar;
ufunc_T *ufunc; ufunc_T *ufunc;
int r;
eap->arg = name_end; eap->arg = name_end;
eap->getline = exarg_getline; eap->getline = exarg_getline;
@ -4894,16 +4917,28 @@ compile_nested_function(exarg_T *eap, cctx_T *cctx)
&& compile_def_function(ufunc, TRUE, cctx) == FAIL) && compile_def_function(ufunc, TRUE, cctx) == FAIL)
return NULL; return NULL;
// Define a local variable for the function reference. if (is_global)
lvar = reserve_local(cctx, name_start, name_end - name_start, {
TRUE, ufunc->uf_func_type); char_u *func_name = vim_strnsave(name_start + 2,
name_end - name_start - 2);
if (generate_FUNCREF(cctx, ufunc->uf_dfunc_idx) == FAIL if (func_name == NULL)
|| generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL) == FAIL) r = FAIL;
return NULL; 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? // TODO: warning for trailing text?
return (char_u *)""; return r == FAIL ? NULL : (char_u *)"";
} }
/* /*
@ -7641,6 +7676,11 @@ delete_instr(isn_T *isn)
} }
break; 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_2BOOL:
case ISN_2STRING: case ISN_2STRING:
case ISN_ADDBLOB: case ISN_ADDBLOB:

View File

@ -723,7 +723,10 @@ call_def_function(
dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
+ ufunc->uf_dfunc_idx; + ufunc->uf_dfunc_idx;
if (dfunc->df_instr == NULL) if (dfunc->df_instr == NULL)
{
iemsg("using call_def_function() on not compiled function");
return FAIL; return FAIL;
}
} }
CLEAR_FIELD(ectx); CLEAR_FIELD(ectx);
@ -1726,6 +1729,15 @@ call_def_function(
} }
break; 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 // jump if a condition is met
case ISN_JUMP: case ISN_JUMP:
{ {
@ -2912,6 +2924,15 @@ ex_disassemble(exarg_T *eap)
} }
break; 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: case ISN_JUMP:
{ {
char *when = "?"; char *when = "?";