# HG changeset patch # User Christian Brabandt # Date 1469824208 -7200 # Node ID 8c2553beff0f783bd523754903bda32f7a71a2b5 # Parent fee40b9f498942da1db58e9cd285e3f2c2ff6adb commit https://github.com/vim/vim/commit/1e96d9bf98f9ab84d5af7f98d6a961d91b17364f Author: Bram Moolenaar Date: Fri Jul 29 22:15:09 2016 +0200 patch 7.4.2119 Problem: Closures are not supported. Solution: Capture variables in lambdas from the outer scope. (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 @@ -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 @@ -40,7 +40,7 @@ 1.1 Variable types ~ There are nine types of variables: 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. 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| 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} :echo F(5, 2) < 3 @@ -1228,6 +1228,18 @@ The arguments are optional. Example: > :let F = {-> 'error function'} :echo F() < 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()|: > :echo map([1, 2, 3], {idx, val -> val + 1}) @@ -1245,6 +1257,12 @@ The lambda expression is also useful for Note how execute() is used to execute an Ex command. That's ugly though. + +Lambda expressions have internal names like '42'. If you get an error +for a lambda expression, you can find what it is with the following command: > + :function {'42'} +See also: |numbered-function| + ============================================================================== 3. Internal variable *internal-variables* *E461* @@ -7494,7 +7512,8 @@ test_null_string() *test_null_string test_settime({expr}) *test_settime()* 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 normal behavior is restored. diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -237,8 +237,8 @@ static int get_env_tv(char_u **arg, typv 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 void check_vars(char_u *name, int len); 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 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); @@ -4332,6 +4332,9 @@ eval7( { partial_T *partial; + if (!evaluate) + check_vars(s, len); + /* If "s" is the name of a variable of type VAR_FUNC * use its contents. */ s = deref_func_name(s, &len, &partial, !evaluate); @@ -4363,7 +4366,10 @@ eval7( else if (evaluate) ret = get_var_tv(s, len, rettv, NULL, TRUE, FALSE); else + { + check_vars(s, len); ret = OK; + } } 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) { partial_T *pt = tv->vval.v_partial; @@ -5549,6 +5559,8 @@ set_ref_in_item( */ if (pt != NULL) { + abort = set_ref_in_func(pt->pt_name, copyID); + if (pt->pt_dict != NULL) { typval_T dtv; @@ -6791,6 +6803,34 @@ get_var_tv( } /* + * 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. * Also handle function call with Funcref variable: func(expr) * Can all be combined: dict.func(expr)[idx]['func'](expr) @@ -7274,13 +7314,20 @@ find_var(char_u *name, hashtab_T **htp, { char_u *varname; hashtab_T *ht; + dictitem_T *ret = NULL; ht = find_var_ht(name, &varname); if (htp != NULL) *htp = ht; if (ht == 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. * Set "varname" to the start of name without ':'. */ - static hashtab_T * + hashtab_T * find_var_ht(char_u *name, char_u **varname) { hashitem_T *hi; @@ -7617,6 +7664,10 @@ set_var( } 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) && var_check_func_name(name, v == NULL)) return; diff --git a/src/ex_cmds2.c b/src/ex_cmds2.c --- a/src/ex_cmds2.c +++ b/src/ex_cmds2.c @@ -1265,8 +1265,16 @@ set_ref_in_timer(int copyID) for (timer = first_timer; timer != NULL; timer = timer->tr_next) { - tv.v_type = VAR_PARTIAL; - tv.vval.v_partial = timer->tr_partial; + if (timer->tr_partial != NULL) + { + 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); } return abort; diff --git a/src/globals.h b/src/globals.h --- a/src/globals.h +++ b/src/globals.h @@ -1658,6 +1658,9 @@ EXTERN time_T time_for_testing INIT(= 0) /* Abort conversion to string after a recursion error. */ 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 /* diff --git a/src/proto/eval.pro b/src/proto/eval.pro --- a/src/proto/eval.pro +++ b/src/proto/eval.pro @@ -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); 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); +hashtab_T *find_var_ht(char_u *name, char_u **varname); char_u *get_var_value(char_u *name); void new_script_vars(scid_T id); void init_var_dict(dict_T *dict, dictitem_T *dict_var, int scope); diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro --- a/src/proto/userfunc.pro +++ b/src/proto/userfunc.pro @@ -46,7 +46,9 @@ 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); +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); int set_ref_in_func_args(int copyID); +int set_ref_in_func(char_u *name, int copyID); /* vim: set ft=c : */ 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 @@ -21,7 +21,7 @@ function! Test_lambda_with_timer() let s:timer_id = 0 function! s:Foo() "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 call s:Foo() @@ -51,3 +51,161 @@ func Test_not_lamda() let x = {'>' : 'foo'} call assert_equal('foo', x['>']) 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 diff --git a/src/userfunc.c b/src/userfunc.c --- a/src/userfunc.c +++ b/src/userfunc.c @@ -15,6 +15,8 @@ #if defined(FEAT_EVAL) || defined(PROTO) +typedef struct funccall_S funccall_T; + /* * 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, used for s: variables */ 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 start with 123_ ( is K_SPECIAL KS_EXTRA KE_SNR) */ @@ -70,8 +73,6 @@ struct ufunc #define FIXVAR_CNT 12 /* number of fixed variables */ /* structure to hold info for a function that is currently being executed. */ -typedef struct funccall_S funccall_T; - struct funccall_S { ufunc_T *func; /* function being called */ @@ -96,6 +97,11 @@ struct funccall_S proftime_T prof_child; /* time spent in a child */ #endif 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 *re { garray_T newargs; garray_T newlines; + garray_T *pnewargs; ufunc_T *fp = NULL; int varargs; int ret; @@ -266,6 +273,8 @@ get_lambda_tv(char_u **arg, typval_T *re char_u *start = skipwhite(*arg + 1); char_u *s, *e; static int lambda_no = 0; + int *old_eval_lavars = eval_lavars_used; + int eval_lavars = FALSE; ga_init(&newargs); ga_init(&newlines); @@ -276,11 +285,19 @@ get_lambda_tv(char_u **arg, typval_T *re return NOTDONE; /* Parse the arguments again. */ + if (evaluate) + pnewargs = &newargs; + else + pnewargs = NULL; *arg = skipwhite(*arg + 1); - ret = get_function_args(arg, '-', &newargs, &varargs, FALSE); + ret = get_function_args(arg, '-', pnewargs, &varargs, FALSE); if (ret == FAIL || **arg != '>') 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. */ *arg = skipwhite(*arg + 1); s = *arg; @@ -298,32 +315,42 @@ get_lambda_tv(char_u **arg, typval_T *re int len; char_u *p; - fp = (ufunc_T *)alloc((unsigned)(sizeof(ufunc_T) + 20)); + sprintf((char*)name, "%d", ++lambda_no); + + fp = (ufunc_T *)alloc((unsigned)(sizeof(ufunc_T) + STRLEN(name))); if (fp == NULL) goto errret; - sprintf((char*)name, "%d", ++lambda_no); - ga_init2(&newlines, (int)sizeof(char_u *), 1); if (ga_grow(&newlines, 1) == FAIL) goto errret; - /* Add "return " before the expression. - * TODO: Support multiple expressions. */ + /* Add "return " before the expression. */ len = 7 + e - s + 1; p = (char_u *)alloc(len); if (p == NULL) goto errret; ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p; STRCPY(p, "return "); - STRNCPY(p + 7, s, e - s); - p[7 + e - s] = NUL; + vim_strncpy(p + 7, s, e - s); fp->uf_refcount = 1; STRCPY(fp->uf_name, name); hash_add(&func_hashtab, UF2HIKEY(fp)); fp->uf_args = newargs; fp->uf_lines = newlines; + if (current_funccal != NULL && eval_lavars) + { + fp->uf_scoped = current_funccal; + current_funccal->fc_refcount++; + if (ga_grow(¤t_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 fp->uf_tml_count = NULL; @@ -341,15 +368,15 @@ get_lambda_tv(char_u **arg, typval_T *re rettv->vval.v_string = vim_strsave(name); rettv->v_type = VAR_FUNC; } - else - ga_clear_strings(&newargs); - + + eval_lavars_used = old_eval_lavars; return OK; errret: ga_clear_strings(&newargs); ga_clear_strings(&newlines); vim_free(fp); + eval_lavars_used = old_eval_lavars; return FAIL; } @@ -624,6 +651,15 @@ free_funccal( int free_val) /* a: vars were allocated */ { 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 * allocated variables. */ @@ -637,6 +673,16 @@ free_funccal( for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next) 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); } @@ -696,6 +742,11 @@ call_user_func( /* Check if this function has a breakpoint. */ fc->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0); 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, "", 8) == 0) islambda = TRUE; @@ -758,7 +809,6 @@ call_user_func( for (i = 0; i < argcount; ++i) { int addlocal = FALSE; - dictitem_T *v2; ai = i - fp->uf_args.ga_len; if (ai < 0) @@ -778,9 +828,6 @@ call_user_func( { v = &fc->fixvar[fixvar_idx++].var; v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - - if (addlocal) - v2 = v; } else { @@ -789,36 +836,23 @@ call_user_func( if (v == NULL) break; 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); - hash_add(&fc->l_avars.dv_hashtab, DI2HIKEY(v)); /* Note: the values are copied directly to avoid alloc/free. * "argvars" must have VAR_FIXED for v_lock. */ v->di_tv = argvars[i]; 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) { - STRCPY(v2->di_key, name); - copy_tv(&v->di_tv, &v2->di_tv); - v2->di_tv.v_lock = VAR_FIXED; - hash_add(&fc->l_vars.dv_hashtab, DI2HIKEY(v2)); + /* Named arguments should be accessed without the "a:" prefix in + * lambda expressions. Add to the l: dict. */ + copy_tv(&v->di_tv, &v->di_tv); + 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) { @@ -1014,7 +1048,8 @@ call_user_func( * free the funccall_T and what's in it. */ if (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) + && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT + && fc->fc_refcount <= 0) { free_funccal(fc, FALSE); } @@ -1049,6 +1084,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. */ static void @@ -1072,6 +1153,8 @@ func_free(ufunc_T *fp) else hash_remove(&func_hashtab, hi); + funccal_unref(fp->uf_scoped, fp); + vim_free(fp); } @@ -2216,6 +2299,7 @@ ex_function(exarg_T *eap) } fp->uf_args = newargs; fp->uf_lines = newlines; + fp->uf_scoped = NULL; #ifdef FEAT_PROFILE fp->uf_tml_count = NULL; fp->uf_tml_total = NULL; @@ -2705,7 +2789,8 @@ can_free_funccal(funccall_T *fc, int cop { return (fc->l_varlist.lv_copyID != copyID && fc->l_vars.dv_copyID != copyID - && fc->l_avars.dv_copyID != copyID); + && fc->l_avars.dv_copyID != copyID + && fc->fc_copyID != copyID); } /* @@ -3451,6 +3536,40 @@ get_current_funccal_dict(hashtab_T *ht) } /* + * 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. */ int @@ -3461,6 +3580,7 @@ set_ref_in_previous_funccal(int copyID) 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, NULL); 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) { + 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_avars.dv_hashtab, copyID, NULL); } @@ -3501,4 +3622,42 @@ set_ref_in_func_args(int copyID) 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 */ 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 */ /**/ + 2119, +/**/ 2118, /**/ 2117,