mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
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:
commit
fb2adadc9e
@ -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:".
|
||||
|
@ -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;
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user