changeset 9688:2ea935bdd1a1 v7.4.2120

commit https://github.com/vim/vim/commit/10ce39a0d52272a3dfff2feb8c631529f29e6740 Author: Bram Moolenaar <Bram@vim.org> Date: Fri Jul 29 22:37:06 2016 +0200 patch 7.4.2120 Problem: User defined functions can't be a closure. Solution: Add the "closure" argument. Allow using :unlet on a bound variable. (Yasuhiro Matsumoto, Ken Takata)
author Christian Brabandt <cb@256bit.org>
date Fri, 29 Jul 2016 22:45:06 +0200
parents a8421d842331
children 75466263dfbd
files runtime/doc/eval.txt src/eval.c src/proto/userfunc.pro src/testdir/test_lambda.vim src/userfunc.c src/version.c
diffstat 6 files changed, 132 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1240,6 +1240,7 @@ function returns: >
 	:let Bar = Foo(4)
 	:echo Bar(6)
 <	5
+See also |:func-closure|.
 
 Examples for using a lambda expression with |sort()|, |map()| and |filter()|: >
 	:echo map([1, 2, 3], {idx, val -> val + 1})
@@ -8217,7 +8218,7 @@ last defined. Example: >
 See |:verbose-cmd| for more information.
 
 						*E124* *E125* *E853* *E884*
-:fu[nction][!] {name}([arguments]) [range] [abort] [dict]
+:fu[nction][!] {name}([arguments]) [range] [abort] [dict] [closure]
 			Define a new function by the name {name}.  The name
 			must be made of alphanumeric characters and '_', and
 			must start with a capital or "s:" (see above).  Note
@@ -8260,6 +8261,28 @@ See |:verbose-cmd| for more information.
 			be invoked through an entry in a |Dictionary|.	The
 			local variable "self" will then be set to the
 			dictionary.  See |Dictionary-function|.
+						*:func-closure* *E932*
+			When the [closure] argument is added, the function
+			can access variables and arguments from the outer
+			scope.  This is usually called a closure.  In this
+			example Bar() uses "x" from the scope of Foo().  It
+			remains referenced even after Foo() returns: >
+				:function! Foo()
+				:  let x = 0
+				:  function! Bar() closure
+				:    let x += 1
+				:    return x
+				:  endfunction
+				:  return function('Bar')
+				:endfunction
+
+				:let F = Foo()
+				:echo F()
+<				1 >
+				:echo F()
+<				2 >
+				:echo F()
+<				3
 
 						*function-search-undo*
 			The last used search pattern and the redo command "."
--- a/src/eval.c
+++ b/src/eval.c
@@ -2837,7 +2837,9 @@ do_unlet(char_u *name, int forceit)
 	    }
 	}
 	hi = hash_find(ht, varname);
-	if (!HASHITEM_EMPTY(hi))
+	if (HASHITEM_EMPTY(hi))
+	    hi = find_hi_in_scoped_ht(name, &varname, &ht);
+	if (hi != NULL && !HASHITEM_EMPTY(hi))
 	{
 	    di = HI2DI(hi);
 	    if (var_check_fixed(di->di_flags, name, FALSE)
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -46,6 +46,7 @@ void *clear_current_funccal(void);
 void restore_current_funccal(void *f);
 void list_func_vars(int *first);
 dict_T *get_current_funccal_dict(hashtab_T *ht);
+hashitem_T *find_hi_in_scoped_ht(char_u *name, char_u **varname, hashtab_T **pht);
 dictitem_T *find_var_in_scoped_ht(char_u *name, char_u **varname, int no_autoload);
 int set_ref_in_previous_funccal(int copyID);
 int set_ref_in_call_stack(int copyID);
--- a/src/testdir/test_lambda.vim
+++ b/src/testdir/test_lambda.vim
@@ -1,3 +1,5 @@
+" Test for lambda and closure
+
 function! Test_lambda_with_filter()
   let s:x = 2
   call assert_equal([2, 3], filter([1, 2, 3], {i, v -> v >= s:x}))
@@ -100,7 +102,7 @@ function! Test_lambda_do_not_share_local
   call assert_equal('no', l:F[1]())
 endfunction
 
-function! Test_lambda_closure()
+function! Test_lambda_closure_counter()
   function! s:foo()
     let x = 0
     return {-> [execute("let x += 1"), x][-1]}
@@ -209,3 +211,35 @@ function! Test_lambda_combination()
   let Fact = {f -> {x -> x == 0 ? 1 : x * f(x - 1)}}
   call assert_equal(120, Z(Fact)(5))
 endfunction
+
+function! Test_closure_counter()
+  function! s:foo()
+    let x = 0
+    function! s:bar() closure
+      let x += 1
+      return x
+    endfunction
+    return function('s:bar')
+  endfunction
+
+  let l:F = s:foo()
+  call test_garbagecollect_now()
+  call assert_equal(1, l:F())
+  call assert_equal(2, l:F())
+  call assert_equal(3, l:F())
+  call assert_equal(4, l:F())
+endfunction
+
+function! Test_closure_unlet()
+  function! s:foo()
+    let x = 1
+    function! s:bar() closure
+      unlet x
+    endfunction
+    call s:bar()
+    return l:
+  endfunction
+
+  call assert_false(has_key(s:foo(), 'x'))
+  call test_garbagecollect_now()
+endfunction
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -59,6 +59,7 @@ struct ufunc
 #define FC_ABORT    1		/* abort function on error */
 #define FC_RANGE    2		/* function accepts range */
 #define FC_DICT	    4		/* Dict function, uses "self" */
+#define FC_CLOSURE  8		/* closure, uses outer scope variables */
 
 /* From user function to hashitem and back. */
 #define UF2HIKEY(fp) ((fp)->uf_name)
@@ -312,7 +313,7 @@ get_lambda_tv(char_u **arg, typval_T *re
 
     if (evaluate)
     {
-	int	len;
+	int	len, flags = 0;
 	char_u	*p;
 
 	sprintf((char*)name, "<lambda>%d", ++lambda_no);
@@ -341,6 +342,7 @@ get_lambda_tv(char_u **arg, typval_T *re
 	fp->uf_lines = newlines;
 	if (current_funccal != NULL && eval_lavars)
 	{
+	    flags |= FC_CLOSURE;
 	    fp->uf_scoped = current_funccal;
 	    current_funccal->fc_refcount++;
 	    if (ga_grow(&current_funccal->fc_funcs, 1) == FAIL)
@@ -361,7 +363,7 @@ get_lambda_tv(char_u **arg, typval_T *re
 	    func_do_profile(fp);
 #endif
 	fp->uf_varargs = TRUE;
-	fp->uf_flags = 0;
+	fp->uf_flags = flags;
 	fp->uf_calls = 0;
 	fp->uf_script_ID = current_SID;
 
@@ -1487,6 +1489,8 @@ list_func_head(ufunc_T *fp, int indent)
 	MSG_PUTS(" range");
     if (fp->uf_flags & FC_DICT)
 	MSG_PUTS(" dict");
+    if (fp->uf_flags & FC_CLOSURE)
+	MSG_PUTS(" closure");
     msg_clr_eos();
     if (p_verbose > 0)
 	last_set_msg(fp->uf_script_ID);
@@ -1948,7 +1952,7 @@ ex_function(exarg_T *eap)
     if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL)
 	goto errret_2;
 
-    /* find extra arguments "range", "dict" and "abort" */
+    /* find extra arguments "range", "dict", "abort" and "closure" */
     for (;;)
     {
 	p = skipwhite(p);
@@ -1967,6 +1971,11 @@ ex_function(exarg_T *eap)
 	    flags |= FC_ABORT;
 	    p += 5;
 	}
+	else if (STRNCMP(p, "closure", 7) == 0)
+	{
+	    flags |= FC_CLOSURE;
+	    p += 7;
+	}
 	else
 	    break;
     }
@@ -2299,7 +2308,25 @@ ex_function(exarg_T *eap)
     }
     fp->uf_args = newargs;
     fp->uf_lines = newlines;
-    fp->uf_scoped = NULL;
+    if ((flags & FC_CLOSURE) != 0)
+    {
+	if (current_funccal == NULL)
+	{
+	    emsg_funcname(N_("E932 Closure function should not be at top level: %s"),
+		    name);
+	    goto erret;
+	}
+	fp->uf_scoped = current_funccal;
+	current_funccal->fc_refcount++;
+	if (ga_grow(&current_funccal->fc_funcs, 1) == FAIL)
+	    goto erret;
+	((ufunc_T **)current_funccal->fc_funcs.ga_data)
+				[current_funccal->fc_funcs.ga_len++] = fp;
+	func_ref(current_funccal->func->uf_name);
+    }
+    else
+	fp->uf_scoped = NULL;
+
 #ifdef FEAT_PROFILE
     fp->uf_tml_count = NULL;
     fp->uf_tml_total = NULL;
@@ -3536,6 +3563,42 @@ get_current_funccal_dict(hashtab_T *ht)
 }
 
 /*
+ * Search hashitem in parent scope.
+ */
+    hashitem_T *
+find_hi_in_scoped_ht(char_u *name, char_u **varname, hashtab_T **pht)
+{
+    funccall_T	*old_current_funccal = current_funccal;
+    hashtab_T	*ht;
+    hashitem_T	*hi = NULL;
+
+    if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL)
+      return NULL;
+
+    /* Search in parent scope which is possible to reference from lambda */
+    current_funccal = current_funccal->func->uf_scoped;
+    while (current_funccal)
+    {
+      ht = find_var_ht(name, varname);
+      if (ht != NULL && **varname != NUL)
+      {
+	  hi = hash_find(ht, *varname);
+	  if (!HASHITEM_EMPTY(hi))
+	  {
+	      *pht = ht;
+	      break;
+	  }
+      }
+      if (current_funccal == current_funccal->func->uf_scoped)
+	  break;
+      current_funccal = current_funccal->func->uf_scoped;
+    }
+    current_funccal = old_current_funccal;
+
+    return hi;
+}
+
+/*
  * Search variable in parent scope.
  */
     dictitem_T *
--- 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 */
 /**/
+    2120,
+/**/
     2119,
 /**/
     2118,