# HG changeset patch # User Christian Brabandt # Date 1469825106 -7200 # Node ID 2ea935bdd1a191aebc1492e8899f01b511333db9 # Parent a8421d84233144f851aa41c4dc7424f35f4c3f1f commit https://github.com/vim/vim/commit/10ce39a0d52272a3dfff2feb8c631529f29e6740 Author: Bram Moolenaar 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) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- 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 "." diff --git a/src/eval.c b/src/eval.c --- 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) diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro --- 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); diff --git a/src/testdir/test_lambda.vim b/src/testdir/test_lambda.vim --- 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 diff --git a/src/userfunc.c b/src/userfunc.c --- 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, "%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(¤t_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(¤t_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 * diff --git a/src/version.c b/src/version.c --- 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,