Merge pull request #13988 from janlazo/vim-8.1.1310

vim-patch:8.1.1310: named function arguments are never optional
This commit is contained in:
Jan Edmund Lazo 2021-02-22 15:04:16 -05:00 committed by GitHub
commit fb2adadc9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 173 additions and 14 deletions

View File

@ -9779,15 +9779,49 @@ 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 .. ": " .. a: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 also only
evaluated when arguments are not specified during a call.
*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

@ -315,6 +315,7 @@ struct ufunc {
int uf_calls; ///< nr of active calls
bool 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
int uf_profiling; ///< true when func is being profiled
int uf_prof_initialized;

View File

@ -67,7 +67,7 @@ void func_init(void)
/// Get function arguments.
static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs,
int *varargs, bool skip)
int *varargs, garray_T *default_args, bool skip)
{
bool mustend = false;
char_u *arg = *argp;
@ -78,12 +78,16 @@ static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs,
if (newargs != NULL) {
ga_init(newargs, (int)sizeof(char_u *), 3);
}
if (default_args != NULL) {
ga_init(default_args, (int)sizeof(char_u *), 3);
}
if (varargs != NULL) {
*varargs = false;
}
// Isolate the arguments: "arg1, arg2, ...)"
bool any_default = false;
while (*p != endchar) {
if (p[0] == '.' && p[1] == '.' && p[2] == '.') {
if (varargs != NULL) {
@ -123,6 +127,38 @@ static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs,
*p = c;
}
if (*skipwhite(p) == '=' && default_args != NULL) {
typval_T rettv;
any_default = true;
p = skipwhite(p) + 1;
p = skipwhite(p);
char_u *expr = p;
if (eval1(&p, &rettv, false) != FAIL) {
ga_grow(default_args, 1);
// trim trailing whitespace
while (p > expr && ascii_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 {
@ -149,6 +185,9 @@ err_ret:
if (newargs != NULL) {
ga_clear_strings(newargs);
}
if (default_args != NULL) {
ga_clear_strings(default_args);
}
return FAIL;
}
@ -195,7 +234,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
bool eval_lavars = false;
// First, check if this is a lambda expression. "->" must exists.
ret = get_function_args(&start, '-', NULL, NULL, true);
ret = get_function_args(&start, '-', NULL, NULL, NULL, true);
if (ret == FAIL || *start != '>') {
return NOTDONE;
}
@ -207,7 +246,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
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;
}
@ -259,6 +298,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
STRCPY(fp->uf_name, name);
hash_add(&func_hashtab, UF2HIKEY(fp));
fp->uf_args = newargs;
ga_init(&fp->uf_def_args, (int)sizeof(char_u *), 1);
fp->uf_lines = newlines;
if (current_funccal != NULL && eval_lavars) {
flags |= FC_CLOSURE;
@ -715,6 +755,7 @@ static bool func_remove(ufunc_T *fp)
static void 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));
if (fp->uf_cb_free != NULL) {
@ -857,12 +898,13 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
}
// Init a: variables, unless none found (in lambda).
// 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);
if ((fp->uf_flags & FC_NOARGS) == 0) {
add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "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;
if ((fp->uf_flags & FC_NOARGS) == 0) {
@ -892,8 +934,11 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++],
"lastline", (varnumber_T)lastline);
}
for (int i = 0; i < argcount; i++) {
bool default_arg_err = false;
for (int i = 0; i < argcount || i < fp->uf_args.ga_len; i++) {
bool addlocal = false;
bool isdefault = false;
typval_T def_rettv;
ai = i - fp->uf_args.ga_len;
if (ai < 0) {
@ -902,6 +947,21 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
if (islambda) {
addlocal = true;
}
// evaluate named argument default expression
isdefault = ai + fp->uf_def_args.ga_len >= 0 && i >= argcount;
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 = true;
break;
}
}
} else {
if ((fp->uf_flags & FC_NOARGS) != 0) {
// Bail out if no a: arguments used (in lambda).
@ -922,7 +982,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
// 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 = isdefault ? def_rettv : argvars[i];
v->di_tv.v_lock = VAR_FIXED;
if (addlocal) {
@ -1046,7 +1106,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
save_did_emsg = did_emsg;
did_emsg = FALSE;
if (islambda) {
if (default_arg_err && (fp->uf_flags & FC_ABORT)) {
did_emsg = true;
} else if (islambda) {
char_u *p = *(char_u **)fp->uf_lines.ga_data + 7;
// A Lambda always has the command "return {expr}". It is much faster
@ -1490,7 +1552,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;
@ -1573,6 +1635,11 @@ static void list_func_head(ufunc_T *fp, int indent, bool force)
msg_puts(", ");
}
msg_puts((const 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) {
if (j) {
@ -1836,6 +1903,7 @@ void 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;
@ -2039,7 +2107,8 @@ void ex_function(exarg_T *eap)
}
}
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;
}
@ -2458,6 +2527,7 @@ void 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) {
register_closure(fp);
@ -2480,6 +2550,7 @@ void 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

@ -95,6 +95,59 @@ func Test_user_func()
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')
" Since neovim does not have v:none, the ability to use the default
" argument with the intermediate argument set to v:none has been omitted.
" Therefore, this test is not performed.
" 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
func Test_failed_call_in_try()
try | call UnknownFunc() | catch | endtry
endfunc