0
0
mirror of https://github.com/vim/vim.git synced 2025-10-07 05:54:16 -04:00

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.
This commit is contained in:
Bram Moolenaar
2020-12-24 15:14:01 +01:00
parent b34f337472
commit 65c4415276
5 changed files with 246 additions and 44 deletions

View File

@@ -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 type is "func", a function with any
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 ~

View File

@@ -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

View File

@@ -154,6 +154,7 @@ one_function_arg(
/*
* Get function arguments.
* "argp" is advanced just after "endchar".
*/
int
get_function_args(
@@ -457,8 +458,32 @@ register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state)
}
#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,6 +564,8 @@ get_lambda_tv(
evalarg->eval_tofree = NULL;
}
if (!equal_arrow)
{
*arg = skipwhite_and_linebreak(*arg, evalarg);
if (**arg != '}')
{
@@ -532,6 +573,7 @@ get_lambda_tv(
goto errret;
}
++*arg;
}
if (evaluate)
{

View File

@@ -750,6 +750,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
2204,
/**/
2203,
/**/

View File

@@ -2967,12 +2967,12 @@ compile_lambda(char_u **arg, cctx_T *cctx)
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,10 +3970,36 @@ compile_expr7(
break;
/*
* nested expression: (expression).
* lambda: (arg, arg) => expr
* funcref: (arg, arg) => { statement }
*/
case '(': *arg = skipwhite(*arg + 1);
case '(': {
char_u *start = skipwhite(*arg + 1);
char_u *after = start;
garray_T ga_arg;
// recursive!
// 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);
@@ -3982,6 +4019,7 @@ compile_expr7(
emsg(_(e_missing_close));
ret = FAIL;
}
}
break;
default: ret = NOTDONE;