0
0
mirror of https://github.com/vim/vim.git synced 2025-07-25 10:54:51 -04:00

patch 7.4.2119

Problem:    Closures are not supported.
Solution:   Capture variables in lambdas from the outer scope. (Yasuhiro
            Matsumoto, Ken Takata)
This commit is contained in:
Bram Moolenaar 2016-07-29 22:15:09 +02:00
parent 83a2a80d6f
commit 1e96d9bf98
9 changed files with 450 additions and 47 deletions

View File

@ -1,4 +1,4 @@
*eval.txt* For Vim version 7.4. Last change: 2016 Jul 24 *eval.txt* For Vim version 7.4. Last change: 2016 Jul 29
VIM REFERENCE MANUAL by Bram Moolenaar VIM REFERENCE MANUAL by Bram Moolenaar
@ -40,7 +40,7 @@ done, the features in this document are not available. See |+eval| and
There are nine types of variables: There are nine types of variables:
Number A 32 or 64 bit signed number. |expr-number| *Number* Number A 32 or 64 bit signed number. |expr-number| *Number*
64-bit Number is available only when compiled with the 64-bit Numbers are available only when compiled with the
|+num64| feature. |+num64| feature.
Examples: -123 0x10 0177 Examples: -123 0x10 0177
@ -1219,7 +1219,7 @@ the following ways:
1. The body of the lambda expression is an |expr1| and not a sequence of |Ex| 1. The body of the lambda expression is an |expr1| and not a sequence of |Ex|
commands. commands.
2. The prefix "a:" is optional for arguments. E.g.: > 2. The prefix "a:" should not be used for arguments. E.g.: >
:let F = {arg1, arg2 -> arg1 - arg2} :let F = {arg1, arg2 -> arg1 - arg2}
:echo F(5, 2) :echo F(5, 2)
< 3 < 3
@ -1228,6 +1228,18 @@ The arguments are optional. Example: >
:let F = {-> 'error function'} :let F = {-> 'error function'}
:echo F() :echo F()
< error function < error function
*closure*
Lambda expressions can access outer scope variables and arguments. This is
often called a closure. Example where "i" a and "a:arg" are used in a lambda
while they exists in the function scope. They remain valid even after the
function returns: >
:function Foo(arg)
: let i = 3
: return {x -> x + i - a:arg}
:endfunction
:let Bar = Foo(4)
:echo Bar(6)
< 5
Examples for using a lambda expression with |sort()|, |map()| and |filter()|: > Examples for using a lambda expression with |sort()|, |map()| and |filter()|: >
:echo map([1, 2, 3], {idx, val -> val + 1}) :echo map([1, 2, 3], {idx, val -> val + 1})
@ -1245,6 +1257,12 @@ The lambda expression is also useful for Channel, Job and timer: >
Note how execute() is used to execute an Ex command. That's ugly though. Note how execute() is used to execute an Ex command. That's ugly though.
Lambda expressions have internal names like '<lambda>42'. If you get an error
for a lambda expression, you can find what it is with the following command: >
:function {'<lambda>42'}
See also: |numbered-function|
============================================================================== ==============================================================================
3. Internal variable *internal-variables* *E461* 3. Internal variable *internal-variables* *E461*
@ -7494,7 +7512,8 @@ test_null_string() *test_null_string()*
test_settime({expr}) *test_settime()* test_settime({expr}) *test_settime()*
Set the time Vim uses internally. Currently only used for Set the time Vim uses internally. Currently only used for
timestamps in the history, as they are used in viminfo. timestamps in the history, as they are used in viminfo, and
for undo.
{expr} must evaluate to a number. When the value is zero the {expr} must evaluate to a number. When the value is zero the
normal behavior is restored. normal behavior is restored.

View File

@ -237,8 +237,8 @@ static int get_env_tv(char_u **arg, typval_T *rettv, int evaluate);
static int get_env_len(char_u **arg); static int get_env_len(char_u **arg);
static char_u * make_expanded_name(char_u *in_start, char_u *expr_start, char_u *expr_end, char_u *in_end); static char_u * make_expanded_name(char_u *in_start, char_u *expr_start, char_u *expr_end, char_u *in_end);
static void check_vars(char_u *name, int len);
static typval_T *alloc_string_tv(char_u *string); static typval_T *alloc_string_tv(char_u *string);
static hashtab_T *find_var_ht(char_u *name, char_u **varname);
static void delete_var(hashtab_T *ht, hashitem_T *hi); static void delete_var(hashtab_T *ht, hashitem_T *hi);
static void list_one_var(dictitem_T *v, char_u *prefix, int *first); static void list_one_var(dictitem_T *v, char_u *prefix, int *first);
static void list_one_var_a(char_u *prefix, char_u *name, int type, char_u *string, int *first); static void list_one_var_a(char_u *prefix, char_u *name, int type, char_u *string, int *first);
@ -4332,6 +4332,9 @@ eval7(
{ {
partial_T *partial; partial_T *partial;
if (!evaluate)
check_vars(s, len);
/* If "s" is the name of a variable of type VAR_FUNC /* If "s" is the name of a variable of type VAR_FUNC
* use its contents. */ * use its contents. */
s = deref_func_name(s, &len, &partial, !evaluate); s = deref_func_name(s, &len, &partial, !evaluate);
@ -4363,7 +4366,10 @@ eval7(
else if (evaluate) else if (evaluate)
ret = get_var_tv(s, len, rettv, NULL, TRUE, FALSE); ret = get_var_tv(s, len, rettv, NULL, TRUE, FALSE);
else else
{
check_vars(s, len);
ret = OK; ret = OK;
}
} }
vim_free(alias); vim_free(alias);
} }
@ -5540,6 +5546,10 @@ set_ref_in_item(
} }
} }
} }
else if (tv->v_type == VAR_FUNC)
{
abort = set_ref_in_func(tv->vval.v_string, copyID);
}
else if (tv->v_type == VAR_PARTIAL) else if (tv->v_type == VAR_PARTIAL)
{ {
partial_T *pt = tv->vval.v_partial; partial_T *pt = tv->vval.v_partial;
@ -5549,6 +5559,8 @@ set_ref_in_item(
*/ */
if (pt != NULL) if (pt != NULL)
{ {
abort = set_ref_in_func(pt->pt_name, copyID);
if (pt->pt_dict != NULL) if (pt->pt_dict != NULL)
{ {
typval_T dtv; typval_T dtv;
@ -6790,6 +6802,34 @@ get_var_tv(
return ret; return ret;
} }
/*
* Check if variable "name[len]" is a local variable or an argument.
* If so, "*eval_lavars_used" is set to TRUE.
*/
static void
check_vars(char_u *name, int len)
{
int cc;
char_u *varname;
hashtab_T *ht;
if (eval_lavars_used == NULL)
return;
/* truncate the name, so that we can use strcmp() */
cc = name[len];
name[len] = NUL;
ht = find_var_ht(name, &varname);
if (ht == get_funccal_local_ht() || ht == get_funccal_args_ht())
{
if (find_var(name, NULL, TRUE) != NULL)
*eval_lavars_used = TRUE;
}
name[len] = cc;
}
/* /*
* Handle expr[expr], expr[expr:expr] subscript and .name lookup. * Handle expr[expr], expr[expr:expr] subscript and .name lookup.
* Also handle function call with Funcref variable: func(expr) * Also handle function call with Funcref variable: func(expr)
@ -7274,13 +7314,20 @@ find_var(char_u *name, hashtab_T **htp, int no_autoload)
{ {
char_u *varname; char_u *varname;
hashtab_T *ht; hashtab_T *ht;
dictitem_T *ret = NULL;
ht = find_var_ht(name, &varname); ht = find_var_ht(name, &varname);
if (htp != NULL) if (htp != NULL)
*htp = ht; *htp = ht;
if (ht == NULL) if (ht == NULL)
return NULL; return NULL;
return find_var_in_ht(ht, *name, varname, no_autoload || htp != NULL); ret = find_var_in_ht(ht, *name, varname, no_autoload || htp != NULL);
if (ret != NULL)
return ret;
/* Search in parent scope for lambda */
return find_var_in_scoped_ht(name, varname ? &varname : NULL,
no_autoload || htp != NULL);
} }
/* /*
@ -7341,7 +7388,7 @@ find_var_in_ht(
* Return NULL if the name is not valid. * Return NULL if the name is not valid.
* Set "varname" to the start of name without ':'. * Set "varname" to the start of name without ':'.
*/ */
static hashtab_T * hashtab_T *
find_var_ht(char_u *name, char_u **varname) find_var_ht(char_u *name, char_u **varname)
{ {
hashitem_T *hi; hashitem_T *hi;
@ -7617,6 +7664,10 @@ set_var(
} }
v = find_var_in_ht(ht, 0, varname, TRUE); v = find_var_in_ht(ht, 0, varname, TRUE);
/* Search in parent scope which is possible to reference from lambda */
if (v == NULL)
v = find_var_in_scoped_ht(name, varname ? &varname : NULL, TRUE);
if ((tv->v_type == VAR_FUNC || tv->v_type == VAR_PARTIAL) if ((tv->v_type == VAR_FUNC || tv->v_type == VAR_PARTIAL)
&& var_check_func_name(name, v == NULL)) && var_check_func_name(name, v == NULL))
return; return;

View File

@ -1265,8 +1265,16 @@ set_ref_in_timer(int copyID)
for (timer = first_timer; timer != NULL; timer = timer->tr_next) for (timer = first_timer; timer != NULL; timer = timer->tr_next)
{ {
tv.v_type = VAR_PARTIAL; if (timer->tr_partial != NULL)
tv.vval.v_partial = timer->tr_partial; {
tv.v_type = VAR_PARTIAL;
tv.vval.v_partial = timer->tr_partial;
}
else
{
tv.v_type = VAR_FUNC;
tv.vval.v_string = timer->tr_callback;
}
abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL); abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
} }
return abort; return abort;

View File

@ -1658,6 +1658,9 @@ EXTERN time_T time_for_testing INIT(= 0);
/* Abort conversion to string after a recursion error. */ /* Abort conversion to string after a recursion error. */
EXTERN int did_echo_string_emsg INIT(= FALSE); EXTERN int did_echo_string_emsg INIT(= FALSE);
/* Used for checking if local variables or arguments used in a lambda. */
EXTERN int *eval_lavars_used INIT(= NULL);
#endif #endif
/* /*

View File

@ -87,6 +87,7 @@ char_u *get_tv_string_chk(typval_T *varp);
char_u *get_tv_string_buf_chk(typval_T *varp, char_u *buf); char_u *get_tv_string_buf_chk(typval_T *varp, char_u *buf);
dictitem_T *find_var(char_u *name, hashtab_T **htp, int no_autoload); dictitem_T *find_var(char_u *name, hashtab_T **htp, int no_autoload);
dictitem_T *find_var_in_ht(hashtab_T *ht, int htname, char_u *varname, int no_autoload); dictitem_T *find_var_in_ht(hashtab_T *ht, int htname, char_u *varname, int no_autoload);
hashtab_T *find_var_ht(char_u *name, char_u **varname);
char_u *get_var_value(char_u *name); char_u *get_var_value(char_u *name);
void new_script_vars(scid_T id); void new_script_vars(scid_T id);
void init_var_dict(dict_T *dict, dictitem_T *dict_var, int scope); void init_var_dict(dict_T *dict, dictitem_T *dict_var, int scope);

View File

@ -46,7 +46,9 @@ void *clear_current_funccal(void);
void restore_current_funccal(void *f); void restore_current_funccal(void *f);
void list_func_vars(int *first); void list_func_vars(int *first);
dict_T *get_current_funccal_dict(hashtab_T *ht); dict_T *get_current_funccal_dict(hashtab_T *ht);
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_previous_funccal(int copyID);
int set_ref_in_call_stack(int copyID); int set_ref_in_call_stack(int copyID);
int set_ref_in_func_args(int copyID); int set_ref_in_func_args(int copyID);
int set_ref_in_func(char_u *name, int copyID);
/* vim: set ft=c : */ /* vim: set ft=c : */

View File

@ -21,7 +21,7 @@ function! Test_lambda_with_timer()
let s:timer_id = 0 let s:timer_id = 0
function! s:Foo() function! s:Foo()
"let n = 0 "let n = 0
let s:timer_id = timer_start(50, {-> execute("let s:n += 1 | echo s:n")}, {"repeat": -1}) let s:timer_id = timer_start(50, {-> execute("let s:n += 1 | echo s:n", "")}, {"repeat": -1})
endfunction endfunction
call s:Foo() call s:Foo()
@ -51,3 +51,161 @@ func Test_not_lamda()
let x = {'>' : 'foo'} let x = {'>' : 'foo'}
call assert_equal('foo', x['>']) call assert_equal('foo', x['>'])
endfunc endfunc
function! Test_lambda_capture_by_reference()
let v = 1
let l:F = {x -> x + v}
let v = 2
call assert_equal(12, l:F(10))
endfunction
function! Test_lambda_side_effect()
function! s:update_and_return(arr)
let a:arr[1] = 5
return a:arr
endfunction
function! s:foo(arr)
return {-> s:update_and_return(a:arr)}
endfunction
let arr = [3,2,1]
call assert_equal([3, 5, 1], s:foo(arr)())
endfunction
function! Test_lambda_refer_local_variable_from_other_scope()
function! s:foo(X)
return a:X() " refer l:x in s:bar()
endfunction
function! s:bar()
let x = 123
return s:foo({-> x})
endfunction
call assert_equal(123, s:bar())
endfunction
function! Test_lambda_do_not_share_local_variable()
function! s:define_funcs()
let l:One = {-> split(execute("let a = 'abc' | echo a"))[0]}
let l:Two = {-> exists("a") ? a : "no"}
return [l:One, l:Two]
endfunction
let l:F = s:define_funcs()
call assert_equal('no', l:F[1]())
call assert_equal('abc', l:F[0]())
call assert_equal('no', l:F[1]())
endfunction
function! Test_lambda_closure()
function! s:foo()
let x = 0
return {-> [execute("let x += 1"), x][-1]}
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_lambda_with_a_var()
function! s:foo()
let x = 2
return {... -> a:000 + [x]}
endfunction
function! s:bar()
return s:foo()(1)
endfunction
call assert_equal([1, 2], s:bar())
endfunction
function! Test_lambda_call_lambda_from_lambda()
function! s:foo(x)
let l:F1 = {-> {-> a:x}}
return {-> l:F1()}
endfunction
let l:F = s:foo(1)
call assert_equal(1, l:F()())
endfunction
function! Test_lambda_delfunc()
function! s:gen()
let pl = l:
let l:Foo = {-> get(pl, "Foo", get(pl, "Bar", {-> 0}))}
let l:Bar = l:Foo
delfunction l:Foo
return l:Bar
endfunction
let l:F = s:gen()
call assert_fails(':call l:F()', 'E117:')
endfunction
function! Test_lambda_scope()
function! s:NewCounter()
let c = 0
return {-> [execute('let c += 1'), c][-1]}
endfunction
function! s:NewCounter2()
return {-> [execute('let c += 100'), c][-1]}
endfunction
let l:C = s:NewCounter()
let l:D = s:NewCounter2()
call assert_equal(1, l:C())
call assert_fails(':call l:D()', 'E15:') " E121: then E15:
call assert_equal(2, l:C())
endfunction
function! Test_lambda_share_scope()
function! s:New()
let c = 0
let l:Inc0 = {-> [execute('let c += 1'), c][-1]}
let l:Dec0 = {-> [execute('let c -= 1'), c][-1]}
return [l:Inc0, l:Dec0]
endfunction
let [l:Inc, l:Dec] = s:New()
call assert_equal(1, l:Inc())
call assert_equal(2, l:Inc())
call assert_equal(1, l:Dec())
endfunction
function! Test_lambda_circular_reference()
function! s:Foo()
let d = {}
let d.f = {-> d}
return d.f
endfunction
call s:Foo()
call test_garbagecollect_now()
let i = 0 | while i < 10000 | call s:Foo() | let i+= 1 | endwhile
call test_garbagecollect_now()
endfunction
function! Test_lambda_combination()
call assert_equal(2, {x -> {x -> x}}(1)(2))
call assert_equal(10, {y -> {x -> x(y)(10)}({y -> y})}({z -> z}))
call assert_equal(5.0, {x -> {y -> x / y}}(10)(2.0))
call assert_equal(6, {x -> {y -> {z -> x + y + z}}}(1)(2)(3))
call assert_equal(6, {x -> {f -> f(x)}}(3)({x -> x * 2}))
call assert_equal(6, {f -> {x -> f(x)}}({x -> x * 2})(3))
" Z combinator
let Z = {f -> {x -> f({y -> x(x)(y)})}({x -> f({y -> x(x)(y)})})}
let Fact = {f -> {x -> x == 0 ? 1 : x * f(x - 1)}}
call assert_equal(120, Z(Fact)(5))
endfunction

View File

@ -15,6 +15,8 @@
#if defined(FEAT_EVAL) || defined(PROTO) #if defined(FEAT_EVAL) || defined(PROTO)
typedef struct funccall_S funccall_T;
/* /*
* Structure to hold info for a user function. * Structure to hold info for a user function.
*/ */
@ -47,6 +49,7 @@ struct ufunc
scid_T uf_script_ID; /* ID of script where function was defined, scid_T uf_script_ID; /* ID of script where function was defined,
used for s: variables */ used for s: variables */
int uf_refcount; /* for numbered function: reference count */ int uf_refcount; /* for numbered function: reference count */
funccall_T *uf_scoped; /* l: local variables for closure */
char_u uf_name[1]; /* name of function (actually longer); can char_u uf_name[1]; /* name of function (actually longer); can
start with <SNR>123_ (<SNR> is K_SPECIAL start with <SNR>123_ (<SNR> is K_SPECIAL
KS_EXTRA KE_SNR) */ KS_EXTRA KE_SNR) */
@ -70,8 +73,6 @@ struct ufunc
#define FIXVAR_CNT 12 /* number of fixed variables */ #define FIXVAR_CNT 12 /* number of fixed variables */
/* structure to hold info for a function that is currently being executed. */ /* structure to hold info for a function that is currently being executed. */
typedef struct funccall_S funccall_T;
struct funccall_S struct funccall_S
{ {
ufunc_T *func; /* function being called */ ufunc_T *func; /* function being called */
@ -96,6 +97,11 @@ struct funccall_S
proftime_T prof_child; /* time spent in a child */ proftime_T prof_child; /* time spent in a child */
#endif #endif
funccall_T *caller; /* calling function or NULL */ funccall_T *caller; /* calling function or NULL */
/* for closure */
int fc_refcount;
int fc_copyID; /* for garbage collection */
garray_T fc_funcs; /* list of ufunc_T* which refer this */
}; };
/* /*
@ -259,6 +265,7 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
{ {
garray_T newargs; garray_T newargs;
garray_T newlines; garray_T newlines;
garray_T *pnewargs;
ufunc_T *fp = NULL; ufunc_T *fp = NULL;
int varargs; int varargs;
int ret; int ret;
@ -266,6 +273,8 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
char_u *start = skipwhite(*arg + 1); char_u *start = skipwhite(*arg + 1);
char_u *s, *e; char_u *s, *e;
static int lambda_no = 0; static int lambda_no = 0;
int *old_eval_lavars = eval_lavars_used;
int eval_lavars = FALSE;
ga_init(&newargs); ga_init(&newargs);
ga_init(&newlines); ga_init(&newlines);
@ -276,11 +285,19 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
return NOTDONE; return NOTDONE;
/* Parse the arguments again. */ /* Parse the arguments again. */
if (evaluate)
pnewargs = &newargs;
else
pnewargs = NULL;
*arg = skipwhite(*arg + 1); *arg = skipwhite(*arg + 1);
ret = get_function_args(arg, '-', &newargs, &varargs, FALSE); ret = get_function_args(arg, '-', pnewargs, &varargs, FALSE);
if (ret == FAIL || **arg != '>') if (ret == FAIL || **arg != '>')
goto errret; goto errret;
/* Set up dictionaries for checking local variables and arguments. */
if (evaluate)
eval_lavars_used = &eval_lavars;
/* Get the start and the end of the expression. */ /* Get the start and the end of the expression. */
*arg = skipwhite(*arg + 1); *arg = skipwhite(*arg + 1);
s = *arg; s = *arg;
@ -298,32 +315,42 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
int len; int len;
char_u *p; char_u *p;
fp = (ufunc_T *)alloc((unsigned)(sizeof(ufunc_T) + 20)); sprintf((char*)name, "<lambda>%d", ++lambda_no);
fp = (ufunc_T *)alloc((unsigned)(sizeof(ufunc_T) + STRLEN(name)));
if (fp == NULL) if (fp == NULL)
goto errret; goto errret;
sprintf((char*)name, "<lambda>%d", ++lambda_no);
ga_init2(&newlines, (int)sizeof(char_u *), 1); ga_init2(&newlines, (int)sizeof(char_u *), 1);
if (ga_grow(&newlines, 1) == FAIL) if (ga_grow(&newlines, 1) == FAIL)
goto errret; goto errret;
/* Add "return " before the expression. /* Add "return " before the expression. */
* TODO: Support multiple expressions. */
len = 7 + e - s + 1; len = 7 + e - s + 1;
p = (char_u *)alloc(len); p = (char_u *)alloc(len);
if (p == NULL) if (p == NULL)
goto errret; goto errret;
((char_u **)(newlines.ga_data))[newlines.ga_len++] = p; ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p;
STRCPY(p, "return "); STRCPY(p, "return ");
STRNCPY(p + 7, s, e - s); vim_strncpy(p + 7, s, e - s);
p[7 + e - s] = NUL;
fp->uf_refcount = 1; fp->uf_refcount = 1;
STRCPY(fp->uf_name, name); STRCPY(fp->uf_name, name);
hash_add(&func_hashtab, UF2HIKEY(fp)); hash_add(&func_hashtab, UF2HIKEY(fp));
fp->uf_args = newargs; fp->uf_args = newargs;
fp->uf_lines = newlines; fp->uf_lines = newlines;
if (current_funccal != NULL && eval_lavars)
{
fp->uf_scoped = current_funccal;
current_funccal->fc_refcount++;
if (ga_grow(&current_funccal->fc_funcs, 1) == FAIL)
goto errret;
((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 #ifdef FEAT_PROFILE
fp->uf_tml_count = NULL; fp->uf_tml_count = NULL;
@ -341,15 +368,15 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
rettv->vval.v_string = vim_strsave(name); rettv->vval.v_string = vim_strsave(name);
rettv->v_type = VAR_FUNC; rettv->v_type = VAR_FUNC;
} }
else
ga_clear_strings(&newargs);
eval_lavars_used = old_eval_lavars;
return OK; return OK;
errret: errret:
ga_clear_strings(&newargs); ga_clear_strings(&newargs);
ga_clear_strings(&newlines); ga_clear_strings(&newlines);
vim_free(fp); vim_free(fp);
eval_lavars_used = old_eval_lavars;
return FAIL; return FAIL;
} }
@ -624,6 +651,15 @@ free_funccal(
int free_val) /* a: vars were allocated */ int free_val) /* a: vars were allocated */
{ {
listitem_T *li; listitem_T *li;
int i;
for (i = 0; i < fc->fc_funcs.ga_len; ++i)
{
ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i];
if (fp != NULL)
fp->uf_scoped = NULL;
}
/* The a: variables typevals may not have been allocated, only free the /* The a: variables typevals may not have been allocated, only free the
* allocated variables. */ * allocated variables. */
@ -637,6 +673,16 @@ free_funccal(
for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next) for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next)
clear_tv(&li->li_tv); clear_tv(&li->li_tv);
for (i = 0; i < fc->fc_funcs.ga_len; ++i)
{
ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i];
if (fp != NULL)
func_unref(fc->func->uf_name);
}
ga_clear(&fc->fc_funcs);
func_unref(fc->func->uf_name);
vim_free(fc); vim_free(fc);
} }
@ -696,6 +742,11 @@ call_user_func(
/* Check if this function has a breakpoint. */ /* Check if this function has a breakpoint. */
fc->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0); fc->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0);
fc->dbg_tick = debug_tick; fc->dbg_tick = debug_tick;
/* Set up fields for closure. */
fc->fc_refcount = 0;
fc->fc_copyID = 0;
ga_init2(&fc->fc_funcs, sizeof(ufunc_T *), 1);
func_ref(fp->uf_name);
if (STRNCMP(fp->uf_name, "<lambda>", 8) == 0) if (STRNCMP(fp->uf_name, "<lambda>", 8) == 0)
islambda = TRUE; islambda = TRUE;
@ -758,7 +809,6 @@ call_user_func(
for (i = 0; i < argcount; ++i) for (i = 0; i < argcount; ++i)
{ {
int addlocal = FALSE; int addlocal = FALSE;
dictitem_T *v2;
ai = i - fp->uf_args.ga_len; ai = i - fp->uf_args.ga_len;
if (ai < 0) if (ai < 0)
@ -778,9 +828,6 @@ call_user_func(
{ {
v = &fc->fixvar[fixvar_idx++].var; v = &fc->fixvar[fixvar_idx++].var;
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
if (addlocal)
v2 = v;
} }
else else
{ {
@ -789,36 +836,23 @@ call_user_func(
if (v == NULL) if (v == NULL)
break; break;
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC; 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); STRCPY(v->di_key, name);
hash_add(&fc->l_avars.dv_hashtab, DI2HIKEY(v));
/* Note: the values are copied directly to avoid alloc/free. /* Note: the values are copied directly to avoid alloc/free.
* "argvars" must have VAR_FIXED for v_lock. */ * "argvars" must have VAR_FIXED for v_lock. */
v->di_tv = argvars[i]; v->di_tv = argvars[i];
v->di_tv.v_lock = VAR_FIXED; 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) if (addlocal)
{ {
STRCPY(v2->di_key, name); /* Named arguments should be accessed without the "a:" prefix in
copy_tv(&v->di_tv, &v2->di_tv); * lambda expressions. Add to the l: dict. */
v2->di_tv.v_lock = VAR_FIXED; copy_tv(&v->di_tv, &v->di_tv);
hash_add(&fc->l_vars.dv_hashtab, DI2HIKEY(v2)); hash_add(&fc->l_vars.dv_hashtab, DI2HIKEY(v));
} }
else
hash_add(&fc->l_avars.dv_hashtab, DI2HIKEY(v));
if (ai >= 0 && ai < MAX_FUNC_ARGS) if (ai >= 0 && ai < MAX_FUNC_ARGS)
{ {
@ -1014,7 +1048,8 @@ call_user_func(
* free the funccall_T and what's in it. */ * free the funccall_T and what's in it. */
if (fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT if (fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT
&& fc->l_vars.dv_refcount == DO_NOT_FREE_CNT && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT
&& fc->l_avars.dv_refcount == DO_NOT_FREE_CNT) && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT
&& fc->fc_refcount <= 0)
{ {
free_funccal(fc, FALSE); free_funccal(fc, FALSE);
} }
@ -1048,6 +1083,52 @@ call_user_func(
} }
} }
/*
* Unreference "fc": decrement the reference count and free it when it
* becomes zero. If "fp" is not NULL, "fp" is detached from "fc".
*/
static void
funccal_unref(funccall_T *fc, ufunc_T *fp)
{
funccall_T **pfc;
int i;
int freed = FALSE;
if (fc == NULL)
return;
if (--fc->fc_refcount <= 0)
{
for (pfc = &previous_funccal; *pfc != NULL; )
{
if (fc == *pfc
&& fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT
&& fc->l_vars.dv_refcount == DO_NOT_FREE_CNT
&& fc->l_avars.dv_refcount == DO_NOT_FREE_CNT)
{
*pfc = fc->caller;
free_funccal(fc, TRUE);
freed = TRUE;
}
else
pfc = &(*pfc)->caller;
}
}
if (!freed)
{
func_unref(fc->func->uf_name);
if (fp != NULL)
{
for (i = 0; i < fc->fc_funcs.ga_len; ++i)
{
if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp)
((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL;
}
}
}
}
/* /*
* Free a function and remove it from the list of functions. * Free a function and remove it from the list of functions.
*/ */
@ -1072,6 +1153,8 @@ func_free(ufunc_T *fp)
else else
hash_remove(&func_hashtab, hi); hash_remove(&func_hashtab, hi);
funccal_unref(fp->uf_scoped, fp);
vim_free(fp); vim_free(fp);
} }
@ -2216,6 +2299,7 @@ ex_function(exarg_T *eap)
} }
fp->uf_args = newargs; fp->uf_args = newargs;
fp->uf_lines = newlines; fp->uf_lines = newlines;
fp->uf_scoped = NULL;
#ifdef FEAT_PROFILE #ifdef FEAT_PROFILE
fp->uf_tml_count = NULL; fp->uf_tml_count = NULL;
fp->uf_tml_total = NULL; fp->uf_tml_total = NULL;
@ -2705,7 +2789,8 @@ can_free_funccal(funccall_T *fc, int copyID)
{ {
return (fc->l_varlist.lv_copyID != copyID return (fc->l_varlist.lv_copyID != copyID
&& fc->l_vars.dv_copyID != copyID && fc->l_vars.dv_copyID != copyID
&& fc->l_avars.dv_copyID != copyID); && fc->l_avars.dv_copyID != copyID
&& fc->fc_copyID != copyID);
} }
/* /*
@ -3450,6 +3535,40 @@ get_current_funccal_dict(hashtab_T *ht)
return NULL; return NULL;
} }
/*
* Search variable in parent scope.
*/
dictitem_T *
find_var_in_scoped_ht(char_u *name, char_u **varname, int no_autoload)
{
dictitem_T *v = NULL;
funccall_T *old_current_funccal = current_funccal;
hashtab_T *ht;
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 ? &(*varname) : NULL);
if (ht != NULL)
{
v = find_var_in_ht(ht, *name,
varname ? *varname : NULL, no_autoload);
if (v != NULL)
break;
}
if (current_funccal == current_funccal->func->uf_scoped)
break;
current_funccal = current_funccal->func->uf_scoped;
}
current_funccal = old_current_funccal;
return v;
}
/* /*
* Set "copyID + 1" in previous_funccal and callers. * Set "copyID + 1" in previous_funccal and callers.
*/ */
@ -3461,6 +3580,7 @@ set_ref_in_previous_funccal(int copyID)
for (fc = previous_funccal; fc != NULL; fc = fc->caller) for (fc = previous_funccal; fc != NULL; fc = fc->caller)
{ {
fc->fc_copyID = copyID + 1;
abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1, abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1,
NULL); NULL);
abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1, abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1,
@ -3480,6 +3600,7 @@ set_ref_in_call_stack(int copyID)
for (fc = current_funccal; fc != NULL; fc = fc->caller) for (fc = current_funccal; fc != NULL; fc = fc->caller)
{ {
fc->fc_copyID = copyID;
abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL); abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL);
abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL); abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL);
} }
@ -3501,4 +3622,42 @@ set_ref_in_func_args(int copyID)
return abort; return abort;
} }
/*
* Mark all lists and dicts referenced through function "name" with "copyID".
* "list_stack" is used to add lists to be marked. Can be NULL.
* "ht_stack" is used to add hashtabs to be marked. Can be NULL.
*
* Returns TRUE if setting references failed somehow.
*/
int
set_ref_in_func(char_u *name, int copyID)
{
ufunc_T *fp;
funccall_T *fc;
int error = ERROR_NONE;
char_u fname_buf[FLEN_FIXED + 1];
char_u *tofree = NULL;
char_u *fname;
if (name == NULL)
return FALSE;
fname = fname_trans_sid(name, fname_buf, &tofree, &error);
fp = find_func(fname);
if (fp != NULL)
{
for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped)
{
if (fc->fc_copyID != copyID)
{
fc->fc_copyID = copyID;
set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL);
set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL);
}
}
}
vim_free(tofree);
return FALSE;
}
#endif /* FEAT_EVAL */ #endif /* FEAT_EVAL */

View File

@ -758,6 +758,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
2119,
/**/ /**/
2118, 2118,
/**/ /**/