mirror of
https://github.com/vim/vim.git
synced 2025-10-09 06:14:17 -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
|
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.
|
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 ~
|
Automatic line continuation ~
|
||||||
|
|
||||||
In many cases it is obvious that an expression continues on the next line. In
|
In many cases it is obvious that an expression continues on the next line. In
|
||||||
@@ -405,7 +439,7 @@ arguments: >
|
|||||||
): string
|
): string
|
||||||
|
|
||||||
Since a continuation line cannot be easily recognized the parsing of commands
|
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: >
|
second line is seen as a separate command: >
|
||||||
popup_create(some invalid expression, {
|
popup_create(some invalid expression, {
|
||||||
exit_cb: Func})
|
exit_cb: Func})
|
||||||
@@ -433,14 +467,6 @@ Notes:
|
|||||||
< This does not work: >
|
< This does not work: >
|
||||||
echo [1, 2]
|
echo [1, 2]
|
||||||
[3, 4]
|
[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 ~
|
No curly braces expansion ~
|
||||||
|
|
||||||
|
@@ -1887,6 +1887,100 @@ def Test_expr7_lambda()
|
|||||||
CheckDefFailure(['var Fx = {a -> [0', ' 1]}'], 'E696:', 2)
|
CheckDefFailure(['var Fx = {a -> [0', ' 1]}'], 'E696:', 2)
|
||||||
enddef
|
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()
|
def Test_expr7_lambda_vim9script()
|
||||||
var lines =<< trim END
|
var lines =<< trim END
|
||||||
vim9script
|
vim9script
|
||||||
|
@@ -154,6 +154,7 @@ one_function_arg(
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Get function arguments.
|
* Get function arguments.
|
||||||
|
* "argp" is advanced just after "endchar".
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
get_function_args(
|
get_function_args(
|
||||||
@@ -457,8 +458,32 @@ register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state)
|
|||||||
}
|
}
|
||||||
#endif
|
#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".
|
* 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.
|
* When "types_optional" is TRUE optionally take argument types.
|
||||||
* Return OK or FAIL. Returns NOTDONE for dict or {expr}.
|
* 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 *old_eval_lavars = eval_lavars_used;
|
||||||
int eval_lavars = FALSE;
|
int eval_lavars = FALSE;
|
||||||
char_u *tofree = NULL;
|
char_u *tofree = NULL;
|
||||||
|
int equal_arrow = **arg == '(';
|
||||||
|
|
||||||
|
if (equal_arrow && !in_vim9script())
|
||||||
|
return NOTDONE;
|
||||||
|
|
||||||
ga_init(&newargs);
|
ga_init(&newargs);
|
||||||
ga_init(&newlines);
|
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);
|
s = skipwhite(*arg + 1);
|
||||||
ret = get_function_args(&s, '-', NULL,
|
ret = get_function_args(&s, equal_arrow ? ')' : '-', NULL,
|
||||||
types_optional ? &argtypes : NULL, types_optional,
|
types_optional ? &argtypes : NULL, types_optional,
|
||||||
NULL, NULL, TRUE, NULL, NULL);
|
NULL, NULL, TRUE, NULL, NULL);
|
||||||
if (ret == FAIL || *s != '>')
|
if (ret == FAIL || skip_arrow(s, equal_arrow) == NULL)
|
||||||
return NOTDONE;
|
return NOTDONE;
|
||||||
|
|
||||||
// Parse the arguments again.
|
// Parse the arguments again.
|
||||||
@@ -502,18 +531,28 @@ get_lambda_tv(
|
|||||||
else
|
else
|
||||||
pnewargs = NULL;
|
pnewargs = NULL;
|
||||||
*arg = skipwhite(*arg + 1);
|
*arg = skipwhite(*arg + 1);
|
||||||
ret = get_function_args(arg, '-', pnewargs,
|
ret = get_function_args(arg, equal_arrow ? ')' : '-', pnewargs,
|
||||||
types_optional ? &argtypes : NULL, types_optional,
|
types_optional ? &argtypes : NULL, types_optional,
|
||||||
&varargs, NULL, FALSE, NULL, NULL);
|
&varargs, NULL, FALSE, NULL, NULL);
|
||||||
if (ret == FAIL || **arg != '>')
|
if (ret == FAIL || (*arg = skip_arrow(*arg, equal_arrow)) == NULL)
|
||||||
goto errret;
|
return NOTDONE;
|
||||||
|
|
||||||
// Set up a flag for checking local variables and arguments.
|
// Set up a flag for checking local variables and arguments.
|
||||||
if (evaluate)
|
if (evaluate)
|
||||||
eval_lavars_used = &eval_lavars;
|
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.
|
// Get the start and the end of the expression.
|
||||||
*arg = skipwhite_and_linebreak(*arg + 1, evalarg);
|
|
||||||
start = *arg;
|
start = *arg;
|
||||||
ret = skip_expr_concatenate(arg, &start, &end, evalarg);
|
ret = skip_expr_concatenate(arg, &start, &end, evalarg);
|
||||||
if (ret == FAIL)
|
if (ret == FAIL)
|
||||||
@@ -525,13 +564,16 @@ get_lambda_tv(
|
|||||||
evalarg->eval_tofree = NULL;
|
evalarg->eval_tofree = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
*arg = skipwhite_and_linebreak(*arg, evalarg);
|
if (!equal_arrow)
|
||||||
if (**arg != '}')
|
|
||||||
{
|
{
|
||||||
semsg(_("E451: Expected }: %s"), *arg);
|
*arg = skipwhite_and_linebreak(*arg, evalarg);
|
||||||
goto errret;
|
if (**arg != '}')
|
||||||
|
{
|
||||||
|
semsg(_("E451: Expected }: %s"), *arg);
|
||||||
|
goto errret;
|
||||||
|
}
|
||||||
|
++*arg;
|
||||||
}
|
}
|
||||||
++*arg;
|
|
||||||
|
|
||||||
if (evaluate)
|
if (evaluate)
|
||||||
{
|
{
|
||||||
|
@@ -750,6 +750,8 @@ static char *(features[]) =
|
|||||||
|
|
||||||
static int included_patches[] =
|
static int included_patches[] =
|
||||||
{ /* Add new patch number below this line */
|
{ /* Add new patch number below this line */
|
||||||
|
/**/
|
||||||
|
2204,
|
||||||
/**/
|
/**/
|
||||||
2203,
|
2203,
|
||||||
/**/
|
/**/
|
||||||
|
@@ -2967,12 +2967,12 @@ compile_lambda(char_u **arg, cctx_T *cctx)
|
|||||||
return FAIL;
|
return FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "rettv" will now be a partial referencing the function.
|
||||||
ufunc = rettv.vval.v_partial->pt_func;
|
ufunc = rettv.vval.v_partial->pt_func;
|
||||||
++ufunc->uf_refcount;
|
++ufunc->uf_refcount;
|
||||||
clear_tv(&rettv);
|
clear_tv(&rettv);
|
||||||
|
|
||||||
// The function will have one line: "return {expr}".
|
// Compile the function into instructions.
|
||||||
// Compile it into instructions.
|
|
||||||
compile_def_function(ufunc, TRUE, cctx);
|
compile_def_function(ufunc, TRUE, cctx);
|
||||||
|
|
||||||
clear_evalarg(&evalarg, NULL);
|
clear_evalarg(&evalarg, NULL);
|
||||||
@@ -3565,6 +3565,15 @@ compile_subscript(
|
|||||||
if (**arg == '{')
|
if (**arg == '{')
|
||||||
{
|
{
|
||||||
// lambda call: list->{lambda}
|
// 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)
|
if (compile_lambda_call(arg, cctx) == FAIL)
|
||||||
return FAIL;
|
return FAIL;
|
||||||
}
|
}
|
||||||
@@ -3928,6 +3937,8 @@ compile_expr7(
|
|||||||
&& VIM_ISWHITE(after[-2]))
|
&& VIM_ISWHITE(after[-2]))
|
||||||
|| after == start + 1)
|
|| after == start + 1)
|
||||||
&& IS_WHITE_OR_NUL(after[1]))
|
&& IS_WHITE_OR_NUL(after[1]))
|
||||||
|
// TODO: if we go with the "(arg) => expr" syntax
|
||||||
|
// remove this
|
||||||
ret = compile_lambda(arg, cctx);
|
ret = compile_lambda(arg, cctx);
|
||||||
else
|
else
|
||||||
ret = compile_dict(arg, cctx, ppconst);
|
ret = compile_dict(arg, cctx, ppconst);
|
||||||
@@ -3959,28 +3970,55 @@ compile_expr7(
|
|||||||
break;
|
break;
|
||||||
/*
|
/*
|
||||||
* nested expression: (expression).
|
* 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 ().
|
||||||
if (ppconst->pp_used <= PPSIZE - 10)
|
ret = get_function_args(&after, ')', NULL,
|
||||||
{
|
&ga_arg, TRUE, NULL, NULL,
|
||||||
ret = compile_expr1(arg, cctx, ppconst);
|
TRUE, NULL, NULL);
|
||||||
}
|
if (ret == OK && VIM_ISWHITE(
|
||||||
else
|
*after == ':' ? after[1] : *after))
|
||||||
{
|
{
|
||||||
// Not enough space in ppconst, flush constants.
|
if (*after == ':')
|
||||||
if (generate_ppconst(cctx, ppconst) == FAIL)
|
// Skip over type in "(arg): type".
|
||||||
return FAIL;
|
after = skip_type(skipwhite(after + 1), TRUE);
|
||||||
ret = compile_expr0(arg, cctx);
|
|
||||||
}
|
after = skipwhite(after);
|
||||||
*arg = skipwhite(*arg);
|
if (after[0] == '=' && after[1] == '>'
|
||||||
if (**arg == ')')
|
&& IS_WHITE_OR_NUL(after[2]))
|
||||||
++*arg;
|
{
|
||||||
else if (ret == OK)
|
ret = compile_lambda(arg, cctx);
|
||||||
{
|
break;
|
||||||
emsg(_(e_missing_close));
|
}
|
||||||
ret = FAIL;
|
}
|
||||||
|
|
||||||
|
// (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;
|
break;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user