Skip to content

Commit 10ce39a

Browse files
committed
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)
1 parent 1e96d9b commit 10ce39a

6 files changed

Lines changed: 132 additions & 7 deletions

File tree

runtime/doc/eval.txt

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1240,6 +1240,7 @@ function returns: >
12401240
:let Bar = Foo(4)
12411241
:echo Bar(6)
12421242
< 5
1243+
See also |:func-closure|.
12431244

12441245
Examples for using a lambda expression with |sort()|, |map()| and |filter()|: >
12451246
:echo map([1, 2, 3], {idx, val -> val + 1})
@@ -8217,7 +8218,7 @@ last defined. Example: >
82178218
See |:verbose-cmd| for more information.
82188219

82198220
*E124* *E125* *E853* *E884*
8220-
:fu[nction][!] {name}([arguments]) [range] [abort] [dict]
8221+
:fu[nction][!] {name}([arguments]) [range] [abort] [dict] [closure]
82218222
Define a new function by the name {name}. The name
82228223
must be made of alphanumeric characters and '_', and
82238224
must start with a capital or "s:" (see above). Note
@@ -8260,6 +8261,28 @@ See |:verbose-cmd| for more information.
82608261
be invoked through an entry in a |Dictionary|. The
82618262
local variable "self" will then be set to the
82628263
dictionary. See |Dictionary-function|.
8264+
*:func-closure* *E932*
8265+
When the [closure] argument is added, the function
8266+
can access variables and arguments from the outer
8267+
scope. This is usually called a closure. In this
8268+
example Bar() uses "x" from the scope of Foo(). It
8269+
remains referenced even after Foo() returns: >
8270+
:function! Foo()
8271+
: let x = 0
8272+
: function! Bar() closure
8273+
: let x += 1
8274+
: return x
8275+
: endfunction
8276+
: return function('Bar')
8277+
:endfunction
8278+
8279+
:let F = Foo()
8280+
:echo F()
8281+
< 1 >
8282+
:echo F()
8283+
< 2 >
8284+
:echo F()
8285+
< 3
82638286

82648287
*function-search-undo*
82658288
The last used search pattern and the redo command "."

src/eval.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2837,7 +2837,9 @@ do_unlet(char_u *name, int forceit)
28372837
}
28382838
}
28392839
hi = hash_find(ht, varname);
2840-
if (!HASHITEM_EMPTY(hi))
2840+
if (HASHITEM_EMPTY(hi))
2841+
hi = find_hi_in_scoped_ht(name, &varname, &ht);
2842+
if (hi != NULL && !HASHITEM_EMPTY(hi))
28412843
{
28422844
di = HI2DI(hi);
28432845
if (var_check_fixed(di->di_flags, name, FALSE)

src/proto/userfunc.pro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ void *clear_current_funccal(void);
4646
void restore_current_funccal(void *f);
4747
void list_func_vars(int *first);
4848
dict_T *get_current_funccal_dict(hashtab_T *ht);
49+
hashitem_T *find_hi_in_scoped_ht(char_u *name, char_u **varname, hashtab_T **pht);
4950
dictitem_T *find_var_in_scoped_ht(char_u *name, char_u **varname, int no_autoload);
5051
int set_ref_in_previous_funccal(int copyID);
5152
int set_ref_in_call_stack(int copyID);

src/testdir/test_lambda.vim

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
" Test for lambda and closure
2+
13
function! Test_lambda_with_filter()
24
let s:x = 2
35
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_variable()
100102
call assert_equal('no', l:F[1]())
101103
endfunction
102104

103-
function! Test_lambda_closure()
105+
function! Test_lambda_closure_counter()
104106
function! s:foo()
105107
let x = 0
106108
return {-> [execute("let x += 1"), x][-1]}
@@ -209,3 +211,35 @@ function! Test_lambda_combination()
209211
let Fact = {f -> {x -> x == 0 ? 1 : x * f(x - 1)}}
210212
call assert_equal(120, Z(Fact)(5))
211213
endfunction
214+
215+
function! Test_closure_counter()
216+
function! s:foo()
217+
let x = 0
218+
function! s:bar() closure
219+
let x += 1
220+
return x
221+
endfunction
222+
return function('s:bar')
223+
endfunction
224+
225+
let l:F = s:foo()
226+
call test_garbagecollect_now()
227+
call assert_equal(1, l:F())
228+
call assert_equal(2, l:F())
229+
call assert_equal(3, l:F())
230+
call assert_equal(4, l:F())
231+
endfunction
232+
233+
function! Test_closure_unlet()
234+
function! s:foo()
235+
let x = 1
236+
function! s:bar() closure
237+
unlet x
238+
endfunction
239+
call s:bar()
240+
return l:
241+
endfunction
242+
243+
call assert_false(has_key(s:foo(), 'x'))
244+
call test_garbagecollect_now()
245+
endfunction

src/userfunc.c

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ struct ufunc
5959
#define FC_ABORT 1 /* abort function on error */
6060
#define FC_RANGE 2 /* function accepts range */
6161
#define FC_DICT 4 /* Dict function, uses "self" */
62+
#define FC_CLOSURE 8 /* closure, uses outer scope variables */
6263

6364
/* From user function to hashitem and back. */
6465
#define UF2HIKEY(fp) ((fp)->uf_name)
@@ -312,7 +313,7 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
312313

313314
if (evaluate)
314315
{
315-
int len;
316+
int len, flags = 0;
316317
char_u *p;
317318

318319
sprintf((char*)name, "<lambda>%d", ++lambda_no);
@@ -341,6 +342,7 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
341342
fp->uf_lines = newlines;
342343
if (current_funccal != NULL && eval_lavars)
343344
{
345+
flags |= FC_CLOSURE;
344346
fp->uf_scoped = current_funccal;
345347
current_funccal->fc_refcount++;
346348
if (ga_grow(&current_funccal->fc_funcs, 1) == FAIL)
@@ -361,7 +363,7 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
361363
func_do_profile(fp);
362364
#endif
363365
fp->uf_varargs = TRUE;
364-
fp->uf_flags = 0;
366+
fp->uf_flags = flags;
365367
fp->uf_calls = 0;
366368
fp->uf_script_ID = current_SID;
367369

@@ -1487,6 +1489,8 @@ list_func_head(ufunc_T *fp, int indent)
14871489
MSG_PUTS(" range");
14881490
if (fp->uf_flags & FC_DICT)
14891491
MSG_PUTS(" dict");
1492+
if (fp->uf_flags & FC_CLOSURE)
1493+
MSG_PUTS(" closure");
14901494
msg_clr_eos();
14911495
if (p_verbose > 0)
14921496
last_set_msg(fp->uf_script_ID);
@@ -1948,7 +1952,7 @@ ex_function(exarg_T *eap)
19481952
if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL)
19491953
goto errret_2;
19501954

1951-
/* find extra arguments "range", "dict" and "abort" */
1955+
/* find extra arguments "range", "dict", "abort" and "closure" */
19521956
for (;;)
19531957
{
19541958
p = skipwhite(p);
@@ -1967,6 +1971,11 @@ ex_function(exarg_T *eap)
19671971
flags |= FC_ABORT;
19681972
p += 5;
19691973
}
1974+
else if (STRNCMP(p, "closure", 7) == 0)
1975+
{
1976+
flags |= FC_CLOSURE;
1977+
p += 7;
1978+
}
19701979
else
19711980
break;
19721981
}
@@ -2299,7 +2308,25 @@ ex_function(exarg_T *eap)
22992308
}
23002309
fp->uf_args = newargs;
23012310
fp->uf_lines = newlines;
2302-
fp->uf_scoped = NULL;
2311+
if ((flags & FC_CLOSURE) != 0)
2312+
{
2313+
if (current_funccal == NULL)
2314+
{
2315+
emsg_funcname(N_("E932 Closure function should not be at top level: %s"),
2316+
name);
2317+
goto erret;
2318+
}
2319+
fp->uf_scoped = current_funccal;
2320+
current_funccal->fc_refcount++;
2321+
if (ga_grow(&current_funccal->fc_funcs, 1) == FAIL)
2322+
goto erret;
2323+
((ufunc_T **)current_funccal->fc_funcs.ga_data)
2324+
[current_funccal->fc_funcs.ga_len++] = fp;
2325+
func_ref(current_funccal->func->uf_name);
2326+
}
2327+
else
2328+
fp->uf_scoped = NULL;
2329+
23032330
#ifdef FEAT_PROFILE
23042331
fp->uf_tml_count = NULL;
23052332
fp->uf_tml_total = NULL;
@@ -3535,6 +3562,42 @@ get_current_funccal_dict(hashtab_T *ht)
35353562
return NULL;
35363563
}
35373564

3565+
/*
3566+
* Search hashitem in parent scope.
3567+
*/
3568+
hashitem_T *
3569+
find_hi_in_scoped_ht(char_u *name, char_u **varname, hashtab_T **pht)
3570+
{
3571+
funccall_T *old_current_funccal = current_funccal;
3572+
hashtab_T *ht;
3573+
hashitem_T *hi = NULL;
3574+
3575+
if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL)
3576+
return NULL;
3577+
3578+
/* Search in parent scope which is possible to reference from lambda */
3579+
current_funccal = current_funccal->func->uf_scoped;
3580+
while (current_funccal)
3581+
{
3582+
ht = find_var_ht(name, varname);
3583+
if (ht != NULL && **varname != NUL)
3584+
{
3585+
hi = hash_find(ht, *varname);
3586+
if (!HASHITEM_EMPTY(hi))
3587+
{
3588+
*pht = ht;
3589+
break;
3590+
}
3591+
}
3592+
if (current_funccal == current_funccal->func->uf_scoped)
3593+
break;
3594+
current_funccal = current_funccal->func->uf_scoped;
3595+
}
3596+
current_funccal = old_current_funccal;
3597+
3598+
return hi;
3599+
}
3600+
35383601
/*
35393602
* Search variable in parent scope.
35403603
*/

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,8 @@ static char *(features[]) =
758758

759759
static int included_patches[] =
760760
{ /* Add new patch number below this line */
761+
/**/
762+
2120,
761763
/**/
762764
2119,
763765
/**/

0 commit comments

Comments
 (0)