aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/api/vim.c9
-rw-r--r--src/nvim/eval.c525
-rw-r--r--src/nvim/eval.lua225
-rw-r--r--src/nvim/eval/funcs.c57
-rw-r--r--src/nvim/eval/funcs.h5
-rw-r--r--src/nvim/eval/userfunc.c139
-rw-r--r--src/nvim/eval/userfunc.h26
-rw-r--r--src/nvim/generators/gen_eval.lua7
-rw-r--r--src/nvim/globals.h1
-rw-r--r--src/nvim/lua/executor.c10
-rw-r--r--src/nvim/normal.c12
-rw-r--r--src/nvim/regexp.c14
-rw-r--r--src/nvim/testdir/sautest/autoload/foo.vim4
-rw-r--r--src/nvim/testdir/test_arglist.vim4
-rw-r--r--src/nvim/testdir/test_assert.vim2
-rw-r--r--src/nvim/testdir/test_autoload.vim2
-rw-r--r--src/nvim/testdir/test_bufline.vim8
-rw-r--r--src/nvim/testdir/test_bufwintabinfo.vim2
-rw-r--r--src/nvim/testdir/test_cindent.vim10
-rw-r--r--src/nvim/testdir/test_diffmode.vim14
-rw-r--r--src/nvim/testdir/test_float_func.vim27
-rw-r--r--src/nvim/testdir/test_functions.vim57
-rw-r--r--src/nvim/testdir/test_hide.vim2
-rw-r--r--src/nvim/testdir/test_lambda.vim2
-rw-r--r--src/nvim/testdir/test_match.vim2
-rw-r--r--src/nvim/testdir/test_method.vim159
-rw-r--r--src/nvim/testdir/test_popup.vim6
-rw-r--r--src/nvim/testdir/test_syntax.vim4
-rw-r--r--src/nvim/testdir/test_system.vim4
-rw-r--r--src/nvim/testdir/test_user_func.vim18
-rw-r--r--src/nvim/testdir/test_vimscript.vim4
31 files changed, 974 insertions, 387 deletions
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 90c43a1b04..2536249de5 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -608,12 +608,15 @@ static Object _call_function(String fn, Array args, dict_T *self, Error *err)
recursive++;
try_start();
typval_T rettv;
- int dummy;
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.firstline = curwin->w_cursor.lnum;
+ funcexe.lastline = curwin->w_cursor.lnum;
+ funcexe.evaluate = true;
+ funcexe.selfdict = self;
// call_func() retval is deceptive, ignore it. Instead we set `msg_list`
// (see above) to capture abort-causing non-exception errors.
(void)call_func((char_u *)fn.data, (int)fn.size, &rettv, (int)args.size,
- vim_args, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum,
- &dummy, true, NULL, self);
+ vim_args, &funcexe);
if (!try_end(err)) {
rv = vim_to_object(&rettv);
}
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 5e18a77b6d..5603fbb082 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -65,6 +65,8 @@ static char *e_missbrac = N_("E111: Missing ']'");
static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary");
static char *e_illvar = N_("E461: Illegal variable name: %s");
static char *e_cannot_mod = N_("E995: Cannot modify existing variable");
+static char *e_nowhitespace
+ = N_("E274: No white space allowed before parenthesis");
static char *e_invalwindow = N_("E957: Invalid window number");
static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s");
@@ -736,15 +738,15 @@ int eval_expr_typval(const typval_T *expr, typval_T *argv,
int argc, typval_T *rettv)
FUNC_ATTR_NONNULL_ARG(1, 2, 4)
{
- int dummy;
+ funcexe_T funcexe = FUNCEXE_INIT;
if (expr->v_type == VAR_FUNC) {
const char_u *const s = expr->vval.v_string;
if (s == NULL || *s == NUL) {
return FAIL;
}
- if (call_func(s, -1, rettv, argc, argv, NULL,
- 0L, 0L, &dummy, true, NULL, NULL) == FAIL) {
+ funcexe.evaluate = true;
+ if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) {
return FAIL;
}
} else if (expr->v_type == VAR_PARTIAL) {
@@ -753,8 +755,9 @@ int eval_expr_typval(const typval_T *expr, typval_T *argv,
if (s == NULL || *s == NUL) {
return FAIL;
}
- if (call_func(s, -1, rettv, argc, argv, NULL,
- 0L, 0L, &dummy, true, partial, NULL) == FAIL) {
+ funcexe.evaluate = true;
+ funcexe.partial = partial;
+ if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) {
return FAIL;
}
} else {
@@ -1050,7 +1053,6 @@ int call_vim_function(
)
FUNC_ATTR_NONNULL_ALL
{
- int doesrange;
int ret;
int len = (int)STRLEN(func);
partial_T *pt = NULL;
@@ -1066,9 +1068,12 @@ int call_vim_function(
}
rettv->v_type = VAR_UNKNOWN; // tv_clear() uses this.
- ret = call_func(func, len, rettv, argc, argv, NULL,
- curwin->w_cursor.lnum, curwin->w_cursor.lnum,
- &doesrange, true, pt, NULL);
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.firstline = curwin->w_cursor.lnum;
+ funcexe.lastline = curwin->w_cursor.lnum;
+ funcexe.evaluate = true;
+ funcexe.partial = pt;
+ ret = call_func(func, len, rettv, argc, argv, &funcexe);
fail:
if (ret == FAIL) {
@@ -1724,7 +1729,9 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first)
} else {
// handle d.key, l[idx], f(expr)
const char *const arg_subsc = arg;
- if (handle_subscript(&arg, &tv, true, true) == FAIL) {
+ if (handle_subscript(&arg, &tv, true, true, (const char_u *)name,
+ (const char_u **)&name)
+ == FAIL) {
error = true;
} else {
if (arg == arg_subsc && len == 2 && name[1] == ':') {
@@ -3142,6 +3149,65 @@ static int pattern_match(char_u *pat, char_u *text, bool ic)
return matches;
}
+/// Handle a name followed by "(". Both for just "name(arg)" and for
+/// "expr->name(arg)".
+//
+/// @param arg Points to "(", will be advanced
+/// @param basetv "expr" for "expr->name(arg)"
+//
+/// @return OK or FAIL.
+static int eval_func(char_u **const arg, char_u *const name, const int name_len,
+ typval_T *const rettv, const bool evaluate,
+ typval_T *const basetv)
+ FUNC_ATTR_NONNULL_ARG(1, 2, 4)
+{
+ char_u *s = name;
+ int len = name_len;
+
+ if (!evaluate) {
+ check_vars((const char *)s, len);
+ }
+
+ // If "s" is the name of a variable of type VAR_FUNC
+ // use its contents.
+ partial_T *partial;
+ s = deref_func_name((const char *)s, &len, &partial, !evaluate);
+
+ // Need to make a copy, in case evaluating the arguments makes
+ // the name invalid.
+ s = xmemdupz(s, len);
+
+ // Invoke the function.
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.firstline = curwin->w_cursor.lnum;
+ funcexe.lastline = curwin->w_cursor.lnum;
+ funcexe.evaluate = evaluate;
+ funcexe.partial = partial;
+ funcexe.basetv = basetv;
+ int ret = get_func_tv(s, len, rettv, arg, &funcexe);
+
+ xfree(s);
+
+ // If evaluate is false rettv->v_type was not set in
+ // get_func_tv, but it's needed in handle_subscript() to parse
+ // what follows. So set it here.
+ if (rettv->v_type == VAR_UNKNOWN && !evaluate && **arg == '(') {
+ rettv->vval.v_string = (char_u *)tv_empty_string;
+ rettv->v_type = VAR_FUNC;
+ }
+
+ // Stop the expression evaluation when immediately
+ // aborting on error, or when an interrupt occurred or
+ // an exception was thrown but not caught.
+ if (evaluate && aborting()) {
+ if (ret == OK) {
+ tv_clear(rettv);
+ }
+ ret = FAIL;
+ }
+ return ret;
+}
+
// TODO(ZyX-I): move to eval/expressions
/*
@@ -3161,6 +3227,8 @@ int eval0(char_u *arg, typval_T *rettv, char_u **nextcmd, int evaluate)
{
int ret;
char_u *p;
+ const int did_emsg_before = did_emsg;
+ const int called_emsg_before = called_emsg;
p = skipwhite(arg);
ret = eval1(&p, rettv, evaluate);
@@ -3170,8 +3238,10 @@ int eval0(char_u *arg, typval_T *rettv, char_u **nextcmd, int evaluate)
}
// Report the invalid expression unless the expression evaluation has
// been cancelled due to an aborting error, an interrupt, or an
- // exception.
- if (!aborting()) {
+ // exception, or we already gave a more specific error.
+ // Also check called_emsg for when using assert_fails().
+ if (!aborting() && did_emsg == did_emsg_before
+ && called_emsg == called_emsg_before) {
emsgf(_(e_invexpr2), arg);
}
ret = FAIL;
@@ -3801,6 +3871,7 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string)
// + in front unary plus (ignored)
// trailing [] subscript in String or List
// trailing .name entry in Dictionary
+// trailing ->name() method call
//
// "arg" must point to the first non-white of the expression.
// "arg" is advanced to the next non-white after the recognized expression.
@@ -3815,10 +3886,10 @@ static int eval7(
{
varnumber_T n;
int len;
- char_u *s;
- char_u *start_leader, *end_leader;
+ char_u *s;
+ const char_u *start_leader, *end_leader;
int ret = OK;
- char_u *alias;
+ char_u *alias;
// Initialise variable so that tv_clear() can't mistake this for a
// string and free a string that isn't there.
@@ -3968,44 +4039,7 @@ static int eval7(
ret = FAIL;
} else {
if (**arg == '(') { // recursive!
- partial_T *partial;
-
- if (!evaluate) {
- check_vars((const char *)s, len);
- }
-
- // If "s" is the name of a variable of type VAR_FUNC
- // use its contents.
- s = deref_func_name((const char *)s, &len, &partial, !evaluate);
-
- // Need to make a copy, in case evaluating the arguments makes
- // the name invalid.
- s = xmemdupz(s, len);
-
- // Invoke the function.
- ret = get_func_tv(s, len, rettv, arg,
- curwin->w_cursor.lnum, curwin->w_cursor.lnum,
- &len, evaluate, partial, NULL);
-
- xfree(s);
-
- // If evaluate is false rettv->v_type was not set in
- // get_func_tv, but it's needed in handle_subscript() to parse
- // what follows. So set it here.
- if (rettv->v_type == VAR_UNKNOWN && !evaluate && **arg == '(') {
- rettv->vval.v_string = (char_u *)tv_empty_string;
- rettv->v_type = VAR_FUNC;
- }
-
- // Stop the expression evaluation when immediately
- // aborting on error, or when an interrupt occurred or
- // an exception was thrown but not caught.
- if (evaluate && aborting()) {
- if (ret == OK) {
- tv_clear(rettv);
- }
- ret = FAIL;
- }
+ ret = eval_func(arg, s, len, rettv, evaluate, NULL);
} else if (evaluate) {
ret = get_var_tv((const char *)s, len, rettv, NULL, true, false);
} else {
@@ -4019,53 +4053,232 @@ static int eval7(
*arg = skipwhite(*arg);
// Handle following '[', '(' and '.' for expr[expr], expr.name,
- // expr(expr).
+ // expr(expr), expr->name(expr)
if (ret == OK) {
- ret = handle_subscript((const char **)arg, rettv, evaluate, true);
+ ret = handle_subscript((const char **)arg, rettv, evaluate, true,
+ start_leader, &end_leader);
}
// Apply logical NOT and unary '-', from right to left, ignore '+'.
if (ret == OK && evaluate && end_leader > start_leader) {
- bool error = false;
- varnumber_T val = 0;
- float_T f = 0.0;
+ ret = eval7_leader(rettv, start_leader, &end_leader);
+ }
+ return ret;
+}
+/// Apply the leading "!" and "-" before an eval7 expression to "rettv".
+/// Adjusts "end_leaderp" until it is at "start_leader".
+/// @return OK on success, FAIL on failure.
+static int eval7_leader(typval_T *const rettv, const char_u *const start_leader,
+ const char_u **const end_leaderp)
+ FUNC_ATTR_NONNULL_ALL
+{
+ const char_u *end_leader = *end_leaderp;
+ int ret = OK;
+ bool error = false;
+ varnumber_T val = 0;
+ float_T f = 0.0;
+
+ if (rettv->v_type == VAR_FLOAT) {
+ f = rettv->vval.v_float;
+ } else {
+ val = tv_get_number_chk(rettv, &error);
+ }
+ if (error) {
+ tv_clear(rettv);
+ ret = FAIL;
+ } else {
+ while (end_leader > start_leader) {
+ end_leader--;
+ if (*end_leader == '!') {
+ if (rettv->v_type == VAR_FLOAT) {
+ f = !f;
+ } else {
+ val = !val;
+ }
+ } else if (*end_leader == '-') {
+ if (rettv->v_type == VAR_FLOAT) {
+ f = -f;
+ } else {
+ val = -val;
+ }
+ }
+ }
if (rettv->v_type == VAR_FLOAT) {
- f = rettv->vval.v_float;
+ tv_clear(rettv);
+ rettv->vval.v_float = f;
} else {
- val = tv_get_number_chk(rettv, &error);
- }
- if (error) {
tv_clear(rettv);
- ret = FAIL;
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = val;
+ }
+ }
+
+ *end_leaderp = end_leader;
+ return ret;
+}
+
+/// Call the function referred to in "rettv".
+/// @param lua_funcname If `rettv` refers to a v:lua function, this must point
+/// to the name of the Lua function to call (after the
+/// "v:lua." prefix).
+/// @return OK on success, FAIL on failure.
+static int call_func_rettv(char_u **const arg,
+ typval_T *const rettv,
+ const bool evaluate,
+ dict_T *const selfdict,
+ typval_T *const basetv,
+ const char_u *const lua_funcname)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
+{
+ partial_T *pt = NULL;
+ typval_T functv;
+ const char_u *funcname;
+ bool is_lua = false;
+
+ // need to copy the funcref so that we can clear rettv
+ if (evaluate) {
+ functv = *rettv;
+ rettv->v_type = VAR_UNKNOWN;
+
+ // Invoke the function. Recursive!
+ if (functv.v_type == VAR_PARTIAL) {
+ pt = functv.vval.v_partial;
+ is_lua = is_luafunc(pt);
+ funcname = is_lua ? lua_funcname : partial_name(pt);
} else {
- while (end_leader > start_leader) {
- --end_leader;
- if (*end_leader == '!') {
- if (rettv->v_type == VAR_FLOAT) {
- f = !f;
- } else {
- val = !val;
- }
- } else if (*end_leader == '-') {
- if (rettv->v_type == VAR_FLOAT) {
- f = -f;
- } else {
- val = -val;
- }
- }
+ funcname = functv.vval.v_string;
+ }
+ } else {
+ funcname = (char_u *)"";
+ }
+
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.firstline = curwin->w_cursor.lnum;
+ funcexe.lastline = curwin->w_cursor.lnum;
+ funcexe.evaluate = evaluate;
+ funcexe.partial = pt;
+ funcexe.selfdict = selfdict;
+ funcexe.basetv = basetv;
+ const int ret = get_func_tv(funcname, is_lua ? *arg - funcname : -1, rettv,
+ (char_u **)arg, &funcexe);
+
+ // Clear the funcref afterwards, so that deleting it while
+ // evaluating the arguments is possible (see test55).
+ if (evaluate) {
+ tv_clear(&functv);
+ }
+
+ return ret;
+}
+
+/// Evaluate "->method()".
+/// @param verbose if true, give error messages.
+/// @note "*arg" points to the '-'.
+/// @return FAIL or OK. @note "*arg" is advanced to after the ')'.
+static int eval_lambda(char_u **const arg, typval_T *const rettv,
+ const bool evaluate, const bool verbose)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // Skip over the ->.
+ *arg += 2;
+ typval_T base = *rettv;
+ rettv->v_type = VAR_UNKNOWN;
+
+ int ret = get_lambda_tv(arg, rettv, evaluate);
+ if (ret == NOTDONE) {
+ return FAIL;
+ } else if (**arg != '(') {
+ if (verbose) {
+ if (*skipwhite(*arg) == '(') {
+ EMSG(_(e_nowhitespace));
+ } else {
+ EMSG2(_(e_missingparen), "lambda");
}
- if (rettv->v_type == VAR_FLOAT) {
- tv_clear(rettv);
- rettv->vval.v_float = f;
+ }
+ tv_clear(rettv);
+ ret = FAIL;
+ } else {
+ ret = call_func_rettv(arg, rettv, evaluate, NULL, &base, NULL);
+ }
+
+ // Clear the funcref afterwards, so that deleting it while
+ // evaluating the arguments is possible (see test55).
+ if (evaluate) {
+ tv_clear(&base);
+ }
+
+ return ret;
+}
+
+/// Evaluate "->method()" or "->v:lua.method()".
+/// @note "*arg" points to the '-'.
+/// @return FAIL or OK. "*arg" is advanced to after the ')'.
+static int eval_method(char_u **const arg, typval_T *const rettv,
+ const bool evaluate, const bool verbose)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // Skip over the ->.
+ *arg += 2;
+ typval_T base = *rettv;
+ rettv->v_type = VAR_UNKNOWN;
+
+ // Locate the method name.
+ int len;
+ char_u *name = *arg;
+ char_u *lua_funcname = NULL;
+ if (STRNCMP(name, "v:lua.", 6) == 0) {
+ lua_funcname = name + 6;
+ *arg = (char_u *)skip_luafunc_name((const char *)lua_funcname);
+ *arg = skipwhite(*arg); // to detect trailing whitespace later
+ len = *arg - lua_funcname;
+ } else {
+ char_u *alias;
+ len = get_name_len((const char **)arg, (char **)&alias, evaluate, true);
+ if (alias != NULL) {
+ name = alias;
+ }
+ }
+
+ int ret;
+ if (len <= 0) {
+ if (verbose) {
+ if (lua_funcname == NULL) {
+ EMSG(_("E260: Missing name after ->"));
} else {
- tv_clear(rettv);
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = val;
+ EMSG2(_(e_invexpr2), name);
+ }
+ }
+ ret = FAIL;
+ } else {
+ if (**arg != '(') {
+ if (verbose) {
+ EMSG2(_(e_missingparen), name);
+ }
+ ret = FAIL;
+ } else if (ascii_iswhite((*arg)[-1])) {
+ if (verbose) {
+ EMSG(_(e_nowhitespace));
+ }
+ ret = FAIL;
+ } else if (lua_funcname != NULL) {
+ if (evaluate) {
+ rettv->v_type = VAR_PARTIAL;
+ rettv->vval.v_partial = vvlua_partial;
+ rettv->vval.v_partial->pt_refcount++;
}
+ ret = call_func_rettv(arg, rettv, evaluate, NULL, &base, lua_funcname);
+ } else {
+ ret = eval_func(arg, name, len, rettv, evaluate, &base);
}
}
+ // Clear the funcref afterwards, so that deleting it while
+ // evaluating the arguments is possible (see test55).
+ if (evaluate) {
+ tv_clear(&base);
+ }
+
return ret;
}
@@ -7255,10 +7468,12 @@ bool callback_call(Callback *const callback, const int argcount_in,
abort();
}
- int dummy;
- return call_func(name, -1, rettv, argcount_in, argvars_in,
- NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy,
- true, partial, NULL);
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.firstline = curwin->w_cursor.lnum;
+ funcexe.lastline = curwin->w_cursor.lnum;
+ funcexe.evaluate = true;
+ funcexe.partial = partial;
+ return call_func(name, -1, rettv, argcount_in, argvars_in, &funcexe);
}
static bool set_ref_in_callback(Callback *callback, int copyID,
@@ -8393,13 +8608,23 @@ static bool tv_is_luafunc(typval_T *tv)
return tv->v_type == VAR_PARTIAL && is_luafunc(tv->vval.v_partial);
}
-/// check the function name after "v:lua."
-int check_luafunc_name(const char *str, bool paren)
+/// Skips one character past the end of the name of a v:lua function.
+/// @param p Pointer to the char AFTER the "v:lua." prefix.
+/// @return Pointer to the char one past the end of the function's name.
+const char *skip_luafunc_name(const char *p)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- const char *p = str;
while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.' || *p == '\'') {
p++;
}
+ return p;
+}
+
+/// check the function name after "v:lua."
+int check_luafunc_name(const char *const str, const bool paren)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ const char *const p = skip_luafunc_name(str);
if (*p != (paren ? '(' : NUL)) {
return 0;
} else {
@@ -8407,24 +8632,26 @@ int check_luafunc_name(const char *str, bool paren)
}
}
-/// Handle expr[expr], expr[expr:expr] subscript and .name lookup.
-/// Also handle function call with Funcref variable: func(expr)
-/// Can all be combined: dict.func(expr)[idx]['func'](expr)
+/// Handle:
+/// - expr[expr], expr[expr:expr] subscript
+/// - ".name" lookup
+/// - function call with Funcref variable: func(expr)
+/// - method call: var->method()
+///
+/// Can all be combined in any order: dict.func(expr)[idx]['func'](expr)->len()
int
handle_subscript(
const char **const arg,
typval_T *rettv,
- int evaluate, // do more than finding the end
- int verbose // give error messages
+ int evaluate, // do more than finding the end
+ int verbose, // give error messages
+ const char_u *const start_leader, // start of '!' and '-' prefixes
+ const char_u **const end_leaderp // end of '!' and '-' prefixes
)
{
int ret = OK;
- dict_T *selfdict = NULL;
- const char_u *s;
- int len;
- typval_T functv;
- int slen = 0;
- bool lua = false;
+ dict_T *selfdict = NULL;
+ const char_u *lua_funcname = NULL;
if (tv_is_luafunc(rettv)) {
if (**arg != '.') {
@@ -8433,55 +8660,28 @@ handle_subscript(
} else {
(*arg)++;
- lua = true;
- s = (char_u *)(*arg);
- slen = check_luafunc_name(*arg, true);
- if (slen == 0) {
+ lua_funcname = (char_u *)(*arg);
+ const int len = check_luafunc_name(*arg, true);
+ if (len == 0) {
tv_clear(rettv);
ret = FAIL;
}
- (*arg) += slen;
+ (*arg) += len;
}
}
-
while (ret == OK
- && (**arg == '['
- || (**arg == '.' && rettv->v_type == VAR_DICT)
- || (**arg == '(' && (!evaluate || tv_is_func(*rettv))))
- && !ascii_iswhite(*(*arg - 1))) {
+ && (((**arg == '[' || (**arg == '.' && rettv->v_type == VAR_DICT)
+ || (**arg == '(' && (!evaluate || tv_is_func(*rettv))))
+ && !ascii_iswhite(*(*arg - 1)))
+ || (**arg == '-' && (*arg)[1] == '>'))) {
if (**arg == '(') {
- partial_T *pt = NULL;
- // need to copy the funcref so that we can clear rettv
- if (evaluate) {
- functv = *rettv;
- rettv->v_type = VAR_UNKNOWN;
-
- // Invoke the function. Recursive!
- if (functv.v_type == VAR_PARTIAL) {
- pt = functv.vval.v_partial;
- if (!lua) {
- s = partial_name(pt);
- }
- } else {
- s = functv.vval.v_string;
- }
- } else {
- s = (char_u *)"";
- }
- ret = get_func_tv(s, lua ? slen : -1, rettv, (char_u **)arg,
- curwin->w_cursor.lnum, curwin->w_cursor.lnum,
- &len, evaluate, pt, selfdict);
-
- // Clear the funcref afterwards, so that deleting it while
- // evaluating the arguments is possible (see test55).
- if (evaluate) {
- tv_clear(&functv);
- }
+ ret = call_func_rettv((char_u **)arg, rettv, evaluate, selfdict, NULL,
+ lua_funcname);
- /* Stop the expression evaluation when immediately aborting on
- * error, or when an interrupt occurred or an exception was thrown
- * but not caught. */
+ // Stop the expression evaluation when immediately aborting on
+ // error, or when an interrupt occurred or an exception was thrown
+ // but not caught.
if (aborting()) {
if (ret == OK) {
tv_clear(rettv);
@@ -8490,6 +8690,21 @@ handle_subscript(
}
tv_dict_unref(selfdict);
selfdict = NULL;
+ } else if (**arg == '-') {
+ // Expression "-1.0->method()" applies the leader "-" before
+ // applying ->.
+ if (evaluate && *end_leaderp > start_leader) {
+ ret = eval7_leader(rettv, start_leader, end_leaderp);
+ }
+ if (ret == OK) {
+ if ((*arg)[2] == '{') {
+ // expr->{lambda}()
+ ret = eval_lambda((char_u **)arg, rettv, evaluate, verbose);
+ } else {
+ // expr->name()
+ ret = eval_method((char_u **)arg, rettv, evaluate, verbose);
+ }
+ }
} else { // **arg == '[' || **arg == '.'
tv_dict_unref(selfdict);
if (rettv->v_type == VAR_DICT) {
@@ -9274,6 +9489,7 @@ void ex_echo(exarg_T *eap)
bool atstart = true;
bool need_clear = true;
const int did_emsg_before = did_emsg;
+ const int called_emsg_before = called_emsg;
if (eap->skip)
++emsg_skip;
@@ -9288,7 +9504,8 @@ void ex_echo(exarg_T *eap)
// Report the invalid expression unless the expression evaluation
// has been cancelled due to an aborting error, an interrupt, or an
// exception.
- if (!aborting() && did_emsg == did_emsg_before) {
+ if (!aborting() && did_emsg == did_emsg_before
+ && called_emsg == called_emsg_before) {
EMSG2(_(e_invexpr2), p);
}
need_clr_eos = false;
@@ -10409,19 +10626,11 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments,
typval_T rettv = { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED };
tv_list_ref(arguments);
- int dummy;
- (void)call_func((const char_u *)func,
- name_len,
- &rettv,
- 2,
- argvars,
- NULL,
- curwin->w_cursor.lnum,
- curwin->w_cursor.lnum,
- &dummy,
- true,
- NULL,
- NULL);
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.firstline = curwin->w_cursor.lnum;
+ funcexe.lastline = curwin->w_cursor.lnum;
+ funcexe.evaluate = true;
+ (void)call_func((const char_u *)func, name_len, &rettv, 2, argvars, &funcexe);
tv_list_unref(arguments);
// Restore caller scope information
@@ -10779,7 +10988,9 @@ bool var_exists(const char *var)
n = get_var_tv(name, len, &tv, NULL, false, true) == OK;
if (n) {
// Handle d.key, l[idx], f(expr).
- n = handle_subscript(&var, &tv, true, false) == OK;
+ n = handle_subscript(&var, &tv, true, false, (const char_u *)name,
+ (const char_u **)&name)
+ == OK;
if (n) {
tv_clear(&tv);
}
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index eb20cd1bc8..faff29b268 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -5,6 +5,9 @@
-- args Number of arguments, list with maximum and minimum number of arguments
-- or list with a minimum number of arguments only. Defaults to zero
-- arguments.
+-- base For methods: the argument to use as the base argument (1-indexed):
+-- base->method()
+-- Defaults to BASE_NONE (function cannot be used as a method).
-- func Name of the C function which implements the VimL function. Defaults to
-- `f_{funcname}`.
@@ -12,111 +15,115 @@ local varargs = function(nr)
return {nr}
end
+-- Usable with the base key: use the last function argument as the method base.
+-- Value is from funcs.h file. "BASE_" prefix is omitted.
+local LAST = "BASE_LAST"
+
return {
funcs={
- abs={args=1},
- acos={args=1, func="float_op_wrapper", data="&acos"}, -- WJMc
- add={args=2},
- ['and']={args=2},
+ abs={args=1, base=1},
+ acos={args=1, base=1, func="float_op_wrapper", data="&acos"}, -- WJMc
+ add={args=2, base=1},
+ ['and']={args=2, base=1},
api_info={},
- append={args=2},
- appendbufline={args=3},
+ append={args=2, base=LAST},
+ appendbufline={args=3, base=LAST},
argc={args={0, 1}},
argidx={},
arglistid={args={0, 2}},
argv={args={0, 2}},
- asin={args=1, func="float_op_wrapper", data="&asin"}, -- WJMc
- assert_beeps={args={1}},
- assert_equal={args={2, 3}},
- assert_equalfile={args={2, 3}},
+ asin={args=1, base=1, func="float_op_wrapper", data="&asin"}, -- WJMc
+ assert_beeps={args={1}, base=1},
+ assert_equal={args={2, 3}, base=2},
+ assert_equalfile={args={2, 3}, base=1},
assert_exception={args={1, 2}},
- assert_fails={args={1, 3}},
- assert_false={args={1, 2}},
- assert_inrange={args={3, 4}},
- assert_match={args={2, 3}},
+ assert_fails={args={1, 3}, base=1},
+ assert_false={args={1, 2}, base=1},
+ assert_inrange={args={3, 4}, base=3},
+ assert_match={args={2, 3}, base=2},
assert_nobeep={args={1}},
- assert_notequal={args={2, 3}},
- assert_notmatch={args={2, 3}},
- assert_report={args=1},
- assert_true={args={1, 2}},
- atan={args=1, func="float_op_wrapper", data="&atan"},
- atan2={args=2},
+ assert_notequal={args={2, 3}, base=2},
+ assert_notmatch={args={2, 3}, base=2},
+ assert_report={args=1, base=1},
+ assert_true={args={1, 2}, base=1},
+ atan={args=1, base=1, func="float_op_wrapper", data="&atan"},
+ atan2={args=2, base=1},
browse={args=4},
browsedir={args=2},
- bufadd={args=1},
- bufexists={args=1},
- buffer_exists={args=1, func='f_bufexists'}, -- obsolete
+ bufadd={args=1, base=1},
+ bufexists={args=1, base=1},
+ buffer_exists={args=1, base=1, func='f_bufexists'}, -- obsolete
buffer_name={args={0, 1}, func='f_bufname'}, -- obsolete
buffer_number={args={0, 1}, func='f_bufnr'}, -- obsolete
- buflisted={args=1},
- bufload={args=1},
- bufloaded={args=1},
- bufname={args={0, 1}},
- bufnr={args={0, 2}},
- bufwinid={args=1},
- bufwinnr={args=1},
- byte2line={args=1},
- byteidx={args=2},
- byteidxcomp={args=2},
- call={args={2, 3}},
- ceil={args=1, func="float_op_wrapper", data="&ceil"},
+ buflisted={args=1, base=1},
+ bufload={args=1, base=1},
+ bufloaded={args=1, base=1},
+ bufname={args={0, 1}, base=1},
+ bufnr={args={0, 2}, base=1},
+ bufwinid={args=1, base=1},
+ bufwinnr={args=1, base=1},
+ byte2line={args=1, base=1},
+ byteidx={args=2, base=1},
+ byteidxcomp={args=2, base=1},
+ call={args={2, 3}, base=1},
+ ceil={args=1, base=1, func="float_op_wrapper", data="&ceil"},
changenr={},
chanclose={args={1, 2}},
chansend={args=2},
- char2nr={args={1, 2}},
+ char2nr={args={1, 2}, base=1},
charidx={args={2, 3}},
- cindent={args=1},
- clearmatches={args={0, 1}},
- col={args=1},
- complete={args=2},
- complete_add={args=1},
+ cindent={args=1, base=1},
+ clearmatches={args={0, 1}, base=1},
+ col={args=1, base=1},
+ complete={args=2, base=2},
+ complete_add={args=1, base=1},
complete_check={},
- complete_info={args={0, 1}},
- confirm={args={1, 4}},
- copy={args=1},
- cos={args=1, func="float_op_wrapper", data="&cos"},
- cosh={args=1, func="float_op_wrapper", data="&cosh"},
- count={args={2, 4}},
+ complete_info={args={0, 1}, base=1},
+ confirm={args={1, 4}, base=1},
+ copy={args=1, base=1},
+ cos={args=1, base=1, func="float_op_wrapper", data="&cos"},
+ cosh={args=1, base=1, func="float_op_wrapper", data="&cosh"},
+ count={args={2, 4}, base=1},
cscope_connection={args={0, 3}},
ctxget={args={0, 1}},
ctxpop={},
ctxpush={args={0, 1}},
ctxset={args={1, 2}},
ctxsize={},
- cursor={args={1, 3}},
- debugbreak={args={1, 1}},
- deepcopy={args={1, 2}},
- delete={args={1,2}},
- deletebufline={args={2,3}},
+ cursor={args={1, 3}, base=1},
+ debugbreak={args={1, 1}, base=1},
+ deepcopy={args={1, 2}, base=1},
+ delete={args={1,2}, base=1},
+ deletebufline={args={2,3}, base=1},
dictwatcheradd={args=3},
dictwatcherdel={args=3},
did_filetype={},
- diff_filler={args=1},
- diff_hlID={args=2},
- empty={args=1},
+ diff_filler={args=1, base=1},
+ diff_hlID={args=2, base=1},
+ empty={args=1, base=1},
environ={},
escape={args=2},
- eval={args=1},
+ eval={args=1, base=1},
eventhandler={},
executable={args=1},
execute={args={1, 2}},
exepath={args=1},
exists={args=1},
- exp={args=1, func="float_op_wrapper", data="&exp"},
+ exp={args=1, base=1, func="float_op_wrapper", data="&exp"},
expand={args={1, 3}},
expandcmd={args=1},
- extend={args={2, 3}},
+ extend={args={2, 3}, base=1},
feedkeys={args={1, 2}},
file_readable={args=1, func='f_filereadable'}, -- obsolete
filereadable={args=1},
filewritable={args=1},
- filter={args=2},
+ filter={args=2, base=1},
finddir={args={1, 3}},
findfile={args={1, 3}},
flatten={args={1, 2}},
- float2nr={args=1},
- floor={args=1, func="float_op_wrapper", data="&floor"},
- fmod={args=2},
+ float2nr={args=1, base=1},
+ floor={args=1, base=1, func="float_op_wrapper", data="&floor"},
+ fmod={args=2, base=1},
fnameescape={args=1},
fnamemodify={args=2},
foldclosed={args=1},
@@ -128,7 +135,7 @@ return {
funcref={args={1, 3}},
['function']={args={1, 3}},
garbagecollect={args={0, 1}},
- get={args={2, 3}},
+ get={args={2, 3}, base=1},
getbufinfo={args={0, 1}},
getbufline={args={2, 3}},
getbufvar={args={2, 3}},
@@ -173,7 +180,7 @@ return {
glob2regpat={args=1},
globpath={args={2, 5}},
has={args=1},
- has_key={args=2},
+ has_key={args=2, base=1},
haslocaldir={args={0,2}},
hasmapto={args={1, 3}},
highlightID={args=1, func='f_hlID'}, -- obsolete
@@ -187,22 +194,22 @@ return {
hostname={},
iconv={args=3},
indent={args=1},
- index={args={2, 4}},
+ index={args={2, 4}, base=1},
input={args={1, 3}},
inputdialog={args={1, 3}},
inputlist={args=1},
inputrestore={},
inputsave={},
inputsecret={args={1, 2}},
- insert={args={2, 3}},
+ insert={args={2, 3}, base=1},
interrupt={args=0},
- invert={args=1},
+ invert={args=1, base=1},
isdirectory={args=1},
- isinf={args=1},
+ isinf={args=1, base=1},
islocked={args=1},
- isnan={args=1},
+ isnan={args=1, base=1},
id={args=1},
- items={args=1},
+ items={args=1, base=1},
jobclose={args={1, 2}, func="f_chanclose"},
jobpid={args=1},
jobresize={args=3},
@@ -210,12 +217,12 @@ return {
jobstart={args={1, 2}},
jobstop={args=1},
jobwait={args={1, 2}},
- join={args={1, 2}},
+ join={args={1, 2}, base=1},
json_decode={args=1},
json_encode={args=1},
- keys={args=1},
+ keys={args=1, base=1},
last_buffer_nr={}, -- obsolete
- len={args=1},
+ len={args=1, base=1},
libcall={args=3},
libcallnr={args=3},
line={args={1, 2}},
@@ -223,10 +230,10 @@ return {
lispindent={args=1},
list2str={args={1, 2}},
localtime={},
- log={args=1, func="float_op_wrapper", data="&log"},
- log10={args=1, func="float_op_wrapper", data="&log10"},
+ log={args=1, base=1, func="float_op_wrapper", data="&log"},
+ log10={args=1, base=1, func="float_op_wrapper", data="&log10"},
luaeval={args={1, 2}},
- map={args=2},
+ map={args=2, base=1},
maparg={args={1, 4}},
mapcheck={args={1, 3}},
match={args={2, 4}},
@@ -238,20 +245,20 @@ return {
matchlist={args={2, 4}},
matchstr={args={2, 4}},
matchstrpos={args={2,4}},
- max={args=1},
+ max={args=1, base=1},
menu_get={args={1, 2}},
- min={args=1},
+ min={args=1, base=1},
mkdir={args={1, 3}},
mode={args={0, 1}},
msgpackdump={args=1},
msgpackparse={args=1},
nextnonblank={args=1},
nr2char={args={1, 2}},
- ['or']={args=2},
+ ['or']={args=2, base=1},
pathshorten={args=1},
- pow={args=2},
+ pow={args=2, base=1},
prevnonblank={args=1},
- printf={args=varargs(1)},
+ printf={args=varargs(1), base=2},
prompt_getprompt={args=1},
prompt_setcallback={args={2, 2}},
prompt_setinterrupt={args={2, 2}},
@@ -270,12 +277,12 @@ return {
reltime={args={0, 2}},
reltimefloat={args=1},
reltimestr={args=1},
- remove={args={2, 3}},
+ remove={args={2, 3}, base=1},
rename={args=2},
- ['repeat']={args=2},
+ ['repeat']={args=2, base=1},
resolve={args=1},
- reverse={args=1},
- round={args=1, func="float_op_wrapper", data="&round"},
+ reverse={args=1, base=1},
+ round={args=1, base=1, func="float_op_wrapper", data="&round"},
rpcnotify={args=varargs(2)},
rpcrequest={args=varargs(2)},
rpcstart={args={1, 2}},
@@ -324,19 +331,19 @@ return {
sign_unplace={args={1, 2}},
sign_unplacelist={args={1}},
simplify={args=1},
- sin={args=1, func="float_op_wrapper", data="&sin"},
- sinh={args=1, func="float_op_wrapper", data="&sinh"},
+ sin={args=1, base=1, func="float_op_wrapper", data="&sin"},
+ sinh={args=1, base=1, func="float_op_wrapper", data="&sinh"},
sockconnect={args={2,3}},
- sort={args={1, 3}},
+ sort={args={1, 3}, base=1},
soundfold={args=1},
stdioopen={args=1},
spellbadword={args={0, 1}},
spellsuggest={args={1, 3}},
- split={args={1, 3}},
- sqrt={args=1, func="float_op_wrapper", data="&sqrt"},
+ split={args={1, 3}, base=1},
+ sqrt={args=1, base=1, func="float_op_wrapper", data="&sqrt"},
stdpath={args=1},
- str2float={args=1},
- str2list={args={1, 2}},
+ str2float={args=1, base=1},
+ str2list={args={1, 2}, base=1},
str2nr={args={1, 2}},
strcharpart={args={2, 3}},
strchars={args={1,2}},
@@ -344,31 +351,31 @@ return {
strftime={args={1, 2}},
strgetchar={args={2, 2}},
stridx={args={2, 3}},
- string={args=1},
- strlen={args=1},
+ string={args=1, base=1},
+ strlen={args=1, base=1},
strpart={args={2, 4}},
strptime={args=2},
strridx={args={2, 3}},
- strtrans={args=1},
- strwidth={args=1},
+ strtrans={args=1, base=1},
+ strwidth={args=1, base=1},
submatch={args={1, 2}},
- substitute={args=4},
+ substitute={args=4, base=1},
swapinfo={args={1}},
swapname={args={1}},
synID={args=3},
- synIDattr={args={2, 3}},
- synIDtrans={args=1},
+ synIDattr={args={2, 3}, base=1},
+ synIDtrans={args=1, base=1},
synconcealed={args=2},
synstack={args=2},
- system={args={1, 2}},
- systemlist={args={1, 3}},
+ system={args={1, 2}, base=1},
+ systemlist={args={1, 3}, base=1},
tabpagebuflist={args={0, 1}},
tabpagenr={args={0, 1}},
tabpagewinnr={args={1, 2}},
tagfiles={},
taglist={args={1, 2}},
- tan={args=1, func="float_op_wrapper", data="&tan"},
- tanh={args=1, func="float_op_wrapper", data="&tanh"},
+ tan={args=1, base=1, func="float_op_wrapper", data="&tan"},
+ tanh={args=1, base=1, func="float_op_wrapper", data="&tanh"},
tempname={},
termopen={args={1, 2}},
test_garbagecollect_now={},
@@ -382,12 +389,12 @@ return {
toupper={args=1},
tr={args=3},
trim={args={1,3}},
- trunc={args=1, func="float_op_wrapper", data="&trunc"},
- type={args=1},
+ trunc={args=1, base=1, func="float_op_wrapper", data="&trunc"},
+ type={args=1, base=1},
undofile={args=1},
undotree={},
- uniq={args={1, 3}},
- values={args=1},
+ uniq={args={1, 3}, base=1},
+ values={args=1, base=1},
virtcol={args=1},
visualmode={args={0, 1}},
wait={args={2,3}},
@@ -401,7 +408,7 @@ return {
win_id2win={args=1},
win_screenpos={args=1},
win_splitmove={args={2, 3}},
- winbufnr={args=1},
+ winbufnr={args=1, base=1},
wincol={},
windowsversion={},
winheight={args=1},
@@ -414,6 +421,6 @@ return {
winwidth={args=1},
wordcount={},
writefile={args={2, 3}},
- xor={args=2},
+ xor={args=2, base=1},
},
}
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 4e409cca50..8a1258efd4 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -175,6 +175,53 @@ const VimLFuncDef *find_internal_func(const char *const name)
return find_internal_func_gperf(name, len);
}
+int call_internal_func(const char_u *const fname, const int argcount,
+ typval_T *const argvars, typval_T *const rettv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ const VimLFuncDef *const fdef = find_internal_func((const char *)fname);
+ if (fdef == NULL) {
+ return ERROR_UNKNOWN;
+ } else if (argcount < fdef->min_argc) {
+ return ERROR_TOOFEW;
+ } else if (argcount > fdef->max_argc) {
+ return ERROR_TOOMANY;
+ }
+ argvars[argcount].v_type = VAR_UNKNOWN;
+ fdef->func(argvars, rettv, fdef->data);
+ return ERROR_NONE;
+}
+
+/// Invoke a method for base->method().
+int call_internal_method(const char_u *const fname, const int argcount,
+ typval_T *const argvars, typval_T *const rettv,
+ typval_T *const basetv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ const VimLFuncDef *const fdef = find_internal_func((const char *)fname);
+ if (fdef == NULL) {
+ return ERROR_UNKNOWN;
+ } else if (fdef->base_arg == BASE_NONE) {
+ return ERROR_NOTMETHOD;
+ } else if (argcount + 1 < fdef->min_argc) {
+ return ERROR_TOOFEW;
+ } else if (argcount + 1 > fdef->max_argc) {
+ return ERROR_TOOMANY;
+ }
+
+ typval_T argv[MAX_FUNC_ARGS + 1];
+ const ptrdiff_t base_index
+ = fdef->base_arg == BASE_LAST ? argcount : fdef->base_arg - 1;
+ memcpy(argv, argvars, base_index * sizeof(typval_T));
+ argv[base_index] = *basetv;
+ memcpy(argv + base_index + 1, argvars + base_index,
+ (argcount - base_index) * sizeof(typval_T));
+ argv[argcount + 1].v_type = VAR_UNKNOWN;
+
+ fdef->func(argv, rettv, fdef->data);
+ return ERROR_NONE;
+}
+
/*
* Return TRUE for a non-zero Number and a non-empty String.
*/
@@ -9420,7 +9467,6 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero)
int res;
typval_T rettv;
typval_T argv[3];
- int dummy;
const char *func_name;
partial_T *partial = sortinfo->item_compare_partial;
@@ -9444,10 +9490,11 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero)
tv_copy(TV_LIST_ITEM_TV(si2->item), &argv[1]);
rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this
- res = call_func((const char_u *)func_name,
- -1,
- &rettv, 2, argv, NULL, 0L, 0L, &dummy, true,
- partial, sortinfo->item_compare_selfdict);
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.evaluate = true;
+ funcexe.partial = partial;
+ funcexe.selfdict = sortinfo->item_compare_selfdict;
+ res = call_func((const char_u *)func_name, -1, &rettv, 2, argv, &funcexe);
tv_clear(&argv[0]);
tv_clear(&argv[1]);
diff --git a/src/nvim/eval/funcs.h b/src/nvim/eval/funcs.h
index a343290734..c6a0cb959e 100644
--- a/src/nvim/eval/funcs.h
+++ b/src/nvim/eval/funcs.h
@@ -9,11 +9,16 @@ typedef void (*FunPtr)(void);
/// Prototype of C function that implements VimL function
typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, FunPtr data);
+/// Special flags for base_arg @see VimLFuncDef
+#define BASE_NONE 0 ///< Not a method (no base argument).
+#define BASE_LAST UINT8_MAX ///< Use the last argument as the method base.
+
/// Structure holding VimL function definition
typedef struct fst {
char *name; ///< Name of the function.
uint8_t min_argc; ///< Minimal number of arguments.
uint8_t max_argc; ///< Maximal number of arguments.
+ uint8_t base_arg; ///< Method base arg # (1-indexed), BASE_NONE or BASE_LAST.
VimLFunc func; ///< Function implementation.
FunPtr data; ///< Userdata for function implementation.
} VimLFuncDef;
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index deddec413b..4184e4d922 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -414,12 +414,7 @@ get_func_tv(
int len, // length of "name" or -1 to use strlen()
typval_T *rettv,
char_u **arg, // argument, pointing to the '('
- linenr_T firstline, // first line of range
- linenr_T lastline, // last line of range
- int *doesrange, // return: function handled range
- int evaluate,
- partial_T *partial, // for extra arguments
- dict_T *selfdict // Dictionary for "self"
+ funcexe_T *funcexe // various values
)
{
char_u *argp;
@@ -431,12 +426,13 @@ get_func_tv(
* Get the arguments.
*/
argp = *arg;
- while (argcount < MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) {
+ while (argcount < MAX_FUNC_ARGS
+ - (funcexe->partial == NULL ? 0 : funcexe->partial->pt_argc)) {
argp = skipwhite(argp + 1); // skip the '(' or ','
if (*argp == ')' || *argp == ',' || *argp == NUL) {
break;
}
- if (eval1(&argp, &argvars[argcount], evaluate) == FAIL) {
+ if (eval1(&argp, &argvars[argcount], funcexe->evaluate) == FAIL) {
ret = FAIL;
break;
}
@@ -463,9 +459,7 @@ get_func_tv(
((typval_T **)funcargs.ga_data)[funcargs.ga_len++] = &argvars[i];
}
}
- ret = call_func(name, len, rettv, argcount, argvars, NULL,
- firstline, lastline, doesrange, evaluate,
- partial, selfdict);
+ ret = call_func(name, len, rettv, argcount, argvars, funcexe);
funcargs.ga_len -= i;
} else if (!aborting()) {
@@ -1367,7 +1361,6 @@ int func_call(char_u *name, typval_T *args, partial_T *partial,
{
typval_T argv[MAX_FUNC_ARGS + 1];
int argc = 0;
- int dummy;
int r = 0;
TV_LIST_ITER(args->vval.v_list, item, {
@@ -1380,9 +1373,13 @@ int func_call(char_u *name, typval_T *args, partial_T *partial,
tv_copy(TV_LIST_ITEM_TV(item), &argv[argc++]);
});
- r = call_func(name, -1, rettv, argc, argv, NULL,
- curwin->w_cursor.lnum, curwin->w_cursor.lnum,
- &dummy, true, partial, selfdict);
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.firstline = curwin->w_cursor.lnum;
+ funcexe.lastline = curwin->w_cursor.lnum;
+ funcexe.evaluate = true;
+ funcexe.partial = partial;
+ funcexe.selfdict = selfdict;
+ r = call_func(name, -1, rettv, argc, argv, &funcexe);
func_call_skip_call:
// Free the arguments.
@@ -1402,6 +1399,9 @@ static void user_func_error(int error, const char_u *name)
case ERROR_UNKNOWN:
emsg_funcname(N_("E117: Unknown function: %s"), name);
break;
+ case ERROR_NOTMETHOD:
+ emsg_funcname(N_("E276: Cannot use function as a method: %s"), name);
+ break;
case ERROR_DELETED:
emsg_funcname(N_("E933: Function was deleted: %s"), name);
break;
@@ -1423,12 +1423,25 @@ static void user_func_error(int error, const char_u *name)
}
}
+/// Used by call_func to add a method base (if any) to a function argument list
+/// as the first argument. @see call_func
+static void argv_add_base(typval_T *const basetv, typval_T **const argvars,
+ int *const argcount, typval_T *const new_argvars,
+ int *const argv_base)
+ FUNC_ATTR_NONNULL_ARG(2, 3, 4, 5)
+{
+ if (basetv != NULL) {
+ // Method call: base->Method()
+ memmove(&new_argvars[1], *argvars, sizeof(typval_T) * (*argcount));
+ new_argvars[0] = *basetv;
+ (*argcount)++;
+ *argvars = new_argvars;
+ *argv_base = 1;
+ }
+}
+
/// Call a function with its resolved parameters
///
-/// "argv_func", when not NULL, can be used to fill in arguments only when the
-/// invoked function uses them. It is called like this:
-/// new_argcount = argv_func(current_argcount, argv, called_func_argcount)
-///
/// @return FAIL if function cannot be called, else OK (even if an error
/// occurred while executing the function! Set `msg_list` to capture
/// the error, see do_cmdline()).
@@ -1440,15 +1453,9 @@ call_func(
int argcount_in, // number of "argvars"
typval_T *argvars_in, // vars for arguments, must have "argcount"
// PLUS ONE elements!
- ArgvFunc argv_func, // function to fill in argvars
- linenr_T firstline, // first line of range
- linenr_T lastline, // last line of range
- int *doesrange, // [out] function handled range
- bool evaluate,
- partial_T *partial, // optional, can be NULL
- dict_T *selfdict_in // Dictionary for "self"
+ funcexe_T *funcexe // more arguments
)
- FUNC_ATTR_NONNULL_ARG(1, 3, 5, 9)
+ FUNC_ATTR_NONNULL_ARG(1, 3, 5, 6)
{
int ret = FAIL;
int error = ERROR_NONE;
@@ -1459,9 +1466,12 @@ call_func(
char_u *name = NULL;
int argcount = argcount_in;
typval_T *argvars = argvars_in;
- dict_T *selfdict = selfdict_in;
- typval_T argv[MAX_FUNC_ARGS + 1]; // used when "partial" is not NULL
+ dict_T *selfdict = funcexe->selfdict;
+ typval_T argv[MAX_FUNC_ARGS + 1]; // used when "partial" or
+ // "funcexe->basetv" is not NULL
int argv_clear = 0;
+ int argv_base = 0;
+ partial_T *partial = funcexe->partial;
// Initialize rettv so that it is safe for caller to invoke clear_tv(rettv)
// even when call_func() returns FAIL.
@@ -1480,14 +1490,15 @@ call_func(
fname = fname_trans_sid(name, fname_buf, &tofree, &error);
}
- *doesrange = false;
+ if (funcexe->doesrange != NULL) {
+ *funcexe->doesrange = false;
+ }
if (partial != NULL) {
// When the function has a partial with a dict and there is a dict
// argument, use the dict argument. That is backwards compatible.
// When the dict was bound explicitly use the one from the partial.
- if (partial->pt_dict != NULL
- && (selfdict_in == NULL || !partial->pt_auto)) {
+ if (partial->pt_dict != NULL && (selfdict == NULL || !partial->pt_auto)) {
selfdict = partial->pt_dict;
}
if (error == ERROR_NONE && partial->pt_argc > 0) {
@@ -1506,7 +1517,7 @@ call_func(
}
}
- if (error == ERROR_NONE && evaluate) {
+ if (error == ERROR_NONE && funcexe->evaluate) {
char_u *rfname = fname;
// Ignore "g:" before a function name.
@@ -1521,7 +1532,12 @@ call_func(
if (is_luafunc(partial)) {
if (len > 0) {
error = ERROR_NONE;
+ argv_add_base(funcexe->basetv, &argvars, &argcount, argv, &argv_base);
nlua_typval_call((const char *)funcname, len, argvars, argcount, rettv);
+ } else {
+ // v:lua was called directly; show its name in the emsg
+ XFREE_CLEAR(name);
+ funcname = (const char_u *)"v:lua";
}
} else if (fp != NULL || !builtin_function((const char *)rfname, -1)) {
// User defined function.
@@ -1549,13 +1565,16 @@ call_func(
cfunc_T cb = fp->uf_cb;
error = (*cb)(argcount, argvars, rettv, fp->uf_cb_state);
} else if (fp != NULL) {
- if (argv_func != NULL) {
+ if (funcexe->argv_func != NULL) {
// postponed filling in the arguments, do it now
- argcount = argv_func(argcount, argvars, argv_clear,
- fp->uf_args.ga_len);
+ argcount = funcexe->argv_func(argcount, argvars, argv_clear,
+ fp->uf_args.ga_len);
}
- if (fp->uf_flags & FC_RANGE) {
- *doesrange = true;
+
+ argv_add_base(funcexe->basetv, &argvars, &argcount, argv, &argv_base);
+
+ if (fp->uf_flags & FC_RANGE && funcexe->doesrange != NULL) {
+ *funcexe->doesrange = true;
}
if (argcount < fp->uf_args.ga_len - fp->uf_def_args.ga_len) {
error = ERROR_TOOFEW;
@@ -1565,25 +1584,20 @@ call_func(
error = ERROR_DICT;
} else {
// Call the user function.
- call_user_func(fp, argcount, argvars, rettv, firstline, lastline,
+ call_user_func(fp, argcount, argvars, rettv, funcexe->firstline,
+ funcexe->lastline,
(fp->uf_flags & FC_DICT) ? selfdict : NULL);
error = ERROR_NONE;
}
}
+ } else if (funcexe->basetv != NULL) {
+ // expr->method(): Find the method name in the table, call its
+ // implementation with the base as one of the arguments.
+ error = call_internal_method(fname, argcount, argvars, rettv,
+ funcexe->basetv);
} else {
// Find the function name in the table, call its implementation.
- const VimLFuncDef *const fdef = find_internal_func((const char *)fname);
- if (fdef != NULL) {
- if (argcount < fdef->min_argc) {
- error = ERROR_TOOFEW;
- } else if (argcount > fdef->max_argc) {
- error = ERROR_TOOMANY;
- } else {
- argvars[argcount].v_type = VAR_UNKNOWN;
- fdef->func(argvars, rettv, fdef->data);
- error = ERROR_NONE;
- }
- }
+ error = call_internal_func(fname, argcount, argvars, rettv);
}
/*
* The function call (or "FuncUndefined" autocommand sequence) might
@@ -1607,9 +1621,11 @@ theend:
user_func_error(error, (name != NULL) ? name : funcname);
}
+ // clear the copies made from the partial
while (argv_clear > 0) {
- tv_clear(&argv[--argv_clear]);
+ tv_clear(&argv[--argv_clear + argv_base]);
}
+
xfree(tofree);
xfree(name);
@@ -2901,7 +2917,7 @@ void ex_call(exarg_T *eap)
int len;
typval_T rettv;
linenr_T lnum;
- int doesrange;
+ bool doesrange;
bool failed = false;
funcdict_T fudi;
partial_T *partial = NULL;
@@ -2947,7 +2963,7 @@ void ex_call(exarg_T *eap)
rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this.
if (*startarg != '(') {
- EMSG2(_("E107: Missing parentheses: %s"), eap->arg);
+ EMSG2(_(e_missingparen), eap->arg);
goto end;
}
@@ -2965,15 +2981,22 @@ void ex_call(exarg_T *eap)
curwin->w_cursor.coladd = 0;
}
arg = startarg;
- if (get_func_tv(name, -1, &rettv, &arg,
- eap->line1, eap->line2, &doesrange,
- true, partial, fudi.fd_dict) == FAIL) {
+
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.firstline = eap->line1;
+ funcexe.lastline = eap->line2;
+ funcexe.doesrange = &doesrange;
+ funcexe.evaluate = true;
+ funcexe.partial = partial;
+ funcexe.selfdict = fudi.fd_dict;
+ if (get_func_tv(name, -1, &rettv, &arg, &funcexe) == FAIL) {
failed = true;
break;
}
// Handle a function returning a Funcref, Dictionary or List.
- if (handle_subscript((const char **)&arg, &rettv, true, true)
+ if (handle_subscript((const char **)&arg, &rettv, true, true,
+ (const char_u *)name, (const char_u **)&name)
== FAIL) {
failed = true;
break;
diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h
index e8ad0bf1da..3f111343d2 100644
--- a/src/nvim/eval/userfunc.h
+++ b/src/nvim/eval/userfunc.h
@@ -28,11 +28,37 @@ typedef enum {
ERROR_OTHER,
ERROR_BOTH,
ERROR_DELETED,
+ ERROR_NOTMETHOD,
} FnameTransError;
+/// Used in funcexe_T. Returns the new argcount.
typedef int (*ArgvFunc)(int current_argcount, typval_T *argv, int argskip,
int called_func_argcount);
+/// Structure passed between functions dealing with function call execution.
+typedef struct {
+ ArgvFunc argv_func; ///< when not NULL, can be used to fill in arguments only
+ ///< when the invoked function uses them
+ linenr_T firstline; ///< first line of range
+ linenr_T lastline; ///< last line of range
+ bool *doesrange; ///< [out] if not NULL: function handled range
+ bool evaluate; ///< actually evaluate expressions
+ partial_T *partial; ///< for extra arguments
+ dict_T *selfdict; ///< Dictionary for "self"
+ typval_T *basetv; ///< base for base->method()
+} funcexe_T;
+
+#define FUNCEXE_INIT (funcexe_T) { \
+ .argv_func = NULL, \
+ .firstline = 0, \
+ .lastline = 0, \
+ .doesrange = NULL, \
+ .evaluate = false, \
+ .partial = NULL, \
+ .selfdict = NULL, \
+ .basetv = NULL, \
+}
+
#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j]
#define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j]
diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua
index 679895421a..945fa5099f 100644
--- a/src/nvim/generators/gen_eval.lua
+++ b/src/nvim/generators/gen_eval.lua
@@ -42,7 +42,7 @@ gperfpipe:write([[
%language=ANSI-C
%global-table
%readonly-tables
-%define initializer-suffix ,0,0,NULL,NULL
+%define initializer-suffix ,0,0,BASE_NONE,NULL,NULL
%define word-array-name functions
%define hash-function-name hash_internal_func_gperf
%define lookup-function-name find_internal_func_gperf
@@ -59,9 +59,10 @@ for name, def in pairs(funcs) do
elseif #args == 1 then
args[2] = 'MAX_FUNC_ARGS'
end
+ local base = def.base or "BASE_NONE"
local func = def.func or ('f_' .. name)
local data = def.data or "NULL"
- gperfpipe:write(('%s, %s, %s, &%s, (FunPtr)%s\n')
- :format(name, args[1], args[2], func, data))
+ gperfpipe:write(('%s, %s, %s, %s, &%s, (FunPtr)%s\n')
+ :format(name, args[1], args[2], base, func, data))
end
gperfpipe:close()
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 96acca4ac7..2a72dbcd09 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -972,6 +972,7 @@ EXTERN char_u e_write[] INIT(= N_("E80: Error while writing"));
EXTERN char_u e_zerocount[] INIT(= N_("E939: Positive count required"));
EXTERN char_u e_usingsid[] INIT(= N_(
"E81: Using <SID> not in a script context"));
+EXTERN char_u e_missingparen[] INIT(= N_("E107: Missing parentheses: %s"));
EXTERN char_u e_maxmempat[] INIT(= N_(
"E363: pattern uses more memory than 'maxmempattern'"));
EXTERN char_u e_emptybuf[] INIT(= N_("E749: empty buffer"));
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index cbc2273bc9..b00c4282be 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -785,13 +785,13 @@ int nlua_call(lua_State *lstate)
try_start();
typval_T rettv;
- int dummy;
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.firstline = curwin->w_cursor.lnum;
+ funcexe.lastline = curwin->w_cursor.lnum;
+ funcexe.evaluate = true;
// call_func() retval is deceptive, ignore it. Instead we set `msg_list`
// (TRY_WRAP) to capture abort-causing non-exception errors.
- (void)call_func(name, (int)name_len, &rettv, nargs,
- vim_args, NULL,
- curwin->w_cursor.lnum, curwin->w_cursor.lnum,
- &dummy, true, NULL, NULL);
+ (void)call_func(name, (int)name_len, &rettv, nargs, vim_args, &funcexe);
if (!try_end(&err)) {
nlua_push_typval(lstate, &rettv, false);
}
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 7b2f77a6f9..ffcf659d28 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -2531,12 +2531,12 @@ do_mouse (
}
};
typval_T rettv;
- int doesrange;
- (void)call_func((char_u *)tab_page_click_defs[mouse_col].func,
- -1,
- &rettv, ARRAY_SIZE(argv), argv, NULL,
- curwin->w_cursor.lnum, curwin->w_cursor.lnum,
- &doesrange, true, NULL, NULL);
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.firstline = curwin->w_cursor.lnum;
+ funcexe.lastline = curwin->w_cursor.lnum;
+ funcexe.evaluate = true;
+ (void)call_func((char_u *)tab_page_click_defs[mouse_col].func, -1,
+ &rettv, ARRAY_SIZE(argv), argv, &funcexe);
tv_clear(&rettv);
break;
}
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index 6379174938..98a46cf781 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -6726,26 +6726,24 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
if (expr != NULL) {
typval_T argv[2];
- int dummy;
typval_T rettv;
staticList10_T matchList = TV_LIST_STATIC10_INIT;
-
rettv.v_type = VAR_STRING;
rettv.vval.v_string = NULL;
argv[0].v_type = VAR_LIST;
argv[0].vval.v_list = &matchList.sl_list;
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.argv_func = fill_submatch_list;
+ funcexe.evaluate = true;
if (expr->v_type == VAR_FUNC) {
s = expr->vval.v_string;
- call_func(s, -1, &rettv, 1, argv,
- fill_submatch_list, 0L, 0L, &dummy,
- true, NULL, NULL);
+ call_func(s, -1, &rettv, 1, argv, &funcexe);
} else if (expr->v_type == VAR_PARTIAL) {
partial_T *partial = expr->vval.v_partial;
s = partial_name(partial);
- call_func(s, -1, &rettv, 1, argv,
- fill_submatch_list, 0L, 0L, &dummy,
- true, partial, NULL);
+ funcexe.partial = partial;
+ call_func(s, -1, &rettv, 1, argv, &funcexe);
}
if (tv_list_len(&matchList.sl_list) > 0) {
// fill_submatch_list() was called.
diff --git a/src/nvim/testdir/sautest/autoload/foo.vim b/src/nvim/testdir/sautest/autoload/foo.vim
index d7dcd5ce3d..298e7275d8 100644
--- a/src/nvim/testdir/sautest/autoload/foo.vim
+++ b/src/nvim/testdir/sautest/autoload/foo.vim
@@ -5,3 +5,7 @@ let foo#bar = {}
func foo#bar.echo()
let g:called_foo_bar_echo += 1
endfunc
+
+func foo#addFoo(head)
+ return a:head .. 'foo'
+endfunc
diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim
index a1ef8325ec..01d8f32893 100644
--- a/src/nvim/testdir/test_arglist.vim
+++ b/src/nvim/testdir/test_arglist.vim
@@ -90,8 +90,8 @@ func Test_argadd_empty_curbuf()
call assert_equal('', bufname('%'))
call assert_equal(1, line('$'))
rew
- call assert_notequal(curbuf, bufnr('%'))
- call assert_equal('Xargadd', bufname('%'))
+ call assert_notequal(curbuf, '%'->bufnr())
+ call assert_equal('Xargadd', '%'->bufname())
call assert_equal(2, line('$'))
%argd
diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim
index 1d114221dc..52f243aaea 100644
--- a/src/nvim/testdir/test_assert.vim
+++ b/src/nvim/testdir/test_assert.vim
@@ -7,7 +7,7 @@ func Test_assert_equalfile()
let goodtext = ["one", "two", "three"]
call writefile(goodtext, 'Xone')
- call assert_equal(1, assert_equalfile('Xone', 'xyzxyz'))
+ call assert_equal(1, 'Xone'->assert_equalfile('xyzxyz'))
call assert_match("E485: Can't read file xyzxyz", v:errors[0])
call remove(v:errors, 0)
diff --git a/src/nvim/testdir/test_autoload.vim b/src/nvim/testdir/test_autoload.vim
index 7396c227c9..b8c4fa251f 100644
--- a/src/nvim/testdir/test_autoload.vim
+++ b/src/nvim/testdir/test_autoload.vim
@@ -8,6 +8,8 @@ func Test_autoload_dict_func()
call g:foo#bar.echo()
call assert_equal(1, g:loaded_foo_vim)
call assert_equal(1, g:called_foo_bar_echo)
+
+ eval 'bar'->g:foo#addFoo()->assert_equal('barfoo')
endfunc
func Test_source_autoload()
diff --git a/src/nvim/testdir/test_bufline.vim b/src/nvim/testdir/test_bufline.vim
index e038bce08e..b4e8a0bc71 100644
--- a/src/nvim/testdir/test_bufline.vim
+++ b/src/nvim/testdir/test_bufline.vim
@@ -102,7 +102,7 @@ func Test_deletebufline()
call assert_equal(0, deletebufline(b, 2, 8))
call assert_equal(['aaa'], getbufline(b, 1, 2))
exe "bd!" b
- call assert_equal(1, deletebufline(b, 1))
+ call assert_equal(1, b->deletebufline(1))
split Xtest
call setline(1, ['a', 'b', 'c'])
@@ -131,11 +131,11 @@ func Test_appendbufline_redraw()
endif
let lines =<< trim END
new foo
- let winnr=bufwinnr('foo')
- let buf=bufnr('foo')
+ let winnr = 'foo'->bufwinnr()
+ let buf = bufnr('foo')
wincmd p
call appendbufline(buf, '$', range(1,200))
- exe winnr. 'wincmd w'
+ exe winnr .. 'wincmd w'
norm! G
wincmd p
call deletebufline(buf, 1, '$')
diff --git a/src/nvim/testdir/test_bufwintabinfo.vim b/src/nvim/testdir/test_bufwintabinfo.vim
index cb7ab44798..4b5b55e6bf 100644
--- a/src/nvim/testdir/test_bufwintabinfo.vim
+++ b/src/nvim/testdir/test_bufwintabinfo.vim
@@ -18,7 +18,7 @@ function Test_getbufwintabinfo()
let l = getbufinfo('%')
call assert_equal(bufnr('%'), l[0].bufnr)
call assert_equal('vim', l[0].variables.editor)
- call assert_notequal(-1, index(l[0].windows, bufwinid('%')))
+ call assert_notequal(-1, index(l[0].windows, '%'->bufwinid()))
" Test for getbufinfo() with 'bufmodified'
call assert_equal(0, len(getbufinfo({'bufmodified' : 1})))
diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim
index b6c2d1467e..562867f548 100644
--- a/src/nvim/testdir/test_cindent.vim
+++ b/src/nvim/testdir/test_cindent.vim
@@ -118,6 +118,16 @@ b = something();
bw!
endfunc
+func Test_cindent_func()
+ new
+ setlocal cindent
+ call setline(1, ['int main(void)', '{', 'return 0;', '}'])
+ call assert_equal(-1, cindent(0))
+ call assert_equal(&sw, 3->cindent())
+ call assert_equal(-1, cindent(line('$')+1))
+ bwipe!
+endfunc
+
" this was going beyond the end of the line.
func Test_cindent_case()
new
diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim
index f39cda7663..efa7f552e0 100644
--- a/src/nvim/testdir/test_diffmode.vim
+++ b/src/nvim/testdir/test_diffmode.vim
@@ -725,7 +725,7 @@ func Test_diff_filler()
diffthis
redraw
- call assert_equal([0, 0, 0, 0, 0, 0, 0, 1, 0], map(range(-1, 7), 'diff_filler(v:val)'))
+ call assert_equal([0, 0, 0, 0, 0, 0, 0, 1, 0], map(range(-1, 7), 'v:val->diff_filler()'))
wincmd w
call assert_equal([0, 0, 0, 0, 2, 0, 0, 0], map(range(-1, 6), 'diff_filler(v:val)'))
@@ -741,16 +741,16 @@ func Test_diff_hlID()
diffthis
redraw
- call assert_equal(synIDattr(diff_hlID(-1, 1), "name"), "")
+ call diff_hlID(-1, 1)->synIDattr("name")->assert_equal("")
call assert_equal(diff_hlID(1, 1), hlID("DiffChange"))
- call assert_equal(synIDattr(diff_hlID(1, 1), "name"), "DiffChange")
+ call diff_hlID(1, 1)->synIDattr("name")->assert_equal("DiffChange")
call assert_equal(diff_hlID(1, 2), hlID("DiffText"))
- call assert_equal(synIDattr(diff_hlID(1, 2), "name"), "DiffText")
- call assert_equal(synIDattr(diff_hlID(2, 1), "name"), "")
+ call diff_hlID(1, 2)->synIDattr("name")->assert_equal("DiffText")
+ call diff_hlID(2, 1)->synIDattr("name")->assert_equal("")
call assert_equal(diff_hlID(3, 1), hlID("DiffAdd"))
- call assert_equal(synIDattr(diff_hlID(3, 1), "name"), "DiffAdd")
- call assert_equal(synIDattr(diff_hlID(4, 1), "name"), "")
+ call diff_hlID(3, 1)->synIDattr("name")->assert_equal("DiffAdd")
+ call diff_hlID(4, 1)->synIDattr("name")->assert_equal("")
wincmd w
call assert_equal(diff_hlID(1, 1), hlID("DiffChange"))
diff --git a/src/nvim/testdir/test_float_func.vim b/src/nvim/testdir/test_float_func.vim
index 154ef570e0..78675d7016 100644
--- a/src/nvim/testdir/test_float_func.vim
+++ b/src/nvim/testdir/test_float_func.vim
@@ -7,6 +7,8 @@ end
func Test_abs()
call assert_equal('1.23', string(abs(1.23)))
call assert_equal('1.23', string(abs(-1.23)))
+ eval -1.23->abs()->string()->assert_equal('1.23')
+
call assert_equal('0.0', string(abs(0.0)))
call assert_equal('0.0', string(abs(1.0/(1.0/0.0))))
call assert_equal('0.0', string(abs(-1.0/(1.0/0.0))))
@@ -22,6 +24,7 @@ endfunc
func Test_sqrt()
call assert_equal('0.0', string(sqrt(0.0)))
call assert_equal('1.414214', string(sqrt(2.0)))
+ eval 2.0->sqrt()->string()->assert_equal('1.414214')
call assert_equal("str2float('inf')", string(sqrt(1.0/0.0)))
call assert_equal("str2float('nan')", string(sqrt(-1.0)))
call assert_equal("str2float('nan')", string(sqrt(0.0/0.0)))
@@ -31,6 +34,7 @@ endfunc
func Test_log()
call assert_equal('0.0', string(log(1.0)))
call assert_equal('-0.693147', string(log(0.5)))
+ eval 0.5->log()->string()->assert_equal('-0.693147')
call assert_equal("-str2float('inf')", string(log(0.0)))
call assert_equal("str2float('nan')", string(log(-1.0)))
call assert_equal("str2float('inf')", string(log(1.0/0.0)))
@@ -42,6 +46,7 @@ func Test_log10()
call assert_equal('0.0', string(log10(1.0)))
call assert_equal('2.0', string(log10(100.0)))
call assert_equal('2.079181', string(log10(120.0)))
+ eval 120.0->log10()->string()->assert_equal('2.079181')
call assert_equal("-str2float('inf')", string(log10(0.0)))
call assert_equal("str2float('nan')", string(log10(-1.0)))
call assert_equal("str2float('inf')", string(log10(1.0/0.0)))
@@ -53,6 +58,7 @@ func Test_exp()
call assert_equal('1.0', string(exp(0.0)))
call assert_equal('7.389056', string(exp(2.0)))
call assert_equal('0.367879', string(exp(-1.0)))
+ eval -1.0->exp()->string()->assert_equal('0.367879')
call assert_equal("str2float('inf')", string(exp(1.0/0.0)))
call assert_equal('0.0', string(exp(-1.0/0.0)))
call assert_equal("str2float('nan')", string(exp(0.0/0.0)))
@@ -63,6 +69,7 @@ func Test_sin()
call assert_equal('0.0', string(sin(0.0)))
call assert_equal('0.841471', string(sin(1.0)))
call assert_equal('-0.479426', string(sin(-0.5)))
+ eval -0.5->sin()->string()->assert_equal('-0.479426')
call assert_equal("str2float('nan')", string(sin(0.0/0.0)))
call assert_equal("str2float('nan')", string(sin(1.0/0.0)))
call assert_equal('0.0', string(sin(1.0/(1.0/0.0))))
@@ -73,6 +80,8 @@ endfunc
func Test_asin()
call assert_equal('0.0', string(asin(0.0)))
call assert_equal('1.570796', string(asin(1.0)))
+ eval 1.0->asin()->string()->assert_equal('1.570796')
+
call assert_equal('-0.523599', string(asin(-0.5)))
call assert_equal("str2float('nan')", string(asin(1.1)))
call assert_equal("str2float('nan')", string(asin(1.0/0.0)))
@@ -84,6 +93,7 @@ func Test_sinh()
call assert_equal('0.0', string(sinh(0.0)))
call assert_equal('0.521095', string(sinh(0.5)))
call assert_equal('-1.026517', string(sinh(-0.9)))
+ eval -0.9->sinh()->string()->assert_equal('-1.026517')
call assert_equal("str2float('inf')", string(sinh(1.0/0.0)))
call assert_equal("-str2float('inf')", string(sinh(-1.0/0.0)))
call assert_equal("str2float('nan')", string(sinh(0.0/0.0)))
@@ -94,6 +104,7 @@ func Test_cos()
call assert_equal('1.0', string(cos(0.0)))
call assert_equal('0.540302', string(cos(1.0)))
call assert_equal('0.877583', string(cos(-0.5)))
+ eval -0.5->cos()->string()->assert_equal('0.877583')
call assert_equal("str2float('nan')", string(cos(0.0/0.0)))
call assert_equal("str2float('nan')", string(cos(1.0/0.0)))
call assert_fails('call cos("")', 'E808:')
@@ -103,6 +114,7 @@ func Test_acos()
call assert_equal('1.570796', string(acos(0.0)))
call assert_equal('0.0', string(acos(1.0)))
call assert_equal('3.141593', string(acos(-1.0)))
+ eval -1.0->acos()->string()->assert_equal('3.141593')
call assert_equal('2.094395', string(acos(-0.5)))
call assert_equal("str2float('nan')", string(acos(1.1)))
call assert_equal("str2float('nan')", string(acos(1.0/0.0)))
@@ -113,6 +125,7 @@ endfunc
func Test_cosh()
call assert_equal('1.0', string(cosh(0.0)))
call assert_equal('1.127626', string(cosh(0.5)))
+ eval 0.5->cosh()->string()->assert_equal('1.127626')
call assert_equal("str2float('inf')", string(cosh(1.0/0.0)))
call assert_equal("str2float('inf')", string(cosh(-1.0/0.0)))
call assert_equal("str2float('nan')", string(cosh(0.0/0.0)))
@@ -123,6 +136,7 @@ func Test_tan()
call assert_equal('0.0', string(tan(0.0)))
call assert_equal('0.546302', string(tan(0.5)))
call assert_equal('-0.546302', string(tan(-0.5)))
+ eval -0.5->tan()->string()->assert_equal('-0.546302')
call assert_equal("str2float('nan')", string(tan(1.0/0.0)))
call assert_equal("str2float('nan')", string(cos(0.0/0.0)))
call assert_equal('0.0', string(tan(1.0/(1.0/0.0))))
@@ -134,6 +148,7 @@ func Test_atan()
call assert_equal('0.0', string(atan(0.0)))
call assert_equal('0.463648', string(atan(0.5)))
call assert_equal('-0.785398', string(atan(-1.0)))
+ eval -1.0->atan()->string()->assert_equal('-0.785398')
call assert_equal('1.570796', string(atan(1.0/0.0)))
call assert_equal('-1.570796', string(atan(-1.0/0.0)))
call assert_equal("str2float('nan')", string(atan(0.0/0.0)))
@@ -144,6 +159,7 @@ func Test_atan2()
call assert_equal('-2.356194', string(atan2(-1, -1)))
call assert_equal('2.356194', string(atan2(1, -1)))
call assert_equal('0.0', string(atan2(1.0, 1.0/0.0)))
+ eval 1.0->atan2(1.0/0.0)->string()->assert_equal('0.0')
call assert_equal('1.570796', string(atan2(1.0/0.0, 1.0)))
call assert_equal("str2float('nan')", string(atan2(0.0/0.0, 1.0)))
call assert_fails('call atan2("", -1)', 'E808:')
@@ -154,6 +170,7 @@ func Test_tanh()
call assert_equal('0.0', string(tanh(0.0)))
call assert_equal('0.462117', string(tanh(0.5)))
call assert_equal('-0.761594', string(tanh(-1.0)))
+ eval -1.0->tanh()->string()->assert_equal('-0.761594')
call assert_equal('1.0', string(tanh(1.0/0.0)))
call assert_equal('-1.0', string(tanh(-1.0/0.0)))
call assert_equal("str2float('nan')", string(tanh(0.0/0.0)))
@@ -164,6 +181,7 @@ func Test_fmod()
call assert_equal('0.13', string(fmod(12.33, 1.22)))
call assert_equal('-0.13', string(fmod(-12.33, 1.22)))
call assert_equal("str2float('nan')", string(fmod(1.0/0.0, 1.0)))
+ eval (1.0/0.0)->fmod(1.0)->string()->assert_equal("str2float('nan')")
" On Windows we get "nan" instead of 1.0, accept both.
let res = string(fmod(1.0, 1.0/0.0))
if res != "str2float('nan')"
@@ -177,6 +195,7 @@ endfunc
func Test_pow()
call assert_equal('1.0', string(pow(0.0, 0.0)))
call assert_equal('8.0', string(pow(2.0, 3.0)))
+ eval 2.0->pow(3.0)->string()->assert_equal('8.0')
call assert_equal("str2float('nan')", string(pow(2.0, 0.0/0.0)))
call assert_equal("str2float('nan')", string(pow(0.0/0.0, 3.0)))
call assert_equal("str2float('nan')", string(pow(0.0/0.0, 3.0)))
@@ -192,6 +211,7 @@ func Test_str2float()
call assert_equal('1.0', string(str2float(' 1.0 ')))
call assert_equal('1.23', string(str2float('1.23')))
call assert_equal('1.23', string(str2float('1.23abc')))
+ eval '1.23abc'->str2float()->string()->assert_equal('1.23')
call assert_equal('1.0e40', string(str2float('1e40')))
call assert_equal('-1.23', string(str2float('-1.23')))
call assert_equal('1.23', string(str2float(' + 1.23 ')))
@@ -228,6 +248,7 @@ func Test_float2nr()
call assert_equal(1, float2nr(1.234))
call assert_equal(123, float2nr(1.234e2))
call assert_equal(12, float2nr(123.4e-1))
+ eval 123.4e-1->float2nr()->assert_equal(12)
let max_number = 1/0
let min_number = -max_number
call assert_equal(max_number/2+1, float2nr(pow(2, 62)))
@@ -242,6 +263,7 @@ func Test_floor()
call assert_equal('2.0', string(floor(2.0)))
call assert_equal('2.0', string(floor(2.11)))
call assert_equal('2.0', string(floor(2.99)))
+ eval 2.99->floor()->string()->assert_equal('2.0')
call assert_equal('-3.0', string(floor(-2.11)))
call assert_equal('-3.0', string(floor(-2.99)))
call assert_equal("str2float('nan')", string(floor(0.0/0.0)))
@@ -255,6 +277,7 @@ func Test_ceil()
call assert_equal('3.0', string(ceil(2.11)))
call assert_equal('3.0', string(ceil(2.99)))
call assert_equal('-2.0', string(ceil(-2.11)))
+ eval -2.11->ceil()->string()->assert_equal('-2.0')
call assert_equal('-2.0', string(ceil(-2.99)))
call assert_equal("str2float('nan')", string(ceil(0.0/0.0)))
call assert_equal("str2float('inf')", string(ceil(1.0/0.0)))
@@ -266,6 +289,7 @@ func Test_round()
call assert_equal('2.0', string(round(2.1)))
call assert_equal('3.0', string(round(2.5)))
call assert_equal('3.0', string(round(2.9)))
+ eval 2.9->round()->string()->assert_equal('3.0')
call assert_equal('-2.0', string(round(-2.1)))
call assert_equal('-3.0', string(round(-2.5)))
call assert_equal('-3.0', string(round(-2.9)))
@@ -279,6 +303,7 @@ func Test_trunc()
call assert_equal('2.0', string(trunc(2.1)))
call assert_equal('2.0', string(trunc(2.5)))
call assert_equal('2.0', string(trunc(2.9)))
+ eval 2.9->trunc()->string()->assert_equal('2.0')
call assert_equal('-2.0', string(trunc(-2.1)))
call assert_equal('-2.0', string(trunc(-2.5)))
call assert_equal('-2.0', string(trunc(-2.9)))
@@ -291,6 +316,7 @@ endfunc
func Test_isinf()
call assert_equal(1, isinf(1.0/0.0))
call assert_equal(-1, isinf(-1.0/0.0))
+ eval (-1.0/0.0)->isinf()->assert_equal(-1)
call assert_false(isinf(1.0))
call assert_false(isinf(0.0/0.0))
call assert_false(isinf('a'))
@@ -302,6 +328,7 @@ func Test_isnan()
call assert_true(isnan(0.0/0.0))
call assert_false(isnan(1.0))
call assert_false(isnan(1.0/0.0))
+ eval (1.0/0.0)->isnan()->assert_false()
call assert_false(isnan(-1.0/0.0))
call assert_false(isnan('a'))
call assert_false(isnan([]))
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index 48f97be96b..6cb3e24201 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -852,7 +852,7 @@ func Test_byte2line_line2byte()
set fileformat=mac
call assert_equal([-1, -1, 1, 1, 2, 2, 2, 3, 3, -1],
- \ map(range(-1, 8), 'byte2line(v:val)'))
+ \ map(range(-1, 8), 'v:val->byte2line()'))
call assert_equal([-1, -1, 1, 3, 6, 8, -1],
\ map(range(-1, 5), 'line2byte(v:val)'))
@@ -875,6 +875,34 @@ func Test_byte2line_line2byte()
bw!
endfunc
+func Test_byteidx()
+ let a = '.é.' " one char of two bytes
+ call assert_equal(0, byteidx(a, 0))
+ call assert_equal(0, byteidxcomp(a, 0))
+ call assert_equal(1, byteidx(a, 1))
+ call assert_equal(1, byteidxcomp(a, 1))
+ call assert_equal(3, byteidx(a, 2))
+ call assert_equal(3, byteidxcomp(a, 2))
+ call assert_equal(4, byteidx(a, 3))
+ call assert_equal(4, byteidxcomp(a, 3))
+ call assert_equal(-1, byteidx(a, 4))
+ call assert_equal(-1, byteidxcomp(a, 4))
+
+ let b = '.é.' " normal e with composing char
+ call assert_equal(0, b->byteidx(0))
+ call assert_equal(1, b->byteidx(1))
+ call assert_equal(4, b->byteidx(2))
+ call assert_equal(5, b->byteidx(3))
+ call assert_equal(-1, b->byteidx(4))
+
+ call assert_equal(0, b->byteidxcomp(0))
+ call assert_equal(1, b->byteidxcomp(1))
+ call assert_equal(2, b->byteidxcomp(2))
+ call assert_equal(4, b->byteidxcomp(3))
+ call assert_equal(5, b->byteidxcomp(4))
+ call assert_equal(-1, b->byteidxcomp(5))
+endfunc
+
" Test for charidx()
func Test_charidx()
let a = 'xáb́y'
@@ -1065,7 +1093,7 @@ func Test_col()
call assert_equal(7, col('$'))
call assert_equal(4, col("'x"))
call assert_equal(6, col("'Y"))
- call assert_equal(2, col([1, 2]))
+ call assert_equal(2, [1, 2]->col())
call assert_equal(7, col([1, '$']))
call assert_equal(0, col(''))
@@ -1413,13 +1441,13 @@ func Test_bufadd_bufload()
call assert_equal([''], getbufline(buf, 1, '$'))
let curbuf = bufnr('')
- call writefile(['some', 'text'], 'otherName')
- let buf = bufadd('otherName')
+ call writefile(['some', 'text'], 'XotherName')
+ let buf = 'XotherName'->bufadd()
call assert_notequal(0, buf)
- call assert_equal(1, bufexists('otherName'))
+ eval 'XotherName'->bufexists()->assert_equal(1)
call assert_equal(0, getbufvar(buf, '&buflisted'))
call assert_equal(0, bufloaded(buf))
- call bufload(buf)
+ eval buf->bufload()
call assert_equal(1, bufloaded(buf))
call assert_equal(['some', 'text'], getbufline(buf, 1, '$'))
call assert_equal(curbuf, bufnr(''))
@@ -1439,8 +1467,9 @@ func Test_bufadd_bufload()
call assert_equal(0, bufexists(buf2))
bwipe someName
- bwipe otherName
+ bwipe XotherName
call assert_equal(0, bufexists('someName'))
+ call delete('XotherName')
endfunc
func Test_readdir()
@@ -1473,6 +1502,20 @@ func Test_readdir()
call delete('Xdir', 'rf')
endfunc
+func Test_call()
+ call assert_equal(3, call('len', [123]))
+ call assert_equal(3, 'len'->call([123]))
+ call assert_fails("call call('len', 123)", 'E714:')
+ call assert_equal(0, call('', []))
+
+ function Mylen() dict
+ return len(self.data)
+ endfunction
+ let mydict = {'data': [0, 1, 2, 3], 'len': function("Mylen")}
+ eval mydict.len->call([], mydict)->assert_equal(4)
+ call assert_fails("call call('Mylen', [], 0)", 'E715:')
+endfunc
+
" Test for the eval() function
func Test_eval()
call assert_fails("call eval('5 a')", 'E488:')
diff --git a/src/nvim/testdir/test_hide.vim b/src/nvim/testdir/test_hide.vim
index 128b8ff945..41b1a4ad7c 100644
--- a/src/nvim/testdir/test_hide.vim
+++ b/src/nvim/testdir/test_hide.vim
@@ -37,7 +37,7 @@ function Test_hide()
" :hide as a command
hide
call assert_equal([orig_bname, orig_winnr], [bufname(''), winnr('$')])
- call assert_equal([1, 1], [buflisted('Xf1'), bufloaded('Xf1')])
+ call assert_equal([1, 1], ['Xf1'->buflisted(), 'Xf1'->bufloaded()])
bwipeout! Xf1
new Xf1
diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim
index f026c8a55f..63bb4ae1ef 100644
--- a/src/nvim/testdir/test_lambda.vim
+++ b/src/nvim/testdir/test_lambda.vim
@@ -61,7 +61,7 @@ endfunction
function Test_lambda_fails()
call assert_equal(3, {a, b -> a + b}(1, 2))
- call assert_fails('echo {a, a -> a + a}(1, 2)', 'E15:')
+ call assert_fails('echo {a, a -> a + a}(1, 2)', 'E853:')
call assert_fails('echo {a, b -> a + b)}(1, 2)', 'E15:')
endfunc
diff --git a/src/nvim/testdir/test_match.vim b/src/nvim/testdir/test_match.vim
index 0fd76a23ea..505a052a19 100644
--- a/src/nvim/testdir/test_match.vim
+++ b/src/nvim/testdir/test_match.vim
@@ -242,7 +242,7 @@ func Test_matchaddpos_otherwin()
\]
call assert_equal(expect, savematches)
- call clearmatches(winid)
+ eval winid->clearmatches()
call assert_equal([], getmatches(winid))
call setmatches(savematches, winid)
diff --git a/src/nvim/testdir/test_method.vim b/src/nvim/testdir/test_method.vim
new file mode 100644
index 0000000000..7a6e6aa19d
--- /dev/null
+++ b/src/nvim/testdir/test_method.vim
@@ -0,0 +1,159 @@
+" Tests for ->method()
+
+func Test_list_method()
+ let l = [1, 2, 3]
+ call assert_equal([1, 2, 3, 4], [1, 2, 3]->add(4))
+ eval l->assert_equal(l)
+ eval l->assert_equal(l, 'wrong')
+ eval l->assert_notequal([3, 2, 1])
+ eval l->assert_notequal([3, 2, 1], 'wrong')
+ call assert_equal(l, l->copy())
+ call assert_equal(l, l->deepcopy())
+ call assert_equal(1, l->count(2))
+ call assert_false(l->empty())
+ call assert_true([]->empty())
+ call assert_equal(579, ['123', '+', '456']->join()->eval())
+ call assert_equal([1, 2, 3, 4, 5], [1, 2, 3]->extend([4, 5]))
+ call assert_equal([1, 3], [1, 2, 3]->filter('v:val != 2'))
+ call assert_equal(2, l->get(1))
+ call assert_equal(1, l->index(2))
+ call assert_equal([0, 1, 2, 3], [1, 2, 3]->insert(0))
+ call assert_fails('eval l->items()', 'E715:')
+ call assert_equal('1 2 3', l->join())
+ call assert_fails('eval l->keys()', 'E715:')
+ call assert_equal(3, l->len())
+ call assert_equal([2, 3, 4], [1, 2, 3]->map('v:val + 1'))
+ call assert_equal(3, l->max())
+ call assert_equal(1, l->min())
+ call assert_equal(2, [1, 2, 3]->remove(1))
+ call assert_equal([1, 2, 3, 1, 2, 3], l->repeat(2))
+ call assert_equal([3, 2, 1], [1, 2, 3]->reverse())
+ call assert_equal([1, 2, 3, 4], [4, 2, 3, 1]->sort())
+ call assert_equal('[1, 2, 3]', l->string())
+ call assert_equal(v:t_list, l->type())
+ call assert_equal([1, 2, 3], [1, 1, 2, 3, 3]->uniq())
+ call assert_fails('eval l->values()', 'E715:')
+endfunc
+
+func Test_dict_method()
+ let d = #{one: 1, two: 2, three: 3}
+
+ call assert_equal(d, d->copy())
+ call assert_equal(d, d->deepcopy())
+ call assert_equal(1, d->count(2))
+ call assert_false(d->empty())
+ call assert_true({}->empty())
+ call assert_equal(#{one: 1, two: 2, three: 3, four: 4}, d->extend(#{four: 4}))
+ call assert_equal(#{one: 1, two: 2, three: 3}, d->filter('v:val != 4'))
+ call assert_equal(2, d->get('two'))
+ " Nvim doesn't support Blobs yet; expect a different emsg
+ " call assert_fails("let x = d->index(2)", 'E897:')
+ " call assert_fails("let x = d->insert(0)", 'E899:')
+ call assert_fails("let x = d->index(2)", 'E714:')
+ call assert_fails("let x = d->insert(0)", 'E686:')
+ call assert_true(d->has_key('two'))
+ call assert_equal([['one', 1], ['two', 2], ['three', 3]], d->items())
+ call assert_fails("let x = d->join()", 'E714:')
+ call assert_equal(['one', 'two', 'three'], d->keys())
+ call assert_equal(3, d->len())
+ call assert_equal(#{one: 2, two: 3, three: 4}, d->map('v:val + 1'))
+ call assert_equal(#{one: 1, two: 2, three: 3}, d->map('v:val - 1'))
+ call assert_equal(3, d->max())
+ call assert_equal(1, d->min())
+ call assert_equal(2, d->remove("two"))
+ let d.two = 2
+ call assert_fails('let x = d->repeat(2)', 'E731:')
+ " Nvim doesn't support Blobs yet; expect a different emsg
+ " call assert_fails('let x = d->reverse()', 'E899:')
+ call assert_fails('let x = d->reverse()', 'E686:')
+ call assert_fails('let x = d->sort()', 'E686:')
+ call assert_equal("{'one': 1, 'two': 2, 'three': 3}", d->string())
+ call assert_equal(v:t_dict, d->type())
+ call assert_fails('let x = d->uniq()', 'E686:')
+ call assert_equal([1, 2, 3], d->values())
+endfunc
+
+func Test_string_method()
+ eval '1 2 3'->split()->assert_equal(['1', '2', '3'])
+ eval '1 2 3'->split()->map({i, v -> str2nr(v)})->assert_equal([1, 2, 3])
+ eval 'ABC'->str2list()->assert_equal([65, 66, 67])
+ eval 'ABC'->strlen()->assert_equal(3)
+ eval "a\rb\ec"->strtrans()->assert_equal('a^Mb^[c')
+ eval "aあb"->strwidth()->assert_equal(4)
+ eval 'abc'->substitute('b', 'x', '')->assert_equal('axc')
+
+ eval 'abc'->printf('the %s arg')->assert_equal('the abc arg')
+endfunc
+
+func Test_method_append()
+ new
+ eval ['one', 'two', 'three']->append(1)
+ call assert_equal(['', 'one', 'two', 'three'], getline(1, '$'))
+
+ %del
+ let bnr = bufnr('')
+ wincmd w
+ eval ['one', 'two', 'three']->appendbufline(bnr, 1)
+ call assert_equal(['', 'one', 'two', 'three'], getbufline(bnr, 1, '$'))
+
+ exe 'bwipe! ' .. bnr
+endfunc
+
+func Test_method_funcref()
+ func Concat(one, two, three)
+ return a:one .. a:two .. a:three
+ endfunc
+ let FuncRef = function('Concat')
+ eval 'foo'->FuncRef('bar', 'tail')->assert_equal('foobartail')
+
+ " not enough arguments
+ call assert_fails("eval 'foo'->FuncRef('bar')", 'E119:')
+ " too many arguments
+ call assert_fails("eval 'foo'->FuncRef('bar', 'tail', 'four')", 'E118:')
+
+ let Partial = function('Concat', ['two'])
+ eval 'one'->Partial('three')->assert_equal('onetwothree')
+
+ " not enough arguments
+ call assert_fails("eval 'one'->Partial()", 'E119:')
+ " too many arguments
+ call assert_fails("eval 'one'->Partial('three', 'four')", 'E118:')
+
+ delfunc Concat
+endfunc
+
+func Test_method_float()
+ eval 1.234->string()->assert_equal('1.234')
+ eval -1.234->string()->assert_equal('-1.234')
+endfunc
+
+func Test_method_syntax()
+ eval [1, 2, 3] ->sort( )
+ eval [1, 2, 3]
+ \ ->sort(
+ \ )
+ call assert_fails('eval [1, 2, 3]-> sort()', 'E260:')
+ call assert_fails('eval [1, 2, 3]->sort ()', 'E274:')
+ call assert_fails('eval [1, 2, 3]-> sort ()', 'E260:')
+endfunc
+
+func Test_method_lambda()
+ eval "text"->{x -> x .. " extended"}()->assert_equal('text extended')
+ eval "text"->{x, y -> x .. " extended " .. y}('more')->assert_equal('text extended more')
+
+ call assert_fails('eval "text"->{x -> x .. " extended"} ()', 'E274:')
+
+ " todo: lambda accepts more arguments than it consumes
+ " call assert_fails('eval "text"->{x -> x .. " extended"}("more")', 'E99:')
+
+ " Nvim doesn't include test_refcount().
+ " let l = [1, 2, 3]
+ " eval l->{x -> x}()
+ " call assert_equal(1, test_refcount(l))
+endfunc
+
+func Test_method_not_supported()
+ call assert_fails('eval 123->changenr()', 'E276:')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim
index 06bdb1236a..710450293c 100644
--- a/src/nvim/testdir/test_popup.vim
+++ b/src/nvim/testdir/test_popup.vim
@@ -250,7 +250,7 @@ endfunc
func Test_noinsert_complete()
func! s:complTest1() abort
- call complete(1, ['source', 'soundfold'])
+ eval ['source', 'soundfold']->complete(1)
return ''
endfunc
@@ -403,7 +403,7 @@ func DummyCompleteFour(findstart, base)
return 0
else
call complete_add('four1')
- call complete_add('four2')
+ eval 'four2'->complete_add()
call complete_check()
call complete_add('four3')
call complete_add('four4')
@@ -989,7 +989,7 @@ func GetCompleteInfo()
if empty(g:compl_what)
let g:compl_info = complete_info()
else
- let g:compl_info = complete_info(g:compl_what)
+ let g:compl_info = g:compl_what->complete_info()
endif
return ''
endfunc
diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim
index 875e23894f..2344bac498 100644
--- a/src/nvim/testdir/test_syntax.vim
+++ b/src/nvim/testdir/test_syntax.vim
@@ -546,8 +546,8 @@ func Test_synstack_synIDtrans()
call assert_equal([], synstack(1, 1))
norm f/
- call assert_equal(['cComment', 'cCommentStart'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")'))
- call assert_equal(['Comment', 'Comment'], map(synstack(line("."), col(".")), 'synIDattr(synIDtrans(v:val), "name")'))
+ eval synstack(line("."), col("."))->map('synIDattr(v:val, "name")')->assert_equal(['cComment', 'cCommentStart'])
+ eval synstack(line("."), col("."))->map('synIDattr(synIDtrans(v:val), "name")')->assert_equal(['Comment', 'Comment'])
norm fA
call assert_equal(['cComment'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")'))
diff --git a/src/nvim/testdir/test_system.vim b/src/nvim/testdir/test_system.vim
index 6bbe714d19..7b8ee778cc 100644
--- a/src/nvim/testdir/test_system.vim
+++ b/src/nvim/testdir/test_system.vim
@@ -7,10 +7,10 @@ func Test_System()
if !executable('echo') || !executable('cat') || !executable('wc')
return
endif
- let out = system('echo 123')
+ let out = 'echo 123'->system()
call assert_equal("123\n", out)
- let out = systemlist('echo 123')
+ let out = 'echo 123'->systemlist()
if &shell =~# 'cmd.exe$'
call assert_equal(["123\r"], out)
else
diff --git a/src/nvim/testdir/test_user_func.vim b/src/nvim/testdir/test_user_func.vim
index 7dcd92a54b..5231ef7b4f 100644
--- a/src/nvim/testdir/test_user_func.vim
+++ b/src/nvim/testdir/test_user_func.vim
@@ -47,7 +47,7 @@ func FuncWithRef(a)
endfunc
func Test_user_func()
- let g:FuncRef=function("FuncWithRef")
+ let g:FuncRef = function("FuncWithRef")
let g:counter = 0
inoremap <expr> ( ListItem()
inoremap <expr> [ ListReset()
@@ -62,6 +62,14 @@ func Test_user_func()
call assert_equal(9, g:retval)
call assert_equal(333, g:FuncRef(333))
+ let g:retval = "nop"
+ call assert_equal('xxx4asdf', "xxx"->Table(4, "asdf"))
+ call assert_equal('fail', 45->Compute(0, "retval"))
+ call assert_equal('nop', g:retval)
+ call assert_equal('ok', 45->Compute(5, "retval"))
+ call assert_equal(9, g:retval)
+ " call assert_equal(333, 333->g:FuncRef())
+
enew
normal oXX+-XX
@@ -150,6 +158,14 @@ func Test_default_arg()
\ execute('func Args2'))
endfunc
+func s:addFoo(lead)
+ return a:lead .. 'foo'
+endfunc
+
+func Test_user_method()
+ eval 'bar'->s:addFoo()->assert_equal('barfoo')
+endfunc
+
func Test_failed_call_in_try()
try | call UnknownFunc() | catch | endtry
endfunc
diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim
index 5922660ef9..d5837e88c9 100644
--- a/src/nvim/testdir/test_vimscript.vim
+++ b/src/nvim/testdir/test_vimscript.vim
@@ -1372,6 +1372,7 @@ func Test_bitwise_functions()
" and
call assert_equal(127, and(127, 127))
call assert_equal(16, and(127, 16))
+ eval 127->and(16)->assert_equal(16)
call assert_equal(0, and(127, 128))
call assert_fails("call and(1.0, 1)", 'E805:')
call assert_fails("call and([], 1)", 'E745:')
@@ -1382,6 +1383,7 @@ func Test_bitwise_functions()
" or
call assert_equal(23, or(16, 7))
call assert_equal(15, or(8, 7))
+ eval 8->or(7)->assert_equal(15)
call assert_equal(123, or(0, 123))
call assert_fails("call or(1.0, 1)", 'E805:')
call assert_fails("call or([], 1)", 'E745:')
@@ -1392,6 +1394,7 @@ func Test_bitwise_functions()
" xor
call assert_equal(0, xor(127, 127))
call assert_equal(111, xor(127, 16))
+ eval 127->xor(16)->assert_equal(111)
call assert_equal(255, xor(127, 128))
call assert_fails("call xor(1.0, 1)", 'E805:')
call assert_fails("call xor([], 1)", 'E745:')
@@ -1401,6 +1404,7 @@ func Test_bitwise_functions()
call assert_fails("call xor(1, {})", 'E728:')
" invert
call assert_equal(65408, and(invert(127), 65535))
+ eval 127->invert()->and(65535)->assert_equal(65408)
call assert_equal(65519, and(invert(16), 65535))
call assert_equal(65407, and(invert(128), 65535))
call assert_fails("call invert(1.0)", 'E805:')