patch 8.1.1310: named function arguments are never optional

Problem:    Named function arguments are never optional.
Solution:   Support optional function arguments with a default value. (Andy
            Massimino, closes #3952)
This commit is contained in:
Bram Moolenaar
2019-05-09 21:08:58 +02:00
parent 6b528fa062
commit 42ae78cfff
5 changed files with 227 additions and 46 deletions

View File

@ -10920,15 +10920,58 @@ change their contents. Thus you can pass a |List| to a function and have the
function add an item to it. If you want to make sure the function cannot
change a |List| or |Dictionary| use |:lockvar|.
When not using "...", the number of arguments in a function call must be equal
to the number of named arguments. When using "...", the number of arguments
may be larger.
It is also possible to define a function without any arguments. You must
still supply the () then.
It is allowed to define another function inside a function body.
*optional-function-argument*
You can provide default values for positional named arguments. This makes
them optional for function calls. When a positional argument is not
specified at a call, the default expression is used to initialize it.
This only works for functions declared with |function|, not for lambda
expressions |expr-lambda|.
Example: >
function Something(key, value = 10)
echo a:key .. ": " .. value
endfunction
call Something('empty') "empty: 10"
call Something('key, 20) "key: 20"
The argument default expressions are evaluated at the time of the function
call, not definition. Thus it is possible to use an expression which is
invalid the moment the function is defined. The expressions are are also only
evaluated when arguments are not specified during a call.
You can pass |v:none| to use the default expression. Note that this means you
cannot pass v:none as an ordinary value when an argument has a default
expression.
Example: >
function Something(a = 10, b = 20, c = 30)
endfunction
call Something(1, v:none, 3) " b = 20
<
*E989*
Optional arguments with default expressions must occur after any mandatory
arguments. You can use "..." after all optional named arguments.
It is possible for later argument defaults to refer to prior arguments,
but not the other way around. They must be prefixed with "a:", as with all
arguments.
Example that works: >
:function Okay(mandatory, optional = a:mandatory)
:endfunction
Example that does NOT work: >
:function NoGood(first = a:second, second = 10)
:endfunction
<
When not using "...", the number of arguments in a function call must be equal
to the number of mandatory named arguments. When using "...", the number of
arguments may be larger.
*local-variables*
Inside a function local variables can be used. These will disappear when the
function returns. Global variables need to be accessed with "g:".

View File

@ -1402,42 +1402,43 @@ typedef struct funccall_S funccall_T;
*/
typedef struct
{
int uf_varargs; /* variable nr of arguments */
int uf_varargs; // variable nr of arguments
int uf_flags;
int uf_calls; /* nr of active calls */
int uf_cleared; /* func_clear() was already called */
garray_T uf_args; /* arguments */
garray_T uf_lines; /* function lines */
int uf_calls; // nr of active calls
int uf_cleared; // func_clear() was already called
garray_T uf_args; // arguments
garray_T uf_def_args; // default argument expressions
garray_T uf_lines; // function lines
# ifdef FEAT_PROFILE
int uf_profiling; /* TRUE when func is being profiled */
int uf_profiling; // TRUE when func is being profiled
int uf_prof_initialized;
/* profiling the function as a whole */
int uf_tm_count; /* nr of calls */
proftime_T uf_tm_total; /* time spent in function + children */
proftime_T uf_tm_self; /* time spent in function itself */
proftime_T uf_tm_children; /* time spent in children this call */
/* profiling the function per line */
int *uf_tml_count; /* nr of times line was executed */
proftime_T *uf_tml_total; /* time spent in a line + children */
proftime_T *uf_tml_self; /* time spent in a line itself */
proftime_T uf_tml_start; /* start time for current line */
proftime_T uf_tml_children; /* time spent in children for this line */
proftime_T uf_tml_wait; /* start wait time for current line */
int uf_tml_idx; /* index of line being timed; -1 if none */
int uf_tml_execed; /* line being timed was executed */
// profiling the function as a whole
int uf_tm_count; // nr of calls
proftime_T uf_tm_total; // time spent in function + children
proftime_T uf_tm_self; // time spent in function itself
proftime_T uf_tm_children; // time spent in children this call
// profiling the function per line
int *uf_tml_count; // nr of times line was executed
proftime_T *uf_tml_total; // time spent in a line + children
proftime_T *uf_tml_self; // time spent in a line itself
proftime_T uf_tml_start; // start time for current line
proftime_T uf_tml_children; // time spent in children for this line
proftime_T uf_tml_wait; // start wait time for current line
int uf_tml_idx; // index of line being timed; -1 if none
int uf_tml_execed; // line being timed was executed
# endif
sctx_T uf_script_ctx; /* SCTX where function was defined,
used for s: variables */
int uf_refcount; /* reference count, see func_name_refcount() */
funccall_T *uf_scoped; /* l: local variables for closure */
char_u uf_name[1]; /* name of function (actually longer); can
start with <SNR>123_ (<SNR> is K_SPECIAL
KS_EXTRA KE_SNR) */
sctx_T uf_script_ctx; // SCTX where function was defined,
// used for s: variables
int uf_refcount; // reference count, see func_name_refcount()
funccall_T *uf_scoped; // l: local variables for closure
char_u uf_name[1]; // name of function (actually longer); can
// start with <SNR>123_ (<SNR> is K_SPECIAL
// KS_EXTRA KE_SNR)
} ufunc_T;
#define MAX_FUNC_ARGS 20 /* maximum number of function arguments */
#define VAR_SHORT_LEN 20 /* short variable name length */
#define FIXVAR_CNT 12 /* number of fixed variables */
#define MAX_FUNC_ARGS 20 // maximum number of function arguments
#define VAR_SHORT_LEN 20 // short variable name length
#define FIXVAR_CNT 12 // number of fixed variables
/* structure to hold info for a function that is currently being executed. */
struct funccall_S

View File

@ -94,3 +94,53 @@ func Test_user_func()
unlet g:retval g:counter
enew!
endfunc
func Log(val, base = 10)
return log(a:val) / log(a:base)
endfunc
func Args(mandatory, optional = v:null, ...)
return deepcopy(a:)
endfunc
func Args2(a = 1, b = 2, c = 3)
return deepcopy(a:)
endfunc
func MakeBadFunc()
func s:fcn(a, b=1, c)
endfunc
endfunc
func Test_default_arg()
call assert_equal(1.0, Log(10))
call assert_equal(log(10), Log(10, exp(1)))
call assert_fails("call Log(1,2,3)", 'E118')
let res = Args(1)
call assert_equal(res.mandatory, 1)
call assert_equal(res.optional, v:null)
call assert_equal(res['0'], 0)
let res = Args(1,2)
call assert_equal(res.mandatory, 1)
call assert_equal(res.optional, 2)
call assert_equal(res['0'], 0)
let res = Args(1,2,3)
call assert_equal(res.mandatory, 1)
call assert_equal(res.optional, 2)
call assert_equal(res['0'], 1)
call assert_fails("call MakeBadFunc()", 'E989')
call assert_fails("fu F(a=1 ,) | endf", 'E475')
let d = Args2(7, v:none, 9)
call assert_equal([7, 2, 9], [d.a, d.b, d.c])
call assert_equal("\n"
\ .. " function Args2(a = 1, b = 2, c = 3)\n"
\ .. "1 return deepcopy(a:)\n"
\ .. " endfunction",
\ execute('func Args2'))
endfunc

View File

@ -75,6 +75,7 @@ get_function_args(
char_u endchar,
garray_T *newargs,
int *varargs,
garray_T *default_args,
int skip)
{
int mustend = FALSE;
@ -82,9 +83,13 @@ get_function_args(
char_u *p = arg;
int c;
int i;
int any_default = FALSE;
char_u *expr;
if (newargs != NULL)
ga_init2(newargs, (int)sizeof(char_u *), 3);
if (default_args != NULL)
ga_init2(default_args, (int)sizeof(char_u *), 3);
if (varargs != NULL)
*varargs = FALSE;
@ -140,6 +145,43 @@ get_function_args(
*p = c;
}
if (*skipwhite(p) == '=' && default_args != NULL)
{
typval_T rettv;
any_default = TRUE;
p = skipwhite(p) + 1;
p = skipwhite(p);
expr = p;
if (eval1(&p, &rettv, FALSE) != FAIL)
{
if (ga_grow(default_args, 1) == FAIL)
goto err_ret;
// trim trailing whitespace
while (p > expr && VIM_ISWHITE(p[-1]))
p--;
c = *p;
*p = NUL;
expr = vim_strsave(expr);
if (expr == NULL)
{
*p = c;
goto err_ret;
}
((char_u **)(default_args->ga_data))
[default_args->ga_len] = expr;
default_args->ga_len++;
*p = c;
}
else
mustend = TRUE;
}
else if (any_default)
{
emsg(_("E989: Non-default argument follows default argument"));
mustend = TRUE;
}
if (*p == ',')
++p;
else
@ -163,6 +205,8 @@ get_function_args(
err_ret:
if (newargs != NULL)
ga_clear_strings(newargs);
if (default_args != NULL)
ga_clear_strings(default_args);
return FAIL;
}
@ -210,7 +254,7 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
ga_init(&newlines);
/* First, check if this is a lambda expression. "->" must exist. */
ret = get_function_args(&start, '-', NULL, NULL, TRUE);
ret = get_function_args(&start, '-', NULL, NULL, NULL, TRUE);
if (ret == FAIL || *start != '>')
return NOTDONE;
@ -220,7 +264,7 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
else
pnewargs = NULL;
*arg = skipwhite(*arg + 1);
ret = get_function_args(arg, '-', pnewargs, &varargs, FALSE);
ret = get_function_args(arg, '-', pnewargs, &varargs, NULL, FALSE);
if (ret == FAIL || **arg != '>')
goto errret;
@ -272,6 +316,7 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
STRCPY(fp->uf_name, name);
hash_add(&func_hashtab, UF2HIKEY(fp));
fp->uf_args = newargs;
ga_init(&fp->uf_def_args);
fp->uf_lines = newlines;
if (current_funccal != NULL && eval_lavars)
{
@ -729,6 +774,7 @@ call_user_func(
int using_sandbox = FALSE;
funccall_T *fc;
int save_did_emsg;
int default_arg_err = FALSE;
static int depth = 0;
dictitem_T *v;
int fixvar_idx = 0; /* index in fixvar[] */
@ -805,12 +851,13 @@ call_user_func(
/*
* Init a: variables.
* Set a:0 to "argcount".
* Set a:0 to "argcount" less number of named arguments, if >= 0.
* Set a:000 to a list with room for the "..." arguments.
*/
init_var_dict(&fc->l_avars, &fc->l_avars_var, VAR_SCOPE);
add_nr_var(&fc->l_avars, &fc->fixvar[fixvar_idx++].var, "0",
(varnumber_T)(argcount - fp->uf_args.ga_len));
(varnumber_T)(argcount >= fp->uf_args.ga_len
? argcount - fp->uf_args.ga_len : 0));
fc->l_avars.dv_lock = VAR_FIXED;
/* Use "name" to avoid a warning from some compiler that checks the
* destination size. */
@ -835,9 +882,11 @@ call_user_func(
(varnumber_T)firstline);
add_nr_var(&fc->l_avars, &fc->fixvar[fixvar_idx++].var, "lastline",
(varnumber_T)lastline);
for (i = 0; i < argcount; ++i)
for (i = 0; i < argcount || i < fp->uf_args.ga_len; ++i)
{
int addlocal = FALSE;
typval_T def_rettv;
int isdefault = FALSE;
ai = i - fp->uf_args.ga_len;
if (ai < 0)
@ -846,6 +895,25 @@ call_user_func(
name = FUNCARG(fp, i);
if (islambda)
addlocal = TRUE;
// evaluate named argument default expression
isdefault = ai + fp->uf_def_args.ga_len >= 0
&& (i >= argcount || (argvars[i].v_type == VAR_SPECIAL
&& argvars[i].vval.v_number == VVAL_NONE));
if (isdefault)
{
char_u *default_expr = NULL;
def_rettv.v_type = VAR_NUMBER;
def_rettv.vval.v_number = -1;
default_expr = ((char_u **)(fp->uf_def_args.ga_data))
[ai + fp->uf_def_args.ga_len];
if (eval1(&default_expr, &def_rettv, TRUE) == FAIL)
{
default_arg_err = 1;
break;
}
}
}
else
{
@ -867,9 +935,12 @@ call_user_func(
v->di_flags |= DI_FLAGS_RO | DI_FLAGS_FIX;
}
/* Note: the values are copied directly to avoid alloc/free.
* "argvars" must have VAR_FIXED for v_lock. */
v->di_tv = argvars[i];
if (isdefault)
v->di_tv = def_rettv;
else
// Note: the values are copied directly to avoid alloc/free.
// "argvars" must have VAR_FIXED for v_lock.
v->di_tv = argvars[i];
v->di_tv.v_lock = VAR_FIXED;
if (addlocal)
@ -988,8 +1059,11 @@ call_user_func(
save_did_emsg = did_emsg;
did_emsg = FALSE;
/* call do_cmdline() to execute the lines */
do_cmdline(NULL, get_func_line, (void *)fc,
if (default_arg_err && (fp->uf_flags & FC_ABORT))
did_emsg = TRUE;
else
// call do_cmdline() to execute the lines
do_cmdline(NULL, get_func_line, (void *)fc,
DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
--RedrawingDisabled;
@ -1145,6 +1219,7 @@ func_remove(ufunc_T *fp)
func_clear_items(ufunc_T *fp)
{
ga_clear_strings(&(fp->uf_args));
ga_clear_strings(&(fp->uf_def_args));
ga_clear_strings(&(fp->uf_lines));
#ifdef FEAT_PROFILE
vim_free(fp->uf_tml_count);
@ -1498,7 +1573,7 @@ call_func(
if (fp->uf_flags & FC_RANGE)
*doesrange = TRUE;
if (argcount < fp->uf_args.ga_len)
if (argcount < fp->uf_args.ga_len - fp->uf_def_args.ga_len)
error = ERROR_TOOFEW;
else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len)
error = ERROR_TOOMANY;
@ -1624,6 +1699,12 @@ list_func_head(ufunc_T *fp, int indent)
if (j)
msg_puts(", ");
msg_puts((char *)FUNCARG(fp, j));
if (j >= fp->uf_args.ga_len - fp->uf_def_args.ga_len)
{
msg_puts(" = ");
msg_puts(((char **)(fp->uf_def_args.ga_data))
[j - fp->uf_args.ga_len + fp->uf_def_args.ga_len]);
}
}
if (fp->uf_varargs)
{
@ -1889,6 +1970,7 @@ ex_function(exarg_T *eap)
char_u *arg;
char_u *line_arg = NULL;
garray_T newargs;
garray_T default_args;
garray_T newlines;
int varargs = FALSE;
int flags = 0;
@ -2103,7 +2185,8 @@ ex_function(exarg_T *eap)
emsg(_("E862: Cannot use g: here"));
}
if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL)
if (get_function_args(&p, ')', &newargs, &varargs,
&default_args, eap->skip) == FAIL)
goto errret_2;
/* find extra arguments "range", "dict", "abort" and "closure" */
@ -2511,6 +2594,7 @@ ex_function(exarg_T *eap)
fp->uf_refcount = 1;
}
fp->uf_args = newargs;
fp->uf_def_args = default_args;
fp->uf_lines = newlines;
if ((flags & FC_CLOSURE) != 0)
{
@ -2535,6 +2619,7 @@ ex_function(exarg_T *eap)
erret:
ga_clear_strings(&newargs);
ga_clear_strings(&default_args);
errret_2:
ga_clear_strings(&newlines);
ret_free:

View File

@ -767,6 +767,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
1310,
/**/
1309,
/**/