changeset 23318:c76240efdf02 v8.2.2204

patch 8.2.2204: Vim9: using -> both for method and lambda is confusing Commit: https://github.com/vim/vim/commit/65c4415276394c871c7a8711c7633c19ec9235b1 Author: Bram Moolenaar <Bram@vim.org> Date: Thu Dec 24 15:14:01 2020 +0100 patch 8.2.2204: Vim9: using -> both for method and lambda is confusing Problem: Vim9: using -> both for method and lambda is confusing. Solution: Use => for lambda in :def function.
author Bram Moolenaar <Bram@vim.org>
date Thu, 24 Dec 2020 15:15:05 +0100
parents 0aa1d1862cb9
children 2e18dcd91c7b
files runtime/doc/vim9.txt src/testdir/test_vim9_expr.vim src/userfunc.c src/version.c src/vim9compile.c
diffstat 5 files changed, 247 insertions(+), 45 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/vim9.txt
+++ b/runtime/doc/vim9.txt
@@ -1,4 +1,4 @@
-*vim9.txt*	For Vim version 8.2.  Last change: 2020 Dec 23
+*vim9.txt*	For Vim version 8.2.  Last change: 2020 Dec 24
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -340,6 +340,40 @@ When using `function()` the resulting ty
 number of arguments and any return type.  The function can be defined later.
 
 
+Lamba using => instead of -> ~
+
+In legacy script there can be confusion between using "->" for a method call
+and for a lambda.  Also, when a "{" is found the parser needs to figure out if
+it is the start of a lambda or a dictionary, which is now more complicated
+because of the use of argument types.
+
+To avoid these problems Vim9 script uses a different syntax for a lambda,
+which is similar to Javascript: >
+	var Lambda = (arg) => expression
+
+No line break is allowed in the arguments of a lambda up to and includeing the
+"=>".  This is OK: >
+	filter(list, (k, v) =>
+			v > 0)
+This does not work: >
+	filter(list, (k, v)
+			=> v > 0)
+This also does not work:
+	filter(list, (k,
+			v) => v > 0)
+
+Additionally, a lambda can contain statements in {}: >
+	var Lambda = (arg) => {
+		g:was_called = 'yes'
+		return expression
+	    }
+NOT IMPLEMENTED YET
+
+Note that the "{" must be followed by white space, otherwise it is assumed to
+be the start of a dictionary: >
+	var Lambda = (arg) => {key: 42}
+
+
 Automatic line continuation ~
 
 In many cases it is obvious that an expression continues on the next line.  In
@@ -405,7 +439,7 @@ arguments: >
 		): string
 
 Since a continuation line cannot be easily recognized the parsing of commands
-has been made sticter.  E.g., because of the error in the first line, the
+has been made stricter.  E.g., because of the error in the first line, the
 second line is seen as a separate command: >
 	popup_create(some invalid expression, {
 	   exit_cb: Func})
@@ -433,14 +467,6 @@ Notes:
 <  This does not work: >
 	echo [1, 2]
 		[3, 4]
-- No line break is allowed in the arguments of a lambda, between the "{" and
-  "->".  This is OK: >
-	filter(list, {k, v ->
-			v > 0})
-<  This does not work: >
-	filter(list, {k,
-			v -> v > 0})
-
 
 No curly braces expansion ~
 
--- a/src/testdir/test_vim9_expr.vim
+++ b/src/testdir/test_vim9_expr.vim
@@ -1887,6 +1887,100 @@ def Test_expr7_lambda()
   CheckDefFailure(['var Fx = {a -> [0', ' 1]}'], 'E696:', 2)
 enddef
 
+def NewLambdaWithComments(): func
+  return (x) =>
+            # some comment
+            x == 1
+            # some comment
+            ||
+            x == 2
+enddef
+
+def NewLambdaUsingArg(x: number): func
+  return () =>
+            # some comment
+            x == 1
+            # some comment
+            ||
+            x == 2
+enddef
+
+def Test_expr7_new_lambda()
+  var lines =<< trim END
+      var La = () => 'result'
+      assert_equal('result', La())
+      assert_equal([1, 3, 5], [1, 2, 3]->map((key, val) => key + val))
+
+      # line continuation inside lambda with "cond ? expr : expr" works
+      var ll = range(3)
+      map(ll, (k, v) => v % 2 ? {
+                ['111']: 111 } : {}
+            )
+      assert_equal([{}, {111: 111}, {}], ll)
+
+      ll = range(3)
+      map(ll, (k, v) => v == 8 || v
+                    == 9
+                    || v % 2 ? 111 : 222
+            )
+      assert_equal([222, 111, 222], ll)
+
+      ll = range(3)
+      map(ll, (k, v) => v != 8 && v
+                    != 9
+                    && v % 2 == 0 ? 111 : 222
+            )
+      assert_equal([111, 222, 111], ll)
+
+      var dl = [{key: 0}, {key: 22}]->filter(( _, v) => v['key'] )
+      assert_equal([{key: 22}], dl)
+
+      dl = [{key: 12}, {['foo']: 34}]
+      assert_equal([{key: 12}], filter(dl,
+            (_, v) => has_key(v, 'key') ? v['key'] == 12 : 0))
+
+      assert_equal(false, NewLambdaWithComments()(0))
+      assert_equal(true, NewLambdaWithComments()(1))
+      assert_equal(true, NewLambdaWithComments()(2))
+      assert_equal(false, NewLambdaWithComments()(3))
+
+      assert_equal(false, NewLambdaUsingArg(0)())
+      assert_equal(true, NewLambdaUsingArg(1)())
+
+      var res = map([1, 2, 3], (i: number, v: number) => i + v)
+      assert_equal([1, 3, 5], res)
+
+      # Lambda returning a dict
+      var Lmb = () => {key: 42}
+      assert_equal({key: 42}, Lmb())
+  END
+  CheckDefSuccess(lines)
+
+  CheckDefFailure(["var Ref = (a)=>a + 1"], 'E1001:')
+  CheckDefFailure(["var Ref = (a)=> a + 1"], 'E1001:')
+  CheckDefFailure(["var Ref = (a) =>a + 1"], 'E1001:')
+
+  CheckDefFailure(["filter([1, 2], (k,v) => 1)"], 'E1069:', 1)
+  # error is in first line of the lambda
+  CheckDefFailure(["var L = (a) -> a + b"], 'E1001:', 1)
+
+# TODO: lambda after -> doesn't work yet
+#  assert_equal('xxxyyy', 'xxx'->((a, b) => a .. b)('yyy'))
+
+#  CheckDefExecFailure(["var s = 'asdf'->{a -> a}('x')"],
+#        'E1106: One argument too many')
+#  CheckDefExecFailure(["var s = 'asdf'->{a -> a}('x', 'y')"],
+#        'E1106: 2 arguments too many')
+#  CheckDefFailure(["echo 'asdf'->{a -> a}(x)"], 'E1001:', 1)
+
+  CheckDefSuccess(['var Fx = (a) => {k1: 0,', ' k2: 1}'])
+  CheckDefFailure(['var Fx = (a) => {k1: 0', ' k2: 1}'], 'E722:', 2)
+  CheckDefFailure(['var Fx = (a) => {k1: 0,', ' k2 1}'], 'E720:', 2)
+
+  CheckDefSuccess(['var Fx = (a) => [0,', ' 1]'])
+  CheckDefFailure(['var Fx = (a) => [0', ' 1]'], 'E696:', 2)
+enddef
+
 def Test_expr7_lambda_vim9script()
   var lines =<< trim END
       vim9script
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -154,6 +154,7 @@ one_function_arg(
 
 /*
  * Get function arguments.
+ * "argp" is advanced just after "endchar".
  */
     int
 get_function_args(
@@ -458,7 +459,31 @@ register_cfunc(cfunc_T cb, cfunc_free_T 
 #endif
 
 /*
+ * Skip over "->" or "=>" after the arguments of a lambda.
+ * Return NULL if no valid arrow found.
+ */
+    static char_u *
+skip_arrow(char_u *start, int equal_arrow)
+{
+    char_u *s = start;
+
+    if (equal_arrow)
+    {
+	if (*s == ':')
+	    s = skip_type(skipwhite(s + 1), TRUE);
+	s = skipwhite(s);
+	if (*s != '=')
+	    return NULL;
+	++s;
+    }
+    if (*s != '>')
+	return NULL;
+    return skipwhite(s + 1);
+}
+
+/*
  * Parse a lambda expression and get a Funcref from "*arg".
+ * "arg" points to the { in "{arg -> expr}" or the ( in "(arg) => expr"
  * When "types_optional" is TRUE optionally take argument types.
  * Return OK or FAIL.  Returns NOTDONE for dict or {expr}.
  */
@@ -484,16 +509,20 @@ get_lambda_tv(
     int		*old_eval_lavars = eval_lavars_used;
     int		eval_lavars = FALSE;
     char_u	*tofree = NULL;
+    int		equal_arrow = **arg == '(';
+
+    if (equal_arrow && !in_vim9script())
+	return NOTDONE;
 
     ga_init(&newargs);
     ga_init(&newlines);
 
-    // First, check if this is a lambda expression. "->" must exist.
+    // First, check if this is a lambda expression. "->" or "=>" must exist.
     s = skipwhite(*arg + 1);
-    ret = get_function_args(&s, '-', NULL,
+    ret = get_function_args(&s, equal_arrow ? ')' : '-', NULL,
 	    types_optional ? &argtypes : NULL, types_optional,
 						 NULL, NULL, TRUE, NULL, NULL);
-    if (ret == FAIL || *s != '>')
+    if (ret == FAIL || skip_arrow(s, equal_arrow) == NULL)
 	return NOTDONE;
 
     // Parse the arguments again.
@@ -502,18 +531,28 @@ get_lambda_tv(
     else
 	pnewargs = NULL;
     *arg = skipwhite(*arg + 1);
-    ret = get_function_args(arg, '-', pnewargs,
+    ret = get_function_args(arg, equal_arrow ? ')' : '-', pnewargs,
 	    types_optional ? &argtypes : NULL, types_optional,
 					    &varargs, NULL, FALSE, NULL, NULL);
-    if (ret == FAIL || **arg != '>')
-	goto errret;
+    if (ret == FAIL || (*arg = skip_arrow(*arg, equal_arrow)) == NULL)
+	return NOTDONE;
 
     // Set up a flag for checking local variables and arguments.
     if (evaluate)
 	eval_lavars_used = &eval_lavars;
 
+    *arg = skipwhite_and_linebreak(*arg, evalarg);
+
+    // Only recognize "{" as the start of a function body when followed by
+    // white space, "{key: val}" is a dict.
+    if (equal_arrow && **arg == '{' && IS_WHITE_OR_NUL((*arg)[1]))
+    {
+	// TODO: process the function body upto the "}".
+	emsg("Lambda function body not supported yet");
+	goto errret;
+    }
+
     // Get the start and the end of the expression.
-    *arg = skipwhite_and_linebreak(*arg + 1, evalarg);
     start = *arg;
     ret = skip_expr_concatenate(arg, &start, &end, evalarg);
     if (ret == FAIL)
@@ -525,13 +564,16 @@ get_lambda_tv(
 	evalarg->eval_tofree = NULL;
     }
 
-    *arg = skipwhite_and_linebreak(*arg, evalarg);
-    if (**arg != '}')
+    if (!equal_arrow)
     {
-	semsg(_("E451: Expected }: %s"), *arg);
-	goto errret;
+	*arg = skipwhite_and_linebreak(*arg, evalarg);
+	if (**arg != '}')
+	{
+	    semsg(_("E451: Expected }: %s"), *arg);
+	    goto errret;
+	}
+	++*arg;
     }
-    ++*arg;
 
     if (evaluate)
     {
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    2204,
+/**/
     2203,
 /**/
     2202,
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -2967,12 +2967,12 @@ compile_lambda(char_u **arg, cctx_T *cct
 	return FAIL;
     }
 
+    // "rettv" will now be a partial referencing the function.
     ufunc = rettv.vval.v_partial->pt_func;
     ++ufunc->uf_refcount;
     clear_tv(&rettv);
 
-    // The function will have one line: "return {expr}".
-    // Compile it into instructions.
+    // Compile the function into instructions.
     compile_def_function(ufunc, TRUE, cctx);
 
     clear_evalarg(&evalarg, NULL);
@@ -3565,6 +3565,15 @@ compile_subscript(
 	    if (**arg == '{')
 	    {
 		// lambda call:  list->{lambda}
+		// TODO: remove this
+		if (compile_lambda_call(arg, cctx) == FAIL)
+		    return FAIL;
+	    }
+	    else if (**arg == '(')
+	    {
+		// Funcref call:  list->(Refs[2])()
+		// or lambda:	  list->((arg) => expr)()
+		// TODO: make this work
 		if (compile_lambda_call(arg, cctx) == FAIL)
 		    return FAIL;
 	    }
@@ -3928,6 +3937,8 @@ compile_expr7(
 						     && VIM_ISWHITE(after[-2]))
 				|| after == start + 1)
 				&& IS_WHITE_OR_NUL(after[1]))
+			    // TODO: if we go with the "(arg) => expr" syntax
+			    // remove this
 			    ret = compile_lambda(arg, cctx);
 			else
 			    ret = compile_dict(arg, cctx, ppconst);
@@ -3959,28 +3970,55 @@ compile_expr7(
 			break;
 	/*
 	 * nested expression: (expression).
+	 * lambda: (arg, arg) => expr
+	 * funcref: (arg, arg) => { statement }
 	 */
-	case '(':   *arg = skipwhite(*arg + 1);
-
-		    // recursive!
-		    if (ppconst->pp_used <= PPSIZE - 10)
-		    {
-			ret = compile_expr1(arg, cctx, ppconst);
-		    }
-		    else
-		    {
-			// Not enough space in ppconst, flush constants.
-			if (generate_ppconst(cctx, ppconst) == FAIL)
-			    return FAIL;
-			ret = compile_expr0(arg, cctx);
-		    }
-		    *arg = skipwhite(*arg);
-		    if (**arg == ')')
-			++*arg;
-		    else if (ret == OK)
-		    {
-			emsg(_(e_missing_close));
-			ret = FAIL;
+	case '(':   {
+			char_u	    *start = skipwhite(*arg + 1);
+			char_u	    *after = start;
+			garray_T    ga_arg;
+
+			// Find out if "=>" comes after the ().
+			ret = get_function_args(&after, ')', NULL,
+						     &ga_arg, TRUE, NULL, NULL,
+							     TRUE, NULL, NULL);
+			if (ret == OK && VIM_ISWHITE(
+					    *after == ':' ? after[1] : *after))
+			{
+			    if (*after == ':')
+				// Skip over type in "(arg): type".
+				after = skip_type(skipwhite(after + 1), TRUE);
+
+			    after = skipwhite(after);
+			    if (after[0] == '=' && after[1] == '>'
+						  && IS_WHITE_OR_NUL(after[2]))
+			    {
+				ret = compile_lambda(arg, cctx);
+				break;
+			    }
+			}
+
+			// (expression): recursive!
+			*arg = skipwhite(*arg + 1);
+			if (ppconst->pp_used <= PPSIZE - 10)
+			{
+			    ret = compile_expr1(arg, cctx, ppconst);
+			}
+			else
+			{
+			    // Not enough space in ppconst, flush constants.
+			    if (generate_ppconst(cctx, ppconst) == FAIL)
+				return FAIL;
+			    ret = compile_expr0(arg, cctx);
+			}
+			*arg = skipwhite(*arg);
+			if (**arg == ')')
+			    ++*arg;
+			else if (ret == OK)
+			{
+			    emsg(_(e_missing_close));
+			    ret = FAIL;
+			}
 		    }
 		    break;