Mercurial > vim
diff src/vim9execute.c @ 23249:43532077b5ff v8.2.2170
patch 8.2.2170: Vim9: a global function defined in a :def function fails
Commit: https://github.com/vim/vim/commit/f112f30a82f17114d8b08a0fb90928cd19440581
Author: Bram Moolenaar <Bram@vim.org>
Date: Sun Dec 20 17:47:52 2020 +0100
patch 8.2.2170: Vim9: a global function defined in a :def function fails
Problem: Vim9: a global function defined in a :def function fails if it
uses the context.
Solution: Create a partial to store the closure context. (see #7410)
author | Bram Moolenaar <Bram@vim.org> |
---|---|
date | Sun, 20 Dec 2020 18:00:06 +0100 |
parents | 657216220293 |
children | 35583da6397e |
line wrap: on
line diff
--- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -845,6 +845,49 @@ call_eval_func(char_u *name, int argcoun } /* + * When a function reference is used, fill a partial with the information + * needed, especially when it is used as a closure. + */ + static int +fill_partial_and_closure(partial_T *pt, ufunc_T *ufunc, ectx_T *ectx) +{ + pt->pt_func = ufunc; + pt->pt_refcount = 1; + + if (pt->pt_func->uf_flags & FC_CLOSURE) + { + dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + + ectx->ec_dfunc_idx; + + // The closure needs to find arguments and local + // variables in the current stack. + pt->pt_ectx_stack = &ectx->ec_stack; + pt->pt_ectx_frame = ectx->ec_frame_idx; + + // If this function returns and the closure is still + // being used, we need to make a copy of the context + // (arguments and local variables). Store a reference + // to the partial so we can handle that. + if (ga_grow(&ectx->ec_funcrefs, 1) == FAIL) + { + vim_free(pt); + return FAIL; + } + // Extra variable keeps the count of closures created + // in the current function call. + ++(((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_frame_idx + + STACK_FRAME_SIZE + dfunc->df_varcount)->vval.v_number; + + ((partial_T **)ectx->ec_funcrefs.ga_data) + [ectx->ec_funcrefs.ga_len] = pt; + ++pt->pt_refcount; + ++ectx->ec_funcrefs.ga_len; + } + ++pt->pt_func->uf_refcount; + return OK; +} + +/* * Call a "def" function from old Vim script. * Return OK or FAIL. */ @@ -1003,6 +1046,11 @@ call_def_function( ectx.ec_outer_frame = partial->pt_ectx_frame; } } + else if (ufunc->uf_partial != NULL) + { + ectx.ec_outer_stack = ufunc->uf_partial->pt_ectx_stack; + ectx.ec_outer_frame = ufunc->uf_partial->pt_ectx_frame; + } // dummy frame entries for (idx = 0; idx < STACK_FRAME_SIZE; ++idx) @@ -1969,10 +2017,10 @@ call_def_function( // push a function reference to a compiled function case ISN_FUNCREF: { - partial_T *pt = NULL; - dfunc_T *pt_dfunc; - - pt = ALLOC_CLEAR_ONE(partial_T); + partial_T *pt = ALLOC_CLEAR_ONE(partial_T); + dfunc_T *pt_dfunc = ((dfunc_T *)def_functions.ga_data) + + iptr->isn_arg.funcref.fr_func; + if (pt == NULL) goto failed; if (GA_GROW(&ectx.ec_stack, 1) == FAIL) @@ -1980,41 +2028,9 @@ call_def_function( vim_free(pt); goto failed; } - pt_dfunc = ((dfunc_T *)def_functions.ga_data) - + iptr->isn_arg.funcref.fr_func; - pt->pt_func = pt_dfunc->df_ufunc; - pt->pt_refcount = 1; - - if (pt_dfunc->df_ufunc->uf_flags & FC_CLOSURE) - { - dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) - + ectx.ec_dfunc_idx; - - // The closure needs to find arguments and local - // variables in the current stack. - pt->pt_ectx_stack = &ectx.ec_stack; - pt->pt_ectx_frame = ectx.ec_frame_idx; - - // If this function returns and the closure is still - // being used, we need to make a copy of the context - // (arguments and local variables). Store a reference - // to the partial so we can handle that. - if (ga_grow(&ectx.ec_funcrefs, 1) == FAIL) - { - vim_free(pt); - goto failed; - } - // Extra variable keeps the count of closures created - // in the current function call. - tv = STACK_TV_VAR(dfunc->df_varcount); - ++tv->vval.v_number; - - ((partial_T **)ectx.ec_funcrefs.ga_data) - [ectx.ec_funcrefs.ga_len] = pt; - ++pt->pt_refcount; - ++ectx.ec_funcrefs.ga_len; - } - ++pt_dfunc->df_ufunc->uf_refcount; + if (fill_partial_and_closure(pt, pt_dfunc->df_ufunc, + &ectx) == FAIL) + goto failed; tv = STACK_TV_BOT(0); ++ectx.ec_stack.ga_len; @@ -2028,8 +2044,25 @@ call_def_function( case ISN_NEWFUNC: { newfunc_T *newfunc = &iptr->isn_arg.newfunc; - - copy_func(newfunc->nf_lambda, newfunc->nf_global); + ufunc_T *new_ufunc; + + new_ufunc = copy_func( + newfunc->nf_lambda, newfunc->nf_global); + if (new_ufunc != NULL + && (new_ufunc->uf_flags & FC_CLOSURE)) + { + partial_T *pt = ALLOC_CLEAR_ONE(partial_T); + + // Need to create a partial to store the context of the + // function. + if (pt == NULL) + goto failed; + if (fill_partial_and_closure(pt, new_ufunc, + &ectx) == FAIL) + goto failed; + new_ufunc->uf_partial = pt; + --pt->pt_refcount; // not referenced here + } } break; @@ -3114,7 +3147,10 @@ failed: // Deal with any remaining closures, they may be in use somewhere. if (ectx.ec_funcrefs.ga_len > 0) + { handle_closure_in_use(&ectx, FALSE); + ga_clear(&ectx.ec_funcrefs); // TODO: should not be needed? + } estack_pop(); current_sctx = save_current_sctx;