# HG changeset patch # User Bram Moolenaar # Date 1603197004 -7200 # Node ID 71b57779177d6c0803273fa713133d7b71ded5a7 # Parent 6869968c658708018d695076cfb98379805b58c4 patch 8.2.1870: Vim9: no need to keep all script variables Commit: https://github.com/vim/vim/commit/39ca4127a094d8aca6f77c01be4f3fea506d5cb7 Author: Bram Moolenaar Date: Tue Oct 20 14:25:07 2020 +0200 patch 8.2.1870: Vim9: no need to keep all script variables Problem: Vim9: no need to keep all script variables. Solution: Only keep script variables when a function was defined that could use them. Fix freeing static string on exit. diff --git a/src/ex_eval.c b/src/ex_eval.c --- a/src/ex_eval.c +++ b/src/ex_eval.c @@ -930,16 +930,22 @@ leave_block(cstack_T *cstack) { scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid); int i; + int func_defined = + cstack->cs_flags[cstack->cs_idx] & CSF_FUNC_DEF; for (i = cstack->cs_script_var_len[cstack->cs_idx]; i < si->sn_var_vals.ga_len; ++i) { svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) + i; + // sv_name is set to NULL if it was already removed. This happens + // when it was defined in an inner block and no functions were + // defined there. if (sv->sv_name != NULL) // Remove a variable declared inside the block, if it still - // exists, from sn_vars and move the value into sn_all_vars. - hide_script_var(si, i); + // exists, from sn_vars and move the value into sn_all_vars + // if "func_defined" is non-zero. + hide_script_var(si, i, func_defined); } // TODO: is this needed? diff --git a/src/proto/vim9script.pro b/src/proto/vim9script.pro --- a/src/proto/vim9script.pro +++ b/src/proto/vim9script.pro @@ -9,7 +9,7 @@ int find_exported(int sid, char_u *name, char_u *handle_import(char_u *arg_start, garray_T *gap, int import_sid, evalarg_T *evalarg, void *cctx); char_u *vim9_declare_scriptvar(exarg_T *eap, char_u *arg); void add_vim9_script_var(dictitem_T *di, typval_T *tv, type_T *type); -void hide_script_var(scriptitem_T *si, int idx); +void hide_script_var(scriptitem_T *si, int idx, int func_defined); void free_all_script_vars(scriptitem_T *si); svar_T *find_typval_in_script(typval_T *dest); int check_script_var_type(typval_T *dest, typval_T *value, char_u *name); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -917,6 +917,8 @@ typedef struct { # define CSF_SILENT 0x1000 // "emsg_silent" reset by ":try" // Note that CSF_ELSE is only used when CSF_TRY and CSF_WHILE are unset // (an ":if"), and CSF_SILENT is only used when CSF_TRY is set. +// +#define CSF_FUNC_DEF 0x2000 // a function was defined in this block /* * What's pending for being reactivated at the ":endtry" of this try diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim --- a/src/testdir/test_vim9_script.vim +++ b/src/testdir/test_vim9_script.vim @@ -296,6 +296,25 @@ def Test_block_local_vars() delete('Xdidit') enddef +def Test_block_local_vars_with_func() + var lines =<< trim END + vim9script + if true + var foo = 'foo' + if true + var bar = 'bar' + def Func(): list + return [foo, bar] + enddef + endif + endif + # function is compiled here, after blocks have finished, can still access + # "foo" and "bar" + assert_equal(['foo', 'bar'], Func()) + END + CheckScriptSuccess(lines) +enddef + func g:NoSuchFunc() echo 'none' endfunc diff --git a/src/userfunc.c b/src/userfunc.c --- a/src/userfunc.c +++ b/src/userfunc.c @@ -3471,27 +3471,35 @@ define_function(exarg_T *eap, char_u *na if (eap->cmdidx == CMD_def) { - int lnum_save = SOURCING_LNUM; + int lnum_save = SOURCING_LNUM; + cstack_T *cstack = eap->cstack; fp->uf_def_status = UF_TO_BE_COMPILED; // error messages are for the first function line SOURCING_LNUM = sourcing_lnum_top; - if (eap->cstack != NULL && eap->cstack->cs_idx >= 0) + if (cstack != NULL && cstack->cs_idx >= 0) { - int count = eap->cstack->cs_idx + 1; + int count = cstack->cs_idx + 1; + int i; // The block context may be needed for script variables declared in - // a block visible now but not when the function is compiled. + // a block that is visible now but not when the function is called + // later. fp->uf_block_ids = ALLOC_MULT(int, count); if (fp->uf_block_ids != NULL) { - mch_memmove(fp->uf_block_ids, eap->cstack->cs_block_id, + mch_memmove(fp->uf_block_ids, cstack->cs_block_id, sizeof(int) * count); fp->uf_block_depth = count; } - // TODO: set flag in each block to indicate a function was defined + + // Set flag in each block to indicate a function was defined. This + // is used to keep the variable when leaving the block, see + // hide_script_var(). + for (i = 0; i <= cstack->cs_idx; ++i) + cstack->cs_flags[i] |= CSF_FUNC_DEF; } // parse the argument types diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -751,6 +751,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1870, +/**/ 1869, /**/ 1868, diff --git a/src/vim9script.c b/src/vim9script.c --- a/src/vim9script.c +++ b/src/vim9script.c @@ -51,8 +51,8 @@ ex_vim9script(exarg_T *eap) if (STRCMP(p_cpo, CPO_VIM) != 0) { - si->sn_save_cpo = p_cpo; - p_cpo = vim_strsave((char_u *)CPO_VIM); + si->sn_save_cpo = vim_strsave(p_cpo); + set_option_value((char_u *)"cpo", 0L, (char_u *)CPO_VIM, 0); } } @@ -569,8 +569,8 @@ vim9_declare_scriptvar(exarg_T *eap, cha } /* - * Vim9 part of adding a script variable: add it to sn_all_vars and - * sn_var_vals. + * Vim9 part of adding a script variable: add it to sn_all_vars (lookup by name + * with a hashtable) and sn_var_vals (lookup by index). * When "type" is NULL use "tv" for the type. */ void @@ -628,9 +628,11 @@ add_vim9_script_var(dictitem_T *di, typv /* * Hide a script variable when leaving a block. * "idx" is de index in sn_var_vals. + * When "func_defined" is non-zero then a function was defined in this block, + * the variable may be accessed by it. Otherwise the variable can be cleared. */ void -hide_script_var(scriptitem_T *si, int idx) +hide_script_var(scriptitem_T *si, int idx, int func_defined) { svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) + idx; hashtab_T *script_ht = get_script_local_ht(); @@ -639,6 +641,7 @@ hide_script_var(scriptitem_T *si, int id hashitem_T *all_hi; // Remove a variable declared inside the block, if it still exists. + // If it was added in a nested block it will already have been removed. // The typval is moved into the sallvar_T. script_hi = hash_find(script_ht, sv->sv_name); all_hi = hash_find(all_ht, sv->sv_name); @@ -646,19 +649,36 @@ hide_script_var(scriptitem_T *si, int id { dictitem_T *di = HI2DI(script_hi); sallvar_T *sav = HI2SAV(all_hi); + sallvar_T *sav_prev = NULL; // There can be multiple entries with the same name in different // blocks, find the right one. while (sav != NULL && sav->sav_var_vals_idx != idx) + { + sav_prev = sav; sav = sav->sav_next; + } if (sav != NULL) { - sav->sav_tv = di->di_tv; - di->di_tv.v_type = VAR_UNKNOWN; - sav->sav_flags = di->di_flags; - sav->sav_di = NULL; + if (func_defined) + { + // move the typval from the dictitem to the sallvar + sav->sav_tv = di->di_tv; + di->di_tv.v_type = VAR_UNKNOWN; + sav->sav_flags = di->di_flags; + sav->sav_di = NULL; + sv->sv_tv = &sav->sav_tv; + } + else + { + if (sav_prev == NULL) + hash_remove(all_ht, all_hi); + else + sav_prev->sav_next = sav->sav_next; + sv->sv_name = NULL; + vim_free(sav); + } delete_var(script_ht, script_hi); - sv->sv_tv = &sav->sav_tv; } } }