vim-patch:8.2.3783: confusing error for using a variable as a function

Problem:    Confusing error for using a variable as a function.
Solution:   If a function is not found but there is a variable, give a more
            useful error. (issue vim/vim#9310)

2ef9156b42

Co-authored-by: Bram Moolenaar <Bram@vim.org>
This commit is contained in:
zeertzjq
2023-04-16 08:54:07 +08:00
parent 2e8cec5f2b
commit 68ca16c376
7 changed files with 46 additions and 19 deletions

View File

@@ -1236,7 +1236,7 @@ Note that the dot is also used for String concatenation. To avoid confusion
always put spaces around the dot for String concatenation. always put spaces around the dot for String concatenation.
expr8(expr1, ...) |Funcref| function call expr8(expr1, ...) |Funcref| function call *E1085*
When expr8 is a |Funcref| type variable, invoke the function it refers to. When expr8 is a |Funcref| type variable, invoke the function it refers to.

View File

@@ -2220,6 +2220,7 @@ static int eval_func(char **const arg, evalarg_T *const evalarg, char *const nam
const bool evaluate = flags & EVAL_EVALUATE; const bool evaluate = flags & EVAL_EVALUATE;
char *s = name; char *s = name;
int len = name_len; int len = name_len;
bool found_var = false;
if (!evaluate) { if (!evaluate) {
check_vars(s, (size_t)len); check_vars(s, (size_t)len);
@@ -2228,7 +2229,7 @@ static int eval_func(char **const arg, evalarg_T *const evalarg, char *const nam
// If "s" is the name of a variable of type VAR_FUNC // If "s" is the name of a variable of type VAR_FUNC
// use its contents. // use its contents.
partial_T *partial; partial_T *partial;
s = deref_func_name(s, &len, &partial, !evaluate); s = deref_func_name(s, &len, &partial, !evaluate, &found_var);
// Need to make a copy, in case evaluating the arguments makes // Need to make a copy, in case evaluating the arguments makes
// the name invalid. // the name invalid.
@@ -2241,6 +2242,7 @@ static int eval_func(char **const arg, evalarg_T *const evalarg, char *const nam
funcexe.fe_evaluate = evaluate; funcexe.fe_evaluate = evaluate;
funcexe.fe_partial = partial; funcexe.fe_partial = partial;
funcexe.fe_basetv = basetv; funcexe.fe_basetv = basetv;
funcexe.fe_found_var = found_var;
int ret = get_func_tv(s, len, rettv, arg, evalarg, &funcexe); int ret = get_func_tv(s, len, rettv, arg, evalarg, &funcexe);
xfree(s); xfree(s);

View File

@@ -65,6 +65,7 @@ static funccall_T *current_funccal = NULL;
// item in it is still being used. // item in it is still being used.
static funccall_T *previous_funccal = NULL; static funccall_T *previous_funccal = NULL;
static const char *e_unknownfunc = N_("E117: Unknown function: %s");
static const char *e_funcexts = N_("E122: Function %s already exists, add ! to replace it"); 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_funcdict = N_("E717: Dictionary entry already exists");
static const char *e_funcref = N_("E718: Funcref required"); static const char *e_funcref = N_("E718: Funcref required");
@@ -401,9 +402,11 @@ errret:
/// is not needed. /// is not needed.
/// @param[in] no_autoload If true, do not source autoload scripts if function /// @param[in] no_autoload If true, do not source autoload scripts if function
/// was not found. /// was not found.
/// @param[out] found_var If not NULL and a variable was found set it to true.
/// ///
/// @return name of the function. /// @return name of the function.
char *deref_func_name(const char *name, int *lenp, partial_T **const partialp, bool no_autoload) char *deref_func_name(const char *name, int *lenp, partial_T **const partialp, bool no_autoload,
bool *found_var)
FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_NONNULL_ARG(1, 2)
{ {
if (partialp != NULL) { if (partialp != NULL) {
@@ -411,18 +414,25 @@ char *deref_func_name(const char *name, int *lenp, partial_T **const partialp, b
} }
dictitem_T *const v = find_var(name, (size_t)(*lenp), NULL, no_autoload); dictitem_T *const v = find_var(name, (size_t)(*lenp), NULL, no_autoload);
if (v != NULL && v->di_tv.v_type == VAR_FUNC) { if (v == NULL) {
if (v->di_tv.vval.v_string == NULL) { // just in case return (char *)name;
}
typval_T *const tv = &v->di_tv;
if (found_var != NULL) {
*found_var = true;
}
if (tv->v_type == VAR_FUNC) {
if (tv->vval.v_string == NULL) { // just in case
*lenp = 0; *lenp = 0;
return ""; return "";
} }
*lenp = (int)strlen(v->di_tv.vval.v_string); *lenp = (int)strlen(tv->vval.v_string);
return v->di_tv.vval.v_string; return tv->vval.v_string;
} }
if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) { if (tv->v_type == VAR_PARTIAL) {
partial_T *const pt = v->di_tv.vval.v_partial; partial_T *const pt = tv->vval.v_partial;
if (pt == NULL) { // just in case if (pt == NULL) { // just in case
*lenp = 0; *lenp = 0;
return ""; return "";
@@ -1454,12 +1464,16 @@ varnumber_T callback_call_retnr(Callback *callback, int argcount, typval_T *argv
/// Give an error message for the result of a function. /// Give an error message for the result of a function.
/// Nothing if "error" is FCERR_NONE. /// Nothing if "error" is FCERR_NONE.
static void user_func_error(int error, const char *name) static void user_func_error(int error, const char *name, funcexe_T *funcexe)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_ALL
{ {
switch (error) { switch (error) {
case FCERR_UNKNOWN: case FCERR_UNKNOWN:
emsg_funcname(N_("E117: Unknown function: %s"), name); if (funcexe->fe_found_var) {
semsg(_(e_not_callable_type_str), name);
} else {
emsg_funcname(e_unknownfunc, name);
}
break; break;
case FCERR_NOTMETHOD: case FCERR_NOTMETHOD:
emsg_funcname(N_("E276: Cannot use function as a method: %s"), name); emsg_funcname(N_("E276: Cannot use function as a method: %s"), name);
@@ -1654,7 +1668,7 @@ theend:
// Report an error unless the argument evaluation or function call has been // Report an error unless the argument evaluation or function call has been
// cancelled due to an aborting error, an interrupt, or an exception. // cancelled due to an aborting error, an interrupt, or an exception.
if (!aborting()) { if (!aborting()) {
user_func_error(error, (name != NULL) ? name : funcname); user_func_error(error, (name != NULL) ? name : funcname, funcexe);
} }
// clear the copies made from the partial // clear the copies made from the partial
@@ -1846,14 +1860,13 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part
// Check if the name is a Funcref. If so, use the value. // Check if the name is a Funcref. If so, use the value.
if (lv.ll_exp_name != NULL) { if (lv.ll_exp_name != NULL) {
len = (int)strlen(lv.ll_exp_name); len = (int)strlen(lv.ll_exp_name);
name = deref_func_name(lv.ll_exp_name, &len, partial, name = deref_func_name(lv.ll_exp_name, &len, partial, flags & TFN_NO_AUTOLOAD, NULL);
flags & TFN_NO_AUTOLOAD);
if (name == lv.ll_exp_name) { if (name == lv.ll_exp_name) {
name = NULL; name = NULL;
} }
} else if (!(flags & TFN_NO_DEREF)) { } else if (!(flags & TFN_NO_DEREF)) {
len = (int)(end - *pp); len = (int)(end - *pp);
name = deref_func_name(*pp, &len, partial, flags & TFN_NO_AUTOLOAD); name = deref_func_name(*pp, &len, partial, flags & TFN_NO_AUTOLOAD, NULL);
if (name == *pp) { if (name == *pp) {
name = NULL; name = NULL;
} }
@@ -3070,7 +3083,8 @@ void ex_call(exarg_T *eap)
// contents. For VAR_PARTIAL get its partial, unless we already have one // contents. For VAR_PARTIAL get its partial, unless we already have one
// from trans_function_name(). // from trans_function_name().
len = (int)strlen(tofree); len = (int)strlen(tofree);
name = deref_func_name(tofree, &len, partial != NULL ? NULL : &partial, false); bool found_var = false;
name = deref_func_name(tofree, &len, partial != NULL ? NULL : &partial, false, &found_var);
// Skip white space to allow ":call func ()". Not good, but required for // Skip white space to allow ":call func ()". Not good, but required for
// backward compatibility. // backward compatibility.
@@ -3104,6 +3118,7 @@ void ex_call(exarg_T *eap)
funcexe.fe_evaluate = true; funcexe.fe_evaluate = true;
funcexe.fe_partial = partial; funcexe.fe_partial = partial;
funcexe.fe_selfdict = fudi.fd_dict; funcexe.fe_selfdict = fudi.fd_dict;
funcexe.fe_found_var = found_var;
if (get_func_tv(name, -1, &rettv, &arg, &evalarg, &funcexe) == FAIL) { if (get_func_tv(name, -1, &rettv, &arg, &evalarg, &funcexe) == FAIL) {
failed = true; failed = true;
break; break;

View File

@@ -75,6 +75,8 @@ typedef struct {
partial_T *fe_partial; ///< for extra arguments partial_T *fe_partial; ///< for extra arguments
dict_T *fe_selfdict; ///< Dictionary for "self" dict_T *fe_selfdict; ///< Dictionary for "self"
typval_T *fe_basetv; ///< base for base->method() typval_T *fe_basetv; ///< base for base->method()
bool fe_found_var; ///< if the function is not found then give an
///< error that a variable is not callable.
} funcexe_T; } funcexe_T;
#define FUNCEXE_INIT (funcexe_T) { \ #define FUNCEXE_INIT (funcexe_T) { \
@@ -86,6 +88,7 @@ typedef struct {
.fe_partial = NULL, \ .fe_partial = NULL, \
.fe_selfdict = NULL, \ .fe_selfdict = NULL, \
.fe_basetv = NULL, \ .fe_basetv = NULL, \
.fe_found_var = false, \
} }
#define FUNCARG(fp, j) ((char **)(fp->uf_args.ga_data))[j] #define FUNCARG(fp, j) ((char **)(fp->uf_args.ga_data))[j]

View File

@@ -1000,6 +1000,7 @@ EXTERN const char e_fnametoolong[] INIT(= N_("E856: Filename too long"));
EXTERN const char e_float_as_string[] INIT(= N_("E806: using Float as a String")); EXTERN const char e_float_as_string[] INIT(= N_("E806: using Float as a String"));
EXTERN const char e_inval_string[] INIT(= N_("E908: using an invalid value as a String")); EXTERN const char e_inval_string[] INIT(= N_("E908: using an invalid value as a String"));
EXTERN const char e_cannot_edit_other_buf[] INIT(= N_("E788: Not allowed to edit another buffer now")); EXTERN const char e_cannot_edit_other_buf[] INIT(= N_("E788: Not allowed to edit another buffer now"));
EXTERN const char e_not_callable_type_str[] INIT(= N_("E1085: Not a callable type: %s"));
EXTERN const char e_cmdmap_err[] INIT(= N_("E5520: <Cmd> mapping must end with <CR>")); EXTERN const char e_cmdmap_err[] INIT(= N_("E5520: <Cmd> mapping must end with <CR>"));
EXTERN const char e_cmdmap_repeated[] EXTERN const char e_cmdmap_repeated[]

View File

@@ -545,7 +545,7 @@ describe('v:lua', function()
eq("Vim:E15: Invalid expression: v:['lua'].foo()", pcall_err(eval, "v:['lua'].foo()")) eq("Vim:E15: Invalid expression: v:['lua'].foo()", pcall_err(eval, "v:['lua'].foo()"))
eq("Vim(call):E461: Illegal variable name: v:['lua']", pcall_err(command, "call v:['lua'].baar()")) eq("Vim(call):E461: Illegal variable name: v:['lua']", pcall_err(command, "call v:['lua'].baar()"))
eq("Vim:E117: Unknown function: v:lua", pcall_err(eval, "v:lua()")) eq("Vim:E1085: Not a callable type: v:lua", pcall_err(eval, "v:lua()"))
eq("Vim(let):E46: Cannot change read-only variable \"v:['lua']\"", pcall_err(command, "let v:['lua'] = 'xx'")) eq("Vim(let):E46: Cannot change read-only variable \"v:['lua']\"", pcall_err(command, "let v:['lua'] = 'xx'"))
eq("Vim(let):E46: Cannot change read-only variable \"v:lua\"", pcall_err(command, "let v:lua = 'xx'")) eq("Vim(let):E46: Cannot change read-only variable \"v:lua\"", pcall_err(command, "let v:lua = 'xx'"))
@@ -553,7 +553,7 @@ describe('v:lua', function()
eq("Vim:E107: Missing parentheses: v:lua.func", pcall_err(eval, "'bad'->v:lua.func")) eq("Vim:E107: Missing parentheses: v:lua.func", pcall_err(eval, "'bad'->v:lua.func"))
eq("Vim:E274: No white space allowed before parenthesis", pcall_err(eval, "'bad'->v:lua.func ()")) eq("Vim:E274: No white space allowed before parenthesis", pcall_err(eval, "'bad'->v:lua.func ()"))
eq("Vim:E107: Missing parentheses: v:lua", pcall_err(eval, "'bad'->v:lua")) eq("Vim:E107: Missing parentheses: v:lua", pcall_err(eval, "'bad'->v:lua"))
eq("Vim:E117: Unknown function: v:lua", pcall_err(eval, "'bad'->v:lua()")) eq("Vim:E1085: Not a callable type: v:lua", pcall_err(eval, "'bad'->v:lua()"))
eq("Vim:E15: Invalid expression: v:lua.()", pcall_err(eval, "'bad'->v:lua.()")) eq("Vim:E15: Invalid expression: v:lua.()", pcall_err(eval, "'bad'->v:lua.()"))
end) end)
end) end)

View File

@@ -2019,6 +2019,12 @@ func Test_call()
" call assert_fails('call test_null_function()()', 'E1192:') " call assert_fails('call test_null_function()()', 'E1192:')
" Nvim doesn't have null partials " Nvim doesn't have null partials
" call assert_fails('call test_null_partial()()', 'E117:') " call assert_fails('call test_null_partial()()', 'E117:')
let lines =<< trim END
let Time = 'localtime'
call Time()
END
CheckScriptFailure(lines, 'E1085:')
endfunc endfunc
func Test_char2nr() func Test_char2nr()