summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2016-07-15 21:25:08 +0200
committerBram Moolenaar <Bram@vim.org>2016-07-15 21:25:08 +0200
commit069c1e7fa9f45a665064f7f2c17da84d6a48f544 (patch)
treeca31debf260df85e956d2d23fc63aa71f9767848
parent93431df9eb02f7cf3d7f2142bb1bef24c5f325b2 (diff)
downloadvim-git-069c1e7fa9f45a665064f7f2c17da84d6a48f544.tar.gz
patch 7.4.2044v7.4.2044
Problem: filter() and map() either require a string or defining a function. Solution: Support lambda, a short way to define a function that evaluates an expression. (Yasuhiro Matsumoto, Ken Takata)
-rw-r--r--runtime/doc/eval.txt65
-rw-r--r--src/Makefile1
-rw-r--r--src/eval.c335
-rw-r--r--src/testdir/test_alot.vim1
-rw-r--r--src/testdir/test_channel.vim56
-rw-r--r--src/testdir/test_lambda.vim48
-rw-r--r--src/version.c2
7 files changed, 428 insertions, 80 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index d8fe9c00e..6e4195d4e 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1,4 +1,4 @@
-*eval.txt* For Vim version 7.4. Last change: 2016 Jul 09
+*eval.txt* For Vim version 7.4. Last change: 2016 Jul 15
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -140,9 +140,10 @@ You will not get an error if you try to change the type of a variable.
1.2 Function references ~
*Funcref* *E695* *E718*
-A Funcref variable is obtained with the |function()| function. It can be used
-in an expression in the place of a function name, before the parenthesis
-around the arguments, to invoke the function it refers to. Example: >
+A Funcref variable is obtained with the |function()| function or created with
+the lambda expression |expr-lambda|. It can be used in an expression in the
+place of a function name, before the parenthesis around the arguments, to
+invoke the function it refers to. Example: >
:let Fn = function("MyFunc")
:echo Fn()
@@ -694,6 +695,7 @@ Expression syntax summary, from least to most significant:
@r contents of register 'r'
function(expr1, ...) function call
func{ti}on(expr1, ...) function call with curly braces
+ {args -> expr1} lambda expression
".." indicates that the operations in this level can be concatenated.
@@ -1207,6 +1209,42 @@ function(expr1, ...) function call
See below |functions|.
+lambda expression *expr-lambda* *lambda*
+-----------------
+{args -> expr1} lambda expression
+
+A lambda expression creates a new unnamed function which returns the result of
+evaluating |expr1|. Lambda expressions are differ from |user-functions| in
+the following ways:
+
+1. The body of the lambda expression is an |expr1| and not a sequence of |Ex|
+ commands.
+2. The prefix "a:" is optional for arguments. E.g.: >
+ :let F = {arg1, arg2 -> arg1 - arg2}
+ :echo F(5, 2)
+< 3
+
+The arguments are optional. Example: >
+ :let F = {-> 'error function'}
+ :echo F()
+< error function
+
+Examples for using a lambda expression with |sort()|, |map()| and |filter()|: >
+ :echo map([1, 2, 3], {idx, val -> val + 1})
+< [2, 3, 4] >
+ :echo sort([3,7,2,1,4], {a, b -> a - b})
+< [1, 2, 3, 4, 7]
+
+The lambda expression is also useful for Channel, Job and timer: >
+ :let timer = timer_start(500,
+ \ {-> execute("echo 'Handler called'", "")},
+ \ {'repeat': 3})
+< Handler called
+ Handler called
+ Handler called
+
+Note how execute() is used to execute an Ex command. That's ugly though.
+
==============================================================================
3. Internal variable *internal-variables* *E461*
@@ -3278,7 +3316,8 @@ execute({command} [, {silent}]) *execute()*
"silent" `:silent` used
"silent!" `:silent!` used
The default is 'silent'. Note that with "silent!", unlike
- `:redir`, error messages are dropped.
+ `:redir`, error messages are dropped. When using an external
+ command the screen may be messed up, use `system()` instead.
*E930*
It is not possible to use `:redir` anywhere in {command}.
@@ -7202,7 +7241,8 @@ system({expr} [, {input}]) *system()* *E677*
in a way |writefile()| does with {binary} set to "b" (i.e.
with a newline between each list item with newlines inside
list items converted to NULs).
- Pipes are not used.
+
+ Pipes are not used, the 'shelltemp' option is not used.
When prepended by |:silent| the shell will not be set to
cooked mode. This is meant to be used for commands that do
@@ -8204,10 +8244,10 @@ can be 0). "a:000" is set to a |List| that contains these arguments. Note
that "a:1" is the same as "a:000[0]".
*E742*
The a: scope and the variables in it cannot be changed, they are fixed.
-However, if a |List| or |Dictionary| is used, you can change their contents.
-Thus you can pass a |List| to a function and have the function add an item to
-it. If you want to make sure the function cannot change a |List| or
-|Dictionary| use |:lockvar|.
+However, if a composite type is used, such as |List| or |Dictionary| , you can
+change their contents. Thus you can pass a |List| to a function and have the
+function add an item to it. If you want to make sure the function cannot
+change a |List| or |Dictionary| use |:lockvar|.
When not using "...", the number of arguments in a function call must be equal
to the number of named arguments. When using "...", the number of arguments
@@ -8219,9 +8259,8 @@ until the matching |:endfunction|. It is allowed to define another function
inside a function body.
*local-variables*
-Inside a function variables can be used. These are local variables, which
-will disappear when the function returns. Global variables need to be
-accessed with "g:".
+Inside a function local variables can be used. These will disappear when the
+function returns. Global variables need to be accessed with "g:".
Example: >
:function Table(title, ...)
diff --git a/src/Makefile b/src/Makefile
index 021cf21ee..f6d60c3e8 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -2046,6 +2046,7 @@ test_arglist \
test_join \
test_json \
test_jumps \
+ test_lambda \
test_langmap \
test_largefile \
test_lispwords \
diff --git a/src/eval.c b/src/eval.c
index e29ba735a..fadd26fc7 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -457,6 +457,8 @@ static dict_T *dict_copy(dict_T *orig, int deep, int copyID);
static long dict_len(dict_T *d);
static char_u *dict2string(typval_T *tv, int copyID, int restore_copyID);
static int get_dict_tv(char_u **arg, typval_T *rettv, int evaluate);
+static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, int *varargs, int skip);
+static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate);
static char_u *echo_string_core(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID, int echo_style, int restore_copyID, int dict_val);
static char_u *echo_string(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID);
static char_u *string_quote(char_u *str, int function);
@@ -5261,9 +5263,12 @@ eval7(
break;
/*
+ * Lambda: {arg, arg -> expr}
* Dictionary: {key: val, key: val}
*/
- case '{': ret = get_dict_tv(arg, rettv, evaluate);
+ case '{': ret = get_lambda_tv(arg, rettv, evaluate);
+ if (ret == NOTDONE)
+ ret = get_dict_tv(arg, rettv, evaluate);
break;
/*
@@ -8110,6 +8115,202 @@ failret:
return OK;
}
+/* Get function arguments. */
+ static int
+get_function_args(
+ char_u **argp,
+ char_u endchar,
+ garray_T *newargs,
+ int *varargs,
+ int skip)
+{
+ int mustend = FALSE;
+ char_u *arg = *argp;
+ char_u *p = arg;
+ int c;
+ int i;
+
+ if (newargs != NULL)
+ ga_init2(newargs, (int)sizeof(char_u *), 3);
+
+ if (varargs != NULL)
+ *varargs = FALSE;
+
+ /*
+ * Isolate the arguments: "arg1, arg2, ...)"
+ */
+ while (*p != endchar)
+ {
+ if (p[0] == '.' && p[1] == '.' && p[2] == '.')
+ {
+ if (varargs != NULL)
+ *varargs = TRUE;
+ p += 3;
+ mustend = TRUE;
+ }
+ else
+ {
+ arg = p;
+ while (ASCII_ISALNUM(*p) || *p == '_')
+ ++p;
+ if (arg == p || isdigit(*arg)
+ || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0)
+ || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0))
+ {
+ if (!skip)
+ EMSG2(_("E125: Illegal argument: %s"), arg);
+ break;
+ }
+ if (newargs != NULL && ga_grow(newargs, 1) == FAIL)
+ return FAIL;
+ if (newargs != NULL)
+ {
+ c = *p;
+ *p = NUL;
+ arg = vim_strsave(arg);
+ if (arg == NULL)
+ goto err_ret;
+
+ /* Check for duplicate argument name. */
+ for (i = 0; i < newargs->ga_len; ++i)
+ if (STRCMP(((char_u **)(newargs->ga_data))[i], arg) == 0)
+ {
+ EMSG2(_("E853: Duplicate argument name: %s"), arg);
+ vim_free(arg);
+ goto err_ret;
+ }
+ ((char_u **)(newargs->ga_data))[newargs->ga_len] = arg;
+ newargs->ga_len++;
+
+ *p = c;
+ }
+ if (*p == ',')
+ ++p;
+ else
+ mustend = TRUE;
+ }
+ p = skipwhite(p);
+ if (mustend && *p != endchar)
+ {
+ if (!skip)
+ EMSG2(_(e_invarg2), *argp);
+ break;
+ }
+ }
+ ++p; /* skip the ')' */
+
+ *argp = p;
+ return OK;
+
+err_ret:
+ if (newargs != NULL)
+ ga_clear_strings(newargs);
+ return FAIL;
+}
+
+/*
+ * Parse a lambda expression and get a Funcref from "*arg".
+ * Return OK or FAIL. Returns NOTDONE for dict or {expr}.
+ */
+ static int
+get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
+{
+ garray_T newargs;
+ garray_T newlines;
+ ufunc_T *fp = NULL;
+ int varargs;
+ int ret;
+ char_u name[20];
+ char_u *start = skipwhite(*arg + 1);
+ char_u *s, *e;
+ static int lambda_no = 0;
+
+ ga_init(&newargs);
+ ga_init(&newlines);
+
+ /* First, check if this is a lambda expression. "->" must exists. */
+ ret = get_function_args(&start, '-', NULL, NULL, TRUE);
+ if (ret == FAIL || *start != '>')
+ return NOTDONE;
+
+ /* Parse the arguments again. */
+ *arg = skipwhite(*arg + 1);
+ ret = get_function_args(arg, '-', &newargs, &varargs, FALSE);
+ if (ret == FAIL || **arg != '>')
+ goto errret;
+
+ /* Get the start and the end of the expression. */
+ *arg = skipwhite(*arg + 1);
+ s = *arg;
+ ret = skip_expr(arg);
+ if (ret == FAIL)
+ goto errret;
+ e = *arg;
+ *arg = skipwhite(*arg);
+ if (**arg != '}')
+ goto errret;
+ ++*arg;
+
+ if (evaluate)
+ {
+ int len;
+ char_u *p;
+
+ fp = (ufunc_T *)alloc((unsigned)(sizeof(ufunc_T) + 20));
+ if (fp == NULL)
+ goto errret;
+
+ sprintf((char*)name, "<lambda>%d", ++lambda_no);
+
+ ga_init2(&newlines, (int)sizeof(char_u *), 1);
+ if (ga_grow(&newlines, 1) == FAIL)
+ goto errret;
+
+ /* Add "return " before the expression.
+ * TODO: Support multiple expressions. */
+ len = 7 + e - s + 1;
+ p = (char_u *)alloc(len);
+ if (p == NULL)
+ goto errret;
+ ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p;
+ STRCPY(p, "return ");
+ STRNCPY(p + 7, s, e - s);
+ p[7 + e - s] = NUL;
+
+ fp->uf_refcount = 1;
+ STRCPY(fp->uf_name, name);
+ hash_add(&func_hashtab, UF2HIKEY(fp));
+ fp->uf_args = newargs;
+ fp->uf_lines = newlines;
+
+#ifdef FEAT_PROFILE
+ fp->uf_tml_count = NULL;
+ fp->uf_tml_total = NULL;
+ fp->uf_tml_self = NULL;
+ fp->uf_profiling = FALSE;
+ if (prof_def_func())
+ func_do_profile(fp);
+#endif
+ fp->uf_varargs = TRUE;
+ fp->uf_flags = 0;
+ fp->uf_calls = 0;
+ fp->uf_script_ID = current_SID;
+
+ rettv->vval.v_string = vim_strsave(name);
+ rettv->v_type = VAR_FUNC;
+ }
+ else
+ ga_clear_strings(&newargs);
+
+ return OK;
+
+errret:
+ ga_clear_strings(&newargs);
+ ga_clear_strings(&newlines);
+ vim_free(fp);
+ return FAIL;
+}
+
static char *
get_var_special_name(int nr)
{
@@ -9321,7 +9522,8 @@ call_func(
call_user_func(fp, argcount, argvars, rettv,
firstline, lastline,
(fp->uf_flags & FC_DICT) ? selfdict : NULL);
- if (--fp->uf_calls <= 0 && isdigit(*fp->uf_name)
+ if (--fp->uf_calls <= 0 && (isdigit(*fp->uf_name)
+ || STRNCMP(fp->uf_name, "<lambda>", 8) == 0)
&& fp->uf_refcount <= 0)
/* Function was unreferenced while being used, free it
* now. */
@@ -24275,7 +24477,6 @@ find_option_end(char_u **arg, int *opt_flags)
ex_function(exarg_T *eap)
{
char_u *theline;
- int i;
int j;
int c;
int saved_did_emsg;
@@ -24287,7 +24488,6 @@ ex_function(exarg_T *eap)
garray_T newargs;
garray_T newlines;
int varargs = FALSE;
- int mustend = FALSE;
int flags = 0;
ufunc_T *fp;
int indent;
@@ -24468,7 +24668,6 @@ ex_function(exarg_T *eap)
}
p = skipwhite(p + 1);
- ga_init2(&newargs, (int)sizeof(char_u *), 3);
ga_init2(&newlines, (int)sizeof(char_u *), 3);
if (!eap->skip)
@@ -24498,66 +24697,8 @@ ex_function(exarg_T *eap)
EMSG(_("E862: Cannot use g: here"));
}
- /*
- * Isolate the arguments: "arg1, arg2, ...)"
- */
- while (*p != ')')
- {
- if (p[0] == '.' && p[1] == '.' && p[2] == '.')
- {
- varargs = TRUE;
- p += 3;
- mustend = TRUE;
- }
- else
- {
- arg = p;
- while (ASCII_ISALNUM(*p) || *p == '_')
- ++p;
- if (arg == p || isdigit(*arg)
- || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0)
- || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0))
- {
- if (!eap->skip)
- EMSG2(_("E125: Illegal argument: %s"), arg);
- break;
- }
- if (ga_grow(&newargs, 1) == FAIL)
- goto erret;
- c = *p;
- *p = NUL;
- arg = vim_strsave(arg);
- if (arg == NULL)
- goto erret;
-
- /* Check for duplicate argument name. */
- for (i = 0; i < newargs.ga_len; ++i)
- if (STRCMP(((char_u **)(newargs.ga_data))[i], arg) == 0)
- {
- EMSG2(_("E853: Duplicate argument name: %s"), arg);
- vim_free(arg);
- goto erret;
- }
-
- ((char_u **)(newargs.ga_data))[newargs.ga_len] = arg;
- *p = c;
- newargs.ga_len++;
- if (*p == ',')
- ++p;
- else
- mustend = TRUE;
- }
- p = skipwhite(p);
- if (mustend && *p != ')')
- {
- if (!eap->skip)
- EMSG2(_(e_invarg2), eap->arg);
- break;
- }
- }
- if (*p != ')')
- goto erret;
- ++p; /* skip the ')' */
+ if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL)
+ goto errret_2;
/* find extra arguments "range", "dict" and "abort" */
for (;;)
@@ -24926,6 +25067,7 @@ ex_function(exarg_T *eap)
erret:
ga_clear_strings(&newargs);
+errret_2:
ga_clear_strings(&newlines);
ret_free:
vim_free(skip_until);
@@ -25740,7 +25882,9 @@ func_unref(char_u *name)
{
ufunc_T *fp;
- if (name != NULL && isdigit(*name))
+ if (name == NULL)
+ return;
+ else if (isdigit(*name))
{
fp = find_func(name);
if (fp == NULL)
@@ -25758,6 +25902,18 @@ func_unref(char_u *name)
func_free(fp);
}
}
+ else if (STRNCMP(name, "<lambda>", 8) == 0)
+ {
+ /* fail silently, when lambda function isn't found. */
+ fp = find_func(name);
+ if (fp != NULL && --fp->uf_refcount <= 0)
+ {
+ /* Only delete it when it's not being used. Otherwise it's done
+ * when "uf_calls" becomes zero. */
+ if (fp->uf_calls == 0)
+ func_free(fp);
+ }
+ }
}
/*
@@ -25768,7 +25924,9 @@ func_ref(char_u *name)
{
ufunc_T *fp;
- if (name != NULL && isdigit(*name))
+ if (name == NULL)
+ return;
+ else if (isdigit(*name))
{
fp = find_func(name);
if (fp == NULL)
@@ -25776,6 +25934,13 @@ func_ref(char_u *name)
else
++fp->uf_refcount;
}
+ else if (STRNCMP(name, "<lambda>", 8) == 0)
+ {
+ /* fail silently, when lambda function isn't found. */
+ fp = find_func(name);
+ if (fp != NULL)
+ ++fp->uf_refcount;
+ }
}
/*
@@ -25801,6 +25966,7 @@ call_user_func(
int fixvar_idx = 0; /* index in fixvar[] */
int i;
int ai;
+ int islambda = FALSE;
char_u numbuf[NUMBUFLEN];
char_u *name;
size_t len;
@@ -25834,6 +26000,9 @@ call_user_func(
fc->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0);
fc->dbg_tick = debug_tick;
+ if (STRNCMP(fp->uf_name, "<lambda>", 8) == 0)
+ islambda = TRUE;
+
/*
* Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables
* with names up to VAR_SHORT_LEN long. This avoids having to alloc/free
@@ -25891,10 +26060,17 @@ call_user_func(
(varnumber_T)lastline);
for (i = 0; i < argcount; ++i)
{
+ int addlocal = FALSE;
+ dictitem_T *v2;
+
ai = i - fp->uf_args.ga_len;
if (ai < 0)
+ {
/* named argument a:name */
name = FUNCARG(fp, i);
+ if (islambda)
+ addlocal = TRUE;
+ }
else
{
/* "..." argument a:1, a:2, etc. */
@@ -25905,6 +26081,9 @@ call_user_func(
{
v = &fc->fixvar[fixvar_idx++].var;
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
+
+ if (addlocal)
+ v2 = v;
}
else
{
@@ -25913,6 +26092,18 @@ call_user_func(
if (v == NULL)
break;
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC;
+
+ if (addlocal)
+ {
+ v2 = (dictitem_T *)alloc((unsigned)(sizeof(dictitem_T)
+ + STRLEN(name)));
+ if (v2 == NULL)
+ {
+ vim_free(v);
+ break;
+ }
+ v2->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC;
+ }
}
STRCPY(v->di_key, name);
hash_add(&fc->l_avars.dv_hashtab, DI2HIKEY(v));
@@ -25922,6 +26113,16 @@ call_user_func(
v->di_tv = argvars[i];
v->di_tv.v_lock = VAR_FIXED;
+ /* Named arguments can be accessed without the "a:" prefix in lambda
+ * expressions. Add to the l: dict. */
+ if (addlocal)
+ {
+ STRCPY(v2->di_key, name);
+ copy_tv(&v->di_tv, &v2->di_tv);
+ v2->di_tv.v_lock = VAR_FIXED;
+ hash_add(&fc->l_vars.dv_hashtab, DI2HIKEY(v2));
+ }
+
if (ai >= 0 && ai < MAX_FUNC_ARGS)
{
list_append(&fc->l_varlist, &fc->l_listitems[ai]);
diff --git a/src/testdir/test_alot.vim b/src/testdir/test_alot.vim
index 3074a5c0b..e9c84398e 100644
--- a/src/testdir/test_alot.vim
+++ b/src/testdir/test_alot.vim
@@ -19,6 +19,7 @@ source test_goto.vim
source test_help_tagjump.vim
source test_join.vim
source test_jumps.vim
+source test_lambda.vim
source test_lispwords.vim
source test_matchstrpos.vim
source test_menu.vim
diff --git a/src/testdir/test_channel.vim b/src/testdir/test_channel.vim
index b86ca13ca..ea7abd4b3 100644
--- a/src/testdir/test_channel.vim
+++ b/src/testdir/test_channel.vim
@@ -95,6 +95,18 @@ func Ch_communicate(port)
endif
call assert_equal('got it', g:Ch_responseMsg)
+ " Using lambda.
+ let g:Ch_responseMsg = ''
+ call ch_sendexpr(handle, 'hello!', {'callback': {a, b -> Ch_requestHandler(a, b)}})
+ call WaitFor('exists("g:Ch_responseHandle")')
+ if !exists('g:Ch_responseHandle')
+ call assert_false(1, 'g:Ch_responseHandle was not set')
+ else
+ call assert_equal(handle, g:Ch_responseHandle)
+ unlet g:Ch_responseHandle
+ endif
+ call assert_equal('got it', g:Ch_responseMsg)
+
" Collect garbage, tests that our handle isn't collected.
call test_garbagecollect_now()
@@ -1069,6 +1081,32 @@ func Test_read_in_close_cb()
endtry
endfunc
+func Test_out_cb_lambda()
+ if !has('job')
+ return
+ endif
+ call ch_log('Test_out_cb_lambda()')
+
+ let job = job_start(s:python . " test_channel_pipe.py",
+ \ {'out_cb': {ch, msg -> execute("let g:Ch_outmsg = 'lambda: ' . msg")},
+ \ 'out_mode': 'json',
+ \ 'err_cb': {ch, msg -> execute(":let g:Ch_errmsg = 'lambda: ' . msg")},
+ \ 'err_mode': 'json'})
+ call assert_equal("run", job_status(job))
+ try
+ let g:Ch_outmsg = ''
+ let g:Ch_errmsg = ''
+ call ch_sendraw(job, "echo [0, \"hello\"]\n")
+ call ch_sendraw(job, "echoerr [0, \"there\"]\n")
+ call WaitFor('g:Ch_outmsg != ""')
+ call assert_equal("lambda: hello", g:Ch_outmsg)
+ call WaitFor('g:Ch_errmsg != ""')
+ call assert_equal("lambda: there", g:Ch_errmsg)
+ finally
+ call job_stop(job)
+ endtry
+endfunc
+
""""""""""
let g:Ch_unletResponse = ''
@@ -1285,6 +1323,24 @@ func Test_collapse_buffers()
bwipe!
endfunc
+function Ch_test_close_lambda(port)
+ let handle = ch_open('localhost:' . a:port, s:chopt)
+ if ch_status(handle) == "fail"
+ call assert_false(1, "Can't open channel")
+ return
+ endif
+ let g:Ch_close_ret = ''
+ call ch_setoptions(handle, {'close_cb': {ch -> execute("let g:Ch_close_ret = 'closed'")}})
+
+ call assert_equal('', ch_evalexpr(handle, 'close me'))
+ call WaitFor('"closed" == g:Ch_close_ret')
+ call assert_equal('closed', g:Ch_close_ret)
+endfunc
+
+func Test_close_lambda()
+ call ch_log('Test_close_lambda()')
+ call s:run_server('Ch_test_close_lambda')
+endfunc
" Uncomment this to see what happens, output is in src/testdir/channellog.
call ch_logfile('channellog', 'w')
diff --git a/src/testdir/test_lambda.vim b/src/testdir/test_lambda.vim
new file mode 100644
index 000000000..9e9979b0c
--- /dev/null
+++ b/src/testdir/test_lambda.vim
@@ -0,0 +1,48 @@
+function! Test_lambda_with_filter()
+ let s:x = 2
+ call assert_equal([2, 3], filter([1, 2, 3], {i, v -> v >= s:x}))
+endfunction
+
+function! Test_lambda_with_map()
+ let s:x = 1
+ call assert_equal([2, 3, 4], map([1, 2, 3], {i, v -> v + s:x}))
+endfunction
+
+function! Test_lambda_with_sort()
+ call assert_equal([1, 2, 3, 4, 7], sort([3,7,2,1,4], {a, b -> a - b}))
+endfunction
+
+function! Test_lambda_with_timer()
+ if !has('timers')
+ return
+ endif
+
+ let s:n = 0
+ let s:timer_id = 0
+ function! s:Foo()
+ "let n = 0
+ let s:timer_id = timer_start(50, {-> execute("let s:n += 1 | echo s:n")}, {"repeat": -1})
+ endfunction
+
+ call s:Foo()
+ sleep 200ms
+ " do not collect lambda
+ call test_garbagecollect_now()
+ let m = s:n
+ sleep 200ms
+ call timer_stop(s:timer_id)
+ call assert_true(m > 1)
+ call assert_true(s:n > m + 1)
+ call assert_true(s:n < 9)
+endfunction
+
+function! Test_lambda_with_partial()
+ let l:Cb = function({... -> ['zero', a:1, a:2, a:3]}, ['one', 'two'])
+ call assert_equal(['zero', 'one', 'two', 'three'], l:Cb('three'))
+endfunction
+
+function Test_lambda_fails()
+ call assert_equal(3, {a, b -> a + b}(1, 2))
+ call assert_fails('echo {a, a -> a + a}(1, 2)', 'E15:')
+ call assert_fails('echo {a, b -> a + b)}(1, 2)', 'E15:')
+endfunc
diff --git a/src/version.c b/src/version.c
index 9acfbe8d4..745bfd223 100644
--- a/src/version.c
+++ b/src/version.c
@@ -759,6 +759,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 2044,
+/**/
2043,
/**/
2042,