diff options
-rw-r--r-- | src/nvim/eval/funcs.c | 25 | ||||
-rw-r--r-- | src/nvim/eval/userfunc.c | 31 | ||||
-rw-r--r-- | src/nvim/globals.h | 1 | ||||
-rw-r--r-- | test/old/testdir/test_user_func.vim | 61 |
4 files changed, 112 insertions, 6 deletions
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index f53b283c79..b064379210 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -228,6 +228,31 @@ const EvalFuncDef *find_internal_func(const char *const name) return index >= 0 ? &functions[index] : NULL; } +/// Check the argument count to use for internal function "fdef". +/// @return -1 for failure, 0 if no method base accepted, 1 if method base is +/// first argument, 2 if method base is second argument, etc. +int check_internal_func(const EvalFuncDef *const fdef, const int argcount) + FUNC_ATTR_NONNULL_ALL +{ + int res; + + if (argcount < fdef->min_argc) { + res = FCERR_TOOFEW; + } else if (argcount > fdef->max_argc) { + res = FCERR_TOOMANY; + } else { + return fdef->base_arg; + } + + const char *const name = fdef->name; + if (res == FCERR_TOOMANY) { + semsg(_(e_toomanyarg), name); + } else { + semsg(_(e_toofewarg), name); + } + return -1; +} + int call_internal_func(const char *const fname, const int argcount, typval_T *const argvars, typval_T *const rettv) FUNC_ATTR_NONNULL_ALL diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 32f9f9182c..65918ba2bf 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -72,7 +72,7 @@ static funccall_T *current_funccal = NULL; // item in it is still being used. static funccall_T *previous_funccal = NULL; -static const char *e_unknownfunc = N_("E117: Unknown function: %s"); +static const char *e_unknown_function_str = N_("E117: Unknown function: %s"); static const char *e_funcexts = N_("E122: Function %s already exists, add ! to replace it"); static const char *e_funcdict = N_("E717: Dictionary entry already exists"); static const char *e_funcref = N_("E718: Funcref required"); @@ -1523,14 +1523,14 @@ varnumber_T callback_call_retnr(Callback *callback, int argcount, typval_T *argv /// Give an error message for the result of a function. /// Nothing if "error" is FCERR_NONE. static void user_func_error(int error, const char *name, funcexe_T *funcexe) - FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_NONNULL_ARG(2) { switch (error) { case FCERR_UNKNOWN: if (funcexe->fe_found_var) { semsg(_(e_not_callable_type_str), name); } else { - emsg_funcname(e_unknownfunc, name); + emsg_funcname(e_unknown_function_str, name); } break; case FCERR_NOTMETHOD: @@ -1543,7 +1543,7 @@ static void user_func_error(int error, const char *name, funcexe_T *funcexe) emsg_funcname(_(e_toomanyarg), name); break; case FCERR_TOOFEW: - emsg_funcname(N_("E119: Not enough arguments for function: %s"), name); + emsg_funcname(_(e_toofewarg), name); break; case FCERR_SCRIPT: emsg_funcname(N_("E120: Using <SID> not in a script context: %s"), name); @@ -3172,6 +3172,29 @@ static int ex_defer_inner(char *name, char **arg, const partial_T *const partial } int r = get_func_arguments(arg, evalarg, false, argvars + partial_argc, &argcount); argcount += partial_argc; + + if (r == OK) { + if (builtin_function(name, -1)) { + const EvalFuncDef *const fdef = find_internal_func(name); + if (fdef == NULL) { + emsg_funcname(e_unknown_function_str, name); + r = FAIL; + } else if (check_internal_func(fdef, argcount) == -1) { + r = FAIL; + } + } else { + ufunc_T *ufunc = find_func(name); + // we tolerate an unknown function here, it might be defined later + if (ufunc != NULL) { + int error = check_user_func_argcount(ufunc, argcount); + if (error != FCERR_UNKNOWN) { + user_func_error(error, name, NULL); + r = FAIL; + } + } + } + } + if (r == FAIL) { while (--argcount >= 0) { tv_clear(&argvars[argcount]); diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 66df1f8920..0c7f55714c 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -953,6 +953,7 @@ EXTERN const char e_dictreq[] INIT(= N_("E715: Dictionary required")); EXTERN const char e_blobidx[] INIT(= N_("E979: Blob index out of range: %" PRId64)); EXTERN const char e_invalblob[] INIT(= N_("E978: Invalid operation for Blob")); EXTERN const char e_toomanyarg[] INIT(= N_("E118: Too many arguments for function: %s")); +EXTERN const char e_toofewarg[] INIT(= N_("E119: Not enough arguments for function: %s")); EXTERN const char e_dictkey[] INIT(= N_("E716: Key not present in Dictionary: \"%s\"")); EXTERN const char e_listreq[] INIT(= N_("E714: List required")); EXTERN const char e_listblobreq[] INIT(= N_("E897: List or Blob required")); diff --git a/test/old/testdir/test_user_func.vim b/test/old/testdir/test_user_func.vim index 5a1931e690..0f9b40814c 100644 --- a/test/old/testdir/test_user_func.vim +++ b/test/old/testdir/test_user_func.vim @@ -5,6 +5,7 @@ source check.vim source shared.vim +source vim9.vim func Table(title, ...) let ret = a:title @@ -627,7 +628,7 @@ func Test_defer_quitall() call DeferLevelOne() END call writefile(lines, 'XdeferQuitall', 'D') - let res = system(GetVimCommandClean() .. ' -X -S XdeferQuitall') + let res = system(GetVimCommand() .. ' -X -S XdeferQuitall') call assert_equal(0, v:shell_error) call assert_false(filereadable('XQuitallOne')) call assert_false(filereadable('XQuitallTwo')) @@ -650,7 +651,7 @@ func Test_defer_quitall_in_expr_func() call Test_defer_in_funcref() END call writefile(lines, 'XdeferQuitallExpr', 'D') - let res = system(GetVimCommandClean() .. ' -X -S XdeferQuitallExpr') + let res = system(GetVimCommand() .. ' -X -S XdeferQuitallExpr') call assert_equal(0, v:shell_error) call assert_false(filereadable('Xentry0')) call assert_false(filereadable('Xentry1')) @@ -662,5 +663,61 @@ func FuncIndex(idx, val) return a:val == 'c' endfunc +func Test_defer_wrong_arguments() + call assert_fails('defer delete()', 'E119:') + call assert_fails('defer FuncIndex(1)', 'E119:') + call assert_fails('defer delete(1, 2, 3)', 'E118:') + call assert_fails('defer FuncIndex(1, 2, 3)', 'E118:') + + throw 'Skipped: Vim9 script is N/A' + let lines =<< trim END + def DeferFunc0() + defer delete() + enddef + defcompile + END + call v9.CheckScriptFailure(lines, 'E119:') + let lines =<< trim END + def DeferFunc3() + defer delete(1, 2, 3) + enddef + defcompile + END + call v9.CheckScriptFailure(lines, 'E118:') + let lines =<< trim END + def DeferFunc2() + defer delete(1, 2) + enddef + defcompile + END + call v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected string but got number') + + def g:FuncOneArg(arg: string) + echo arg + enddef + + let lines =<< trim END + def DeferUserFunc0() + defer g:FuncOneArg() + enddef + defcompile + END + call v9.CheckScriptFailure(lines, 'E119:') + let lines =<< trim END + def DeferUserFunc2() + defer g:FuncOneArg(1, 2) + enddef + defcompile + END + call v9.CheckScriptFailure(lines, 'E118:') + let lines =<< trim END + def DeferUserFunc1() + defer g:FuncOneArg(1) + enddef + defcompile + END + call v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected string but got number') +endfunc + " vim: shiftwidth=2 sts=2 expandtab |