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:
@@ -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 ~
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -750,6 +750,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
2204,
|
||||
/**/
|
||||
2203,
|
||||
/**/
|
||||
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user