mirror of
https://github.com/neovim/neovim.git
synced 2025-09-14 07:18:17 +00:00
vim-patch:8.1.1834: cannot use a lambda as a method
Problem: Cannot use a lambda as a method.
Solution: Implement ->{lambda}(). (closes vim/vim#4768)
22a0c0c4ec
Add an additional lua_funcname argument to call_func_rettv() to maintain
support for v:lua.
A memory leak was introduced with this patch that was fixed in
v8.1.2107.
This commit is contained in:
@@ -1047,7 +1047,8 @@ expr8(expr1, ...) |Funcref| function call
|
|||||||
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.
|
||||||
|
|
||||||
|
|
||||||
expr8->name([args]) method call *method*
|
expr8->name([args]) method call *method* *->*
|
||||||
|
expr8->{lambda}([args])
|
||||||
|
|
||||||
For methods that are also available as global functions this is the same as: >
|
For methods that are also available as global functions this is the same as: >
|
||||||
name(expr8 [, args])
|
name(expr8 [, args])
|
||||||
@@ -1057,6 +1058,9 @@ This allows for chaining, passing the value that one method returns to the
|
|||||||
next method: >
|
next method: >
|
||||||
mylist->filter(filterexpr)->map(mapexpr)->sort()->join()
|
mylist->filter(filterexpr)->map(mapexpr)->sort()->join()
|
||||||
<
|
<
|
||||||
|
Example of using a lambda: >
|
||||||
|
GetPercentage->{x -> x * 100}()->printf('%d%%')
|
||||||
|
|
||||||
*E274*
|
*E274*
|
||||||
"->name(" must not contain white space. There can be white space before the
|
"->name(" must not contain white space. There can be white space before the
|
||||||
"->" and after the "(", thus you can split the lines like this: >
|
"->" and after the "(", thus you can split the lines like this: >
|
||||||
|
151
src/nvim/eval.c
151
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_dictrange = N_("E719: Cannot use [:] with a Dictionary");
|
||||||
static char *e_illvar = N_("E461: Illegal variable name: %s");
|
static char *e_illvar = N_("E461: Illegal variable name: %s");
|
||||||
static char *e_cannot_mod = N_("E995: Cannot modify existing variable");
|
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_invalwindow = N_("E957: Invalid window number");
|
||||||
static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s");
|
static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s");
|
||||||
|
|
||||||
@@ -4100,6 +4102,90 @@ static int eval7(
|
|||||||
return ret;
|
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 {
|
||||||
|
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;
|
||||||
|
|
||||||
|
const 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tv_clear(rettv);
|
||||||
|
return FAIL;
|
||||||
|
}
|
||||||
|
return call_func_rettv(arg, rettv, evaluate, NULL, &base, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
/// Evaluate "->method()".
|
/// Evaluate "->method()".
|
||||||
/// @note "*arg" points to the '-'.
|
/// @note "*arg" points to the '-'.
|
||||||
/// @return FAIL or OK. "*arg" is advanced to after the ')'.
|
/// @return FAIL or OK. "*arg" is advanced to after the ')'.
|
||||||
@@ -4136,7 +4222,7 @@ static int eval_method(char_u **const arg, typval_T *const rettv,
|
|||||||
ret = FAIL;
|
ret = FAIL;
|
||||||
} else if (ascii_iswhite((*arg)[-1])) {
|
} else if (ascii_iswhite((*arg)[-1])) {
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
EMSG(_("E274: No white space allowed before parenthesis"));
|
EMSG(_(e_nowhitespace));
|
||||||
}
|
}
|
||||||
ret = FAIL;
|
ret = FAIL;
|
||||||
} else {
|
} else {
|
||||||
@@ -8510,10 +8596,7 @@ handle_subscript(
|
|||||||
{
|
{
|
||||||
int ret = OK;
|
int ret = OK;
|
||||||
dict_T *selfdict = NULL;
|
dict_T *selfdict = NULL;
|
||||||
const char_u *s;
|
const char_u *lua_funcname = NULL;
|
||||||
typval_T functv;
|
|
||||||
int slen = 0;
|
|
||||||
bool lua = false;
|
|
||||||
|
|
||||||
if (tv_is_luafunc(rettv)) {
|
if (tv_is_luafunc(rettv)) {
|
||||||
if (**arg != '.') {
|
if (**arg != '.') {
|
||||||
@@ -8522,14 +8605,13 @@ handle_subscript(
|
|||||||
} else {
|
} else {
|
||||||
(*arg)++;
|
(*arg)++;
|
||||||
|
|
||||||
lua = true;
|
lua_funcname = (char_u *)(*arg);
|
||||||
s = (char_u *)(*arg);
|
const int len = check_luafunc_name(*arg, true);
|
||||||
slen = check_luafunc_name(*arg, true);
|
if (len == 0) {
|
||||||
if (slen == 0) {
|
|
||||||
tv_clear(rettv);
|
tv_clear(rettv);
|
||||||
ret = FAIL;
|
ret = FAIL;
|
||||||
}
|
}
|
||||||
(*arg) += slen;
|
(*arg) += len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8539,42 +8621,12 @@ handle_subscript(
|
|||||||
&& !ascii_iswhite(*(*arg - 1)))
|
&& !ascii_iswhite(*(*arg - 1)))
|
||||||
|| (**arg == '-' && (*arg)[1] == '>'))) {
|
|| (**arg == '-' && (*arg)[1] == '>'))) {
|
||||||
if (**arg == '(') {
|
if (**arg == '(') {
|
||||||
partial_T *pt = NULL;
|
ret = call_func_rettv((char_u **)arg, rettv, evaluate, selfdict, NULL,
|
||||||
// need to copy the funcref so that we can clear rettv
|
lua_funcname);
|
||||||
if (evaluate) {
|
|
||||||
functv = *rettv;
|
|
||||||
rettv->v_type = VAR_UNKNOWN;
|
|
||||||
|
|
||||||
// Invoke the function. Recursive!
|
// Stop the expression evaluation when immediately aborting on
|
||||||
if (functv.v_type == VAR_PARTIAL) {
|
// error, or when an interrupt occurred or an exception was thrown
|
||||||
pt = functv.vval.v_partial;
|
// but not caught.
|
||||||
if (!lua) {
|
|
||||||
s = partial_name(pt);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
s = functv.vval.v_string;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
s = (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;
|
|
||||||
ret = get_func_tv(s, lua ? slen : -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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 (aborting()) {
|
||||||
if (ret == OK) {
|
if (ret == OK) {
|
||||||
tv_clear(rettv);
|
tv_clear(rettv);
|
||||||
@@ -8584,9 +8636,12 @@ handle_subscript(
|
|||||||
tv_dict_unref(selfdict);
|
tv_dict_unref(selfdict);
|
||||||
selfdict = NULL;
|
selfdict = NULL;
|
||||||
} else if (**arg == '-') {
|
} else if (**arg == '-') {
|
||||||
if (eval_method((char_u **)arg, rettv, evaluate, verbose) == FAIL) {
|
if ((*arg)[2] == '{') {
|
||||||
tv_clear(rettv);
|
// expr->{lambda}()
|
||||||
ret = FAIL;
|
ret = eval_lambda((char_u **)arg, rettv, evaluate, verbose);
|
||||||
|
} else {
|
||||||
|
// expr->name()
|
||||||
|
ret = eval_method((char_u **)arg, rettv, evaluate, verbose);
|
||||||
}
|
}
|
||||||
} else { // **arg == '[' || **arg == '.'
|
} else { // **arg == '[' || **arg == '.'
|
||||||
tv_dict_unref(selfdict);
|
tv_dict_unref(selfdict);
|
||||||
|
@@ -128,4 +128,14 @@ func Test_method_syntax()
|
|||||||
call assert_fails('eval [1, 2, 3]-> sort ()', 'E260:')
|
call assert_fails('eval [1, 2, 3]-> sort ()', 'E260:')
|
||||||
endfunc
|
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:')
|
||||||
|
endfunc
|
||||||
|
|
||||||
" vim: shiftwidth=2 sts=2 expandtab
|
" vim: shiftwidth=2 sts=2 expandtab
|
||||||
|
Reference in New Issue
Block a user