changeset 9527:e8b3db8e2d30 v7.4.2044

commit https://github.com/vim/vim/commit/069c1e7fa9f45a665064f7f2c17da84d6a48f544 Author: Bram Moolenaar <Bram@vim.org> Date: Fri Jul 15 21:25:08 2016 +0200 patch 7.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)
author Christian Brabandt <cb@256bit.org>
date Fri, 15 Jul 2016 21:30:07 +0200
parents 911a70945374
children f4a706d113fa
files runtime/doc/eval.txt src/Makefile src/eval.c src/testdir/test_alot.vim src/testdir/test_channel.vim src/testdir/test_lambda.vim src/version.c
diffstat 7 files changed, 428 insertions(+), 80 deletions(-) [+]
line wrap: on
line diff
--- 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 
 
 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
 	@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}])					*exe
 			"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()*
 		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| t
 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 i
 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, ...)
--- 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 \
--- a/src/eval.c
+++ b/src/eval.c
@@ -457,6 +457,8 @@ static dict_T *dict_copy(dict_T *orig, i
 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_f
 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]);
--- 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
--- 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')
new file mode 100644
--- /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
--- 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,