Skip to content

Commit 1e96d9b

Browse files
committed
patch 7.4.2119
Problem: Closures are not supported. Solution: Capture variables in lambdas from the outer scope. (Yasuhiro Matsumoto, Ken Takata)
1 parent 83a2a80 commit 1e96d9b

9 files changed

Lines changed: 450 additions & 47 deletions

File tree

runtime/doc/eval.txt

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*eval.txt* For Vim version 7.4. Last change: 2016 Jul 24
1+
*eval.txt* For Vim version 7.4. Last change: 2016 Jul 29
22

33

44
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -40,7 +40,7 @@ done, the features in this document are not available. See |+eval| and
4040
There are nine types of variables:
4141

4242
Number A 32 or 64 bit signed number. |expr-number| *Number*
43-
64-bit Number is available only when compiled with the
43+
64-bit Numbers are available only when compiled with the
4444
|+num64| feature.
4545
Examples: -123 0x10 0177
4646

@@ -1219,7 +1219,7 @@ the following ways:
12191219

12201220
1. The body of the lambda expression is an |expr1| and not a sequence of |Ex|
12211221
commands.
1222-
2. The prefix "a:" is optional for arguments. E.g.: >
1222+
2. The prefix "a:" should not be used for arguments. E.g.: >
12231223
:let F = {arg1, arg2 -> arg1 - arg2}
12241224
:echo F(5, 2)
12251225
< 3
@@ -1228,6 +1228,18 @@ The arguments are optional. Example: >
12281228
:let F = {-> 'error function'}
12291229
:echo F()
12301230
< error function
1231+
*closure*
1232+
Lambda expressions can access outer scope variables and arguments. This is
1233+
often called a closure. Example where "i" a and "a:arg" are used in a lambda
1234+
while they exists in the function scope. They remain valid even after the
1235+
function returns: >
1236+
:function Foo(arg)
1237+
: let i = 3
1238+
: return {x -> x + i - a:arg}
1239+
:endfunction
1240+
:let Bar = Foo(4)
1241+
:echo Bar(6)
1242+
< 5
12311243

12321244
Examples for using a lambda expression with |sort()|, |map()| and |filter()|: >
12331245
:echo map([1, 2, 3], {idx, val -> val + 1})
@@ -1245,6 +1257,12 @@ The lambda expression is also useful for Channel, Job and timer: >
12451257

12461258
Note how execute() is used to execute an Ex command. That's ugly though.
12471259

1260+
1261+
Lambda expressions have internal names like '<lambda>42'. If you get an error
1262+
for a lambda expression, you can find what it is with the following command: >
1263+
:function {'<lambda>42'}
1264+
See also: |numbered-function|
1265+
12481266
==============================================================================
12491267
3. Internal variable *internal-variables* *E461*
12501268

@@ -7494,7 +7512,8 @@ test_null_string() *test_null_string()*
74947512

74957513
test_settime({expr}) *test_settime()*
74967514
Set the time Vim uses internally. Currently only used for
7497-
timestamps in the history, as they are used in viminfo.
7515+
timestamps in the history, as they are used in viminfo, and
7516+
for undo.
74987517
{expr} must evaluate to a number. When the value is zero the
74997518
normal behavior is restored.
75007519

src/eval.c

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,8 @@ static int get_env_tv(char_u **arg, typval_T *rettv, int evaluate);
237237

238238
static int get_env_len(char_u **arg);
239239
static char_u * make_expanded_name(char_u *in_start, char_u *expr_start, char_u *expr_end, char_u *in_end);
240+
static void check_vars(char_u *name, int len);
240241
static typval_T *alloc_string_tv(char_u *string);
241-
static hashtab_T *find_var_ht(char_u *name, char_u **varname);
242242
static void delete_var(hashtab_T *ht, hashitem_T *hi);
243243
static void list_one_var(dictitem_T *v, char_u *prefix, int *first);
244244
static void list_one_var_a(char_u *prefix, char_u *name, int type, char_u *string, int *first);
@@ -4332,6 +4332,9 @@ eval7(
43324332
{
43334333
partial_T *partial;
43344334

4335+
if (!evaluate)
4336+
check_vars(s, len);
4337+
43354338
/* If "s" is the name of a variable of type VAR_FUNC
43364339
* use its contents. */
43374340
s = deref_func_name(s, &len, &partial, !evaluate);
@@ -4363,7 +4366,10 @@ eval7(
43634366
else if (evaluate)
43644367
ret = get_var_tv(s, len, rettv, NULL, TRUE, FALSE);
43654368
else
4369+
{
4370+
check_vars(s, len);
43664371
ret = OK;
4372+
}
43674373
}
43684374
vim_free(alias);
43694375
}
@@ -5540,6 +5546,10 @@ set_ref_in_item(
55405546
}
55415547
}
55425548
}
5549+
else if (tv->v_type == VAR_FUNC)
5550+
{
5551+
abort = set_ref_in_func(tv->vval.v_string, copyID);
5552+
}
55435553
else if (tv->v_type == VAR_PARTIAL)
55445554
{
55455555
partial_T *pt = tv->vval.v_partial;
@@ -5549,6 +5559,8 @@ set_ref_in_item(
55495559
*/
55505560
if (pt != NULL)
55515561
{
5562+
abort = set_ref_in_func(pt->pt_name, copyID);
5563+
55525564
if (pt->pt_dict != NULL)
55535565
{
55545566
typval_T dtv;
@@ -6790,6 +6802,34 @@ get_var_tv(
67906802
return ret;
67916803
}
67926804

6805+
/*
6806+
* Check if variable "name[len]" is a local variable or an argument.
6807+
* If so, "*eval_lavars_used" is set to TRUE.
6808+
*/
6809+
static void
6810+
check_vars(char_u *name, int len)
6811+
{
6812+
int cc;
6813+
char_u *varname;
6814+
hashtab_T *ht;
6815+
6816+
if (eval_lavars_used == NULL)
6817+
return;
6818+
6819+
/* truncate the name, so that we can use strcmp() */
6820+
cc = name[len];
6821+
name[len] = NUL;
6822+
6823+
ht = find_var_ht(name, &varname);
6824+
if (ht == get_funccal_local_ht() || ht == get_funccal_args_ht())
6825+
{
6826+
if (find_var(name, NULL, TRUE) != NULL)
6827+
*eval_lavars_used = TRUE;
6828+
}
6829+
6830+
name[len] = cc;
6831+
}
6832+
67936833
/*
67946834
* Handle expr[expr], expr[expr:expr] subscript and .name lookup.
67956835
* Also handle function call with Funcref variable: func(expr)
@@ -7274,13 +7314,20 @@ find_var(char_u *name, hashtab_T **htp, int no_autoload)
72747314
{
72757315
char_u *varname;
72767316
hashtab_T *ht;
7317+
dictitem_T *ret = NULL;
72777318

72787319
ht = find_var_ht(name, &varname);
72797320
if (htp != NULL)
72807321
*htp = ht;
72817322
if (ht == NULL)
72827323
return NULL;
7283-
return find_var_in_ht(ht, *name, varname, no_autoload || htp != NULL);
7324+
ret = find_var_in_ht(ht, *name, varname, no_autoload || htp != NULL);
7325+
if (ret != NULL)
7326+
return ret;
7327+
7328+
/* Search in parent scope for lambda */
7329+
return find_var_in_scoped_ht(name, varname ? &varname : NULL,
7330+
no_autoload || htp != NULL);
72847331
}
72857332

72867333
/*
@@ -7341,7 +7388,7 @@ find_var_in_ht(
73417388
* Return NULL if the name is not valid.
73427389
* Set "varname" to the start of name without ':'.
73437390
*/
7344-
static hashtab_T *
7391+
hashtab_T *
73457392
find_var_ht(char_u *name, char_u **varname)
73467393
{
73477394
hashitem_T *hi;
@@ -7617,6 +7664,10 @@ set_var(
76177664
}
76187665
v = find_var_in_ht(ht, 0, varname, TRUE);
76197666

7667+
/* Search in parent scope which is possible to reference from lambda */
7668+
if (v == NULL)
7669+
v = find_var_in_scoped_ht(name, varname ? &varname : NULL, TRUE);
7670+
76207671
if ((tv->v_type == VAR_FUNC || tv->v_type == VAR_PARTIAL)
76217672
&& var_check_func_name(name, v == NULL))
76227673
return;

src/ex_cmds2.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1265,8 +1265,16 @@ set_ref_in_timer(int copyID)
12651265

12661266
for (timer = first_timer; timer != NULL; timer = timer->tr_next)
12671267
{
1268-
tv.v_type = VAR_PARTIAL;
1269-
tv.vval.v_partial = timer->tr_partial;
1268+
if (timer->tr_partial != NULL)
1269+
{
1270+
tv.v_type = VAR_PARTIAL;
1271+
tv.vval.v_partial = timer->tr_partial;
1272+
}
1273+
else
1274+
{
1275+
tv.v_type = VAR_FUNC;
1276+
tv.vval.v_string = timer->tr_callback;
1277+
}
12701278
abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
12711279
}
12721280
return abort;

src/globals.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1658,6 +1658,9 @@ EXTERN time_T time_for_testing INIT(= 0);
16581658

16591659
/* Abort conversion to string after a recursion error. */
16601660
EXTERN int did_echo_string_emsg INIT(= FALSE);
1661+
1662+
/* Used for checking if local variables or arguments used in a lambda. */
1663+
EXTERN int *eval_lavars_used INIT(= NULL);
16611664
#endif
16621665

16631666
/*

src/proto/eval.pro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ char_u *get_tv_string_chk(typval_T *varp);
8787
char_u *get_tv_string_buf_chk(typval_T *varp, char_u *buf);
8888
dictitem_T *find_var(char_u *name, hashtab_T **htp, int no_autoload);
8989
dictitem_T *find_var_in_ht(hashtab_T *ht, int htname, char_u *varname, int no_autoload);
90+
hashtab_T *find_var_ht(char_u *name, char_u **varname);
9091
char_u *get_var_value(char_u *name);
9192
void new_script_vars(scid_T id);
9293
void init_var_dict(dict_T *dict, dictitem_T *dict_var, int scope);

src/proto/userfunc.pro

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ 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+
dictitem_T *find_var_in_scoped_ht(char_u *name, char_u **varname, int no_autoload);
4950
int set_ref_in_previous_funccal(int copyID);
5051
int set_ref_in_call_stack(int copyID);
5152
int set_ref_in_func_args(int copyID);
53+
int set_ref_in_func(char_u *name, int copyID);
5254
/* vim: set ft=c : */

0 commit comments

Comments
 (0)