# HG changeset patch # User Bram Moolenaar # Date 1565386205 -7200 # Node ID 06c3e15ad84da9e791cb947eb0b6ee95514d1453 # Parent 3043c52e08e40714666ab4459e2071755ab464c3 patch 8.1.1834: cannot use a lambda as a method commit https://github.com/vim/vim/commit/22a0c0c4ecd23b6c43f79ba9b92899ca0b426e29 Author: Bram Moolenaar Date: Fri Aug 9 23:25:08 2019 +0200 patch 8.1.1834: cannot use a lambda as a method Problem: Cannot use a lambda as a method. Solution: Implement ->{lambda}(). (closes https://github.com/vim/vim/issues/4768) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1217,7 +1217,8 @@ expr8(expr1, ...) |Funcref| function cal 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: > name(expr8 [, args]) @@ -1227,6 +1228,9 @@ This allows for chaining, passing the va next method: > mylist->filter(filterexpr)->map(mapexpr)->sort()->join() < +Example of using a lambda: > + GetPercentage->{x -> x * 100}()->printf('%d%%') + *E274* "->name(" must not contain white space. There can be white space before the "->" and after the "(", thus you can split the lines like this: > diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -32,6 +32,7 @@ static char *e_cannot_mod = N_("E995: Ca #ifdef FEAT_FLOAT static char *e_float_as_string = N_("E806: using Float as a String"); #endif +static char *e_nowhitespace = N_("E274: No white space allowed before parenthesis"); #define NAMESPACE_CHAR (char_u *)"abglstvw" @@ -3693,7 +3694,6 @@ eval_func( vim_memset(&funcexe, 0, sizeof(funcexe)); funcexe.firstline = curwin->w_cursor.lnum; funcexe.lastline = curwin->w_cursor.lnum; - funcexe.doesrange = &len; funcexe.evaluate = evaluate; funcexe.partial = partial; funcexe.basetv = basetv; @@ -4822,6 +4822,95 @@ eval7( } /* + * Call the function referred to in "rettv". + */ + static int +call_func_rettv( + char_u **arg, + typval_T *rettv, + int evaluate, + dict_T *selfdict, + typval_T *basetv) +{ + partial_T *pt = NULL; + funcexe_T funcexe; + typval_T functv; + char_u *s; + int ret; + + // 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; + s = partial_name(pt); + } + else + s = functv.vval.v_string; + } + else + s = (char_u *)""; + + vim_memset(&funcexe, 0, sizeof(funcexe)); + funcexe.firstline = curwin->w_cursor.lnum; + funcexe.lastline = curwin->w_cursor.lnum; + funcexe.evaluate = evaluate; + funcexe.partial = pt; + funcexe.selfdict = selfdict; + funcexe.basetv = basetv; + ret = get_func_tv(s, -1, rettv, arg, &funcexe); + + /* Clear the funcref afterwards, so that deleting it while + * evaluating the arguments is possible (see test55). */ + if (evaluate) + clear_tv(&functv); + + return ret; +} + +/* + * Evaluate "->method()". + * "*arg" points to the '-'. + * Returns FAIL or OK. "*arg" is advanced to after the ')'. + */ + static int +eval_lambda( + char_u **arg, + typval_T *rettv, + int evaluate, + int verbose) /* give error messages */ +{ + typval_T base = *rettv; + int ret; + + // Skip over the ->. + *arg += 2; + rettv->v_type = VAR_UNKNOWN; + + ret = get_lambda_tv(arg, rettv, evaluate); + if (ret == NOTDONE) + return FAIL; + else if (**arg != '(') + { + if (verbose) + { + if (*skipwhite(*arg) == '(') + semsg(_(e_nowhitespace)); + else + semsg(_(e_missingparen), "lambda"); + } + clear_tv(rettv); + return FAIL; + } + return call_func_rettv(arg, rettv, evaluate, NULL, &base); +} + +/* * Evaluate "->method()". * "*arg" points to the '-'. * Returns FAIL or OK. "*arg" is advanced to after the ')'. @@ -4865,15 +4954,15 @@ eval_method( else if (VIM_ISWHITE((*arg)[-1])) { if (verbose) - semsg(_("E274: No white space allowed before parenthesis")); + semsg(_(e_nowhitespace)); ret = FAIL; } 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). */ + // Clear the funcref afterwards, so that deleting it while + // evaluating the arguments is possible (see test55). if (evaluate) clear_tv(&base); @@ -7455,8 +7544,6 @@ handle_subscript( { int ret = OK; dict_T *selfdict = NULL; - char_u *s; - typval_T functv; // "." is ".name" lookup when we found a dict or when evaluating and // scriptversion is at least 2, where string concatenation is "..". @@ -7473,43 +7560,11 @@ handle_subscript( { if (**arg == '(') { - partial_T *pt = NULL; - funcexe_T funcexe; - - /* 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; - s = partial_name(pt); - } - else - s = functv.vval.v_string; - } - else - s = (char_u *)""; - - vim_memset(&funcexe, 0, sizeof(funcexe)); - 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, -1, rettv, arg, &funcexe); - - /* Clear the funcref afterwards, so that deleting it while - * evaluating the arguments is possible (see test55). */ - if (evaluate) - clear_tv(&functv); - - /* Stop the expression evaluation when immediately aborting on - * error, or when an interrupt occurred or an exception was thrown - * but not caught. */ + ret = call_func_rettv(arg, rettv, evaluate, selfdict, NULL); + + // 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) @@ -7521,11 +7576,12 @@ handle_subscript( } else if (**arg == '-') { - if (eval_method(arg, rettv, evaluate, verbose) == FAIL) - { - clear_tv(rettv); - ret = FAIL; - } + if ((*arg)[2] == '{') + // expr->{lambda}() + ret = eval_lambda(arg, rettv, evaluate, verbose); + else + // expr->name() + ret = eval_method(arg, rettv, evaluate, verbose); } else /* **arg == '[' || **arg == '.' */ { diff --git a/src/testdir/test_method.vim b/src/testdir/test_method.vim --- a/src/testdir/test_method.vim +++ b/src/testdir/test_method.vim @@ -122,3 +122,13 @@ func Test_method_syntax() 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:') +endfunc diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -770,6 +770,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1834, +/**/ 1833, /**/ 1832,