changeset 17674:06c3e15ad84d v8.1.1834

patch 8.1.1834: cannot use a lambda as a method commit https://github.com/vim/vim/commit/22a0c0c4ecd23b6c43f79ba9b92899ca0b426e29 Author: Bram Moolenaar <Bram@vim.org> 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)
author Bram Moolenaar <Bram@vim.org>
date Fri, 09 Aug 2019 23:30:05 +0200
parents 3043c52e08e4
children 6407bb09ec9b
files runtime/doc/eval.txt src/eval.c src/testdir/test_method.vim src/version.c
diffstat 4 files changed, 121 insertions(+), 49 deletions(-) [+]
line wrap: on
line diff
--- 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: >
--- 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 == '.' */
 	{
--- 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
--- 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,