# HG changeset patch # User Bram Moolenaar # Date 1602759605 -7200 # Node ID 107eae953b8703f10a7be0b142603f77bd180900 # Parent a421723753117d8e48ac564da3431507d30765fb patch 8.2.1846: Vim9: block variables are not found in compiled function Commit: https://github.com/vim/vim/commit/fbbcd00367e1a4fafd047d42ffce0e5dce88925c Author: Bram Moolenaar Date: Thu Oct 15 12:46:44 2020 +0200 patch 8.2.1846: Vim9: block variables are not found in compiled function Problem: Vim9: variables declared in a local block are not found in when a function is compiled. Solution: Look for script variables in sn_all_vars. diff --git a/src/ex_eval.c b/src/ex_eval.c --- a/src/ex_eval.c +++ b/src/ex_eval.c @@ -918,7 +918,8 @@ enter_block(cstack_T *cstack) scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid); cstack->cs_script_var_len[cstack->cs_idx] = si->sn_var_vals.ga_len; - cstack->cs_block_id[cstack->cs_idx] = ++si->sn_current_block_id; + cstack->cs_block_id[cstack->cs_idx] = ++si->sn_last_block_id; + si->sn_current_block_id = si->sn_last_block_id; } } @@ -938,11 +939,16 @@ leave_block(cstack_T *cstack) 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, sv); + hide_script_var(si, i); } // TODO: is this needed? cstack->cs_script_var_len[cstack->cs_idx] = si->sn_var_vals.ga_len; + + if (cstack->cs_idx == 0) + si->sn_current_block_id = 0; + else + si->sn_current_block_id = cstack->cs_block_id[cstack->cs_idx - 1]; } --cstack->cs_idx; } diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro --- a/src/proto/userfunc.pro +++ b/src/proto/userfunc.pro @@ -29,7 +29,7 @@ int call_func(char_u *funcname, int len, char_u *printable_func_name(ufunc_T *fp); char_u *trans_function_name(char_u **pp, int *is_global, int skip, int flags, funcdict_T *fdp, partial_T **partial); char_u *untrans_function_name(char_u *name); -ufunc_T *def_function(exarg_T *eap, char_u *name_arg); +ufunc_T *define_function(exarg_T *eap, char_u *name_arg); void ex_function(exarg_T *eap); void ex_defcompile(exarg_T *eap); int eval_fname_script(char_u *p); diff --git a/src/proto/vim9compile.pro b/src/proto/vim9compile.pro --- a/src/proto/vim9compile.pro +++ b/src/proto/vim9compile.pro @@ -1,7 +1,7 @@ /* vim9compile.c */ int check_defined(char_u *p, size_t len, cctx_T *cctx); int check_compare_types(exptype_T type, typval_T *tv1, typval_T *tv2); -int get_script_item_idx(int sid, char_u *name, int check_writable); +int get_script_item_idx(int sid, char_u *name, int check_writable, cctx_T *cctx); imported_T *find_imported(char_u *name, size_t len, cctx_T *cctx); imported_T *find_imported_in_script(char_u *name, size_t len, int sid); int vim9_comment_start(char_u *p); diff --git a/src/proto/vim9script.pro b/src/proto/vim9script.pro --- a/src/proto/vim9script.pro +++ b/src/proto/vim9script.pro @@ -5,11 +5,11 @@ int not_in_vim9(exarg_T *eap); void ex_export(exarg_T *eap); void free_imports_and_script_vars(int sid); void ex_import(exarg_T *eap); -int find_exported(int sid, char_u *name, ufunc_T **ufunc, type_T **type); +int find_exported(int sid, char_u *name, ufunc_T **ufunc, type_T **type, cctx_T *cctx); 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, svar_T *sv); +void hide_script_var(scriptitem_T *si, int idx); 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 @@ -1582,6 +1582,8 @@ typedef struct char_u *uf_va_name; // name from "...name" or NULL type_T *uf_va_type; // type from "...name: type" or NULL type_T *uf_func_type; // type of the function, &t_func_any if unknown + int uf_block_depth; // nr of entries in uf_block_ids + int *uf_block_ids; // blocks a :def function is defined inside # if defined(FEAT_LUA) cfunc_T uf_cb; // callback function for cfunc cfunc_free_T uf_cb_free; // callback function to free cfunc @@ -1792,7 +1794,8 @@ typedef struct garray_T sn_imports; // imported items, imported_T garray_T sn_type_list; // keeps types used by variables - int sn_current_block_id; // Unique ID for each script block + int sn_current_block_id; // ID for current block, 0 for outer + int sn_last_block_id; // Unique ID for each script block int sn_version; // :scriptversion int sn_had_command; // TRUE if any command was executed 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 @@ -250,6 +250,36 @@ def Test_block_failure() CheckDefFailure(['{', 'echo 1'], 'E1026:') enddef +def Test_block_local_vars() + var lines =<< trim END + vim9script + if true + var text = 'hello' + def SayHello(): string + return text + enddef + def SetText(v: string) + text = v + enddef + endif + + if true + var text = 'again' + def SayAgain(): string + return text + enddef + endif + defcompile + + assert_equal('hello', SayHello()) + assert_equal('again', SayAgain()) + + SetText('foobar') + assert_equal('foobar', SayHello()) + END + CheckScriptSuccess(lines) +enddef + func g:NoSuchFunc() echo 'none' endfunc @@ -1265,15 +1295,16 @@ def Test_import_absolute() assert_equal(9876, g:imported_abs) assert_equal(8888, g:imported_after) - assert_match('\d\+_UseExported.*' .. - 'g:imported_abs = exported.*' .. - '0 LOADSCRIPT exported from .*Xexport_abs.vim.*' .. - '1 STOREG g:imported_abs.*' .. - 'exported = 8888.*' .. - '3 STORESCRIPT exported in .*Xexport_abs.vim.*' .. - 'g:imported_after = exported.*' .. - '4 LOADSCRIPT exported from .*Xexport_abs.vim.*' .. - '5 STOREG g:imported_after.*', + assert_match('\d\+_UseExported\_s*' .. + 'g:imported_abs = exported\_s*' .. + '0 LOADSCRIPT exported-2 from .*Xexport_abs.vim\_s*' .. + '1 STOREG g:imported_abs\_s*' .. + 'exported = 8888\_s*' .. + '2 PUSHNR 8888\_s*' .. + '3 STORESCRIPT exported-2 in .*Xexport_abs.vim\_s*' .. + 'g:imported_after = exported\_s*' .. + '4 LOADSCRIPT exported-2 from .*Xexport_abs.vim\_s*' .. + '5 STOREG g:imported_after', g:import_disassembled) Undo_export_script_lines() diff --git a/src/userfunc.c b/src/userfunc.c --- a/src/userfunc.c +++ b/src/userfunc.c @@ -1134,6 +1134,7 @@ func_clear_items(ufunc_T *fp) ga_clear_strings(&(fp->uf_lines)); VIM_CLEAR(fp->uf_arg_types); VIM_CLEAR(fp->uf_def_arg_idx); + VIM_CLEAR(fp->uf_block_ids); VIM_CLEAR(fp->uf_va_name); clear_type_list(&fp->uf_type_list); @@ -2658,7 +2659,7 @@ list_functions(regmatch_T *regmatch) * Returns a pointer to the function or NULL if no function defined. */ ufunc_T * -def_function(exarg_T *eap, char_u *name_arg) +define_function(exarg_T *eap, char_u *name_arg) { char_u *theline; char_u *line_to_free = NULL; @@ -3477,9 +3478,24 @@ def_function(exarg_T *eap, char_u *name_ // error messages are for the first function line SOURCING_LNUM = sourcing_lnum_top; + if (eap->cstack != NULL && eap->cstack->cs_idx >= 0) + { + int count = eap->cstack->cs_idx + 1; + + // The block context may be needed for script variables declared in + // a block visible now but not when the function is compiled. + 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, + sizeof(int) * count); + fp->uf_block_depth = count; + } + // TODO: set flag in each block to indicate a function was defined + } + // parse the argument types ga_init2(&fp->uf_type_list, sizeof(type_T *), 10); - if (argtypes.ga_len > 0) { // When "varargs" is set the last name/type goes into uf_va_name @@ -3608,7 +3624,7 @@ ret_free: void ex_function(exarg_T *eap) { - (void)def_function(eap, NULL); + (void)define_function(eap, NULL); } /* 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 */ /**/ + 1846, +/**/ 1845, /**/ 1844, diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -195,7 +195,7 @@ lookup_local(char_u *name, size_t len, c * Returns OK when found, FAIL otherwise. */ static int -lookup_arg( +arg_exists( char_u *name, size_t len, int *idxp, @@ -247,7 +247,7 @@ lookup_arg( if (cctx->ctx_outer != NULL) { // Lookup the name for an argument of the outer function. - if (lookup_arg(name, len, idxp, type, gen_load_outer, cctx->ctx_outer) + if (arg_exists(name, len, idxp, type, gen_load_outer, cctx->ctx_outer) == OK) { *gen_load_outer = TRUE; @@ -259,6 +259,57 @@ lookup_arg( } /* + * Lookup a script-local variable in the current script, possibly defined in a + * block that contains the function "cctx->ctx_ufunc". + * "cctx" is NULL at the script level. + * if "len" is <= 0 "name" must be NUL terminated. + * Return NULL when not found. + */ + static sallvar_T * +find_script_var(char_u *name, size_t len, cctx_T *cctx) +{ + scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid); + hashitem_T *hi; + int cc; + sallvar_T *sav; + ufunc_T *ufunc; + + // Find the list of all script variables with the right name. + if (len > 0) + { + cc = name[len]; + name[len] = NUL; + } + hi = hash_find(&si->sn_all_vars.dv_hashtab, name); + if (len > 0) + name[len] = cc; + if (HASHITEM_EMPTY(hi)) + return NULL; + + sav = HI2SAV(hi); + if (sav->sav_block_id == 0 || cctx == NULL) + // variable defined in the script scope or not in a function. + return sav; + + // Go over the variables with this name and find one that was visible + // from the function. + ufunc = cctx->ctx_ufunc; + while (sav != NULL) + { + int idx; + + // Go over the blocks that this function was defined in. If the + // variable block ID matches it was visible to the function. + for (idx = 0; idx < ufunc->uf_block_depth; ++idx) + if (ufunc->uf_block_ids[idx] == sav->sav_block_id) + return sav; + sav = sav->sav_next; + } + + return NULL; +} + +/* * Returnd TRUE if the script context is Vim9 script. */ static int @@ -268,33 +319,50 @@ script_is_vim9() } /* - * Lookup a variable in the current script. + * Lookup a variable (without s: prefix) in the current script. * If "vim9script" is TRUE the script must be Vim9 script. Used for "var" * without "s:". + * "cctx" is NULL at the script level. * Returns OK or FAIL. */ static int -lookup_script(char_u *name, size_t len, int vim9script) -{ - int cc; - hashtab_T *ht; - dictitem_T *di; +script_var_exists(char_u *name, size_t len, int vim9script, cctx_T *cctx) +{ + int is_vim9_script; if (current_sctx.sc_sid <= 0) return FAIL; - ht = &SCRIPT_VARS(current_sctx.sc_sid); - if (vim9script && !script_is_vim9()) + is_vim9_script = script_is_vim9(); + if (vim9script && !is_vim9_script) return FAIL; - cc = name[len]; - name[len] = NUL; - di = find_var_in_ht(ht, 0, name, TRUE); - name[len] = cc; - return di == NULL ? FAIL: OK; + if (is_vim9_script) + { + // Check script variables that were visible where the function was + // defined. + if (find_script_var(name, len, cctx) != NULL) + return OK; + } + else + { + hashtab_T *ht = &SCRIPT_VARS(current_sctx.sc_sid); + dictitem_T *di; + int cc; + + // Check script variables that are currently visible + cc = name[len]; + name[len] = NUL; + di = find_var_in_ht(ht, 0, name, TRUE); + name[len] = cc; + if (di != NULL) + return OK; + } + + return FAIL; } /* * Check if "p[len]" is already defined, either in script "import_sid" or in - * compilation context "cctx". + * compilation context "cctx". "cctx" is NULL at the script level. * Does not check the global namespace. * Return FAIL and give an error if it defined. */ @@ -305,10 +373,10 @@ check_defined(char_u *p, size_t len, cct ufunc_T *ufunc = NULL; p[len] = NUL; - if (lookup_script(p, len, FALSE) == OK + if (script_var_exists(p, len, FALSE, cctx) == OK || (cctx != NULL && (lookup_local(p, len, cctx) != NULL - || lookup_arg(p, len, NULL, NULL, NULL, cctx) == OK)) + || arg_exists(p, len, NULL, NULL, NULL, cctx) == OK)) || find_imported(p, len, cctx) != NULL || (ufunc = find_func_even_dead(p, FALSE, cctx)) != NULL) { @@ -1699,7 +1767,7 @@ reserve_local( { lvar_T *lvar; - if (lookup_arg(name, len, NULL, NULL, NULL, cctx) == OK) + if (arg_exists(name, len, NULL, NULL, NULL, cctx) == OK) { emsg_namelen(_(e_str_is_used_as_argument), name, (int)len); return NULL; @@ -1760,16 +1828,30 @@ free_locals(cctx_T *cctx) * If not found returns -2. */ int -get_script_item_idx(int sid, char_u *name, int check_writable) +get_script_item_idx(int sid, char_u *name, int check_writable, cctx_T *cctx) { hashtab_T *ht; dictitem_T *di; scriptitem_T *si = SCRIPT_ITEM(sid); + svar_T *sv; int idx; + if (!SCRIPT_ID_VALID(sid)) + return -1; + if (sid == current_sctx.sc_sid) + { + sallvar_T *sav = find_script_var(name, (size_t)-1, cctx); + + if (sav == NULL) + return -2; + idx = sav->sav_var_vals_idx; + sv = ((svar_T *)si->sn_var_vals.ga_data) + idx; + if (check_writable && sv->sv_const) + semsg(_(e_readonlyvar), name); + return idx; + } + // First look the name up in the hashtable. - if (!SCRIPT_ID_VALID(sid)) - return -1; ht = &SCRIPT_VARS(sid); di = find_var_in_ht(ht, 0, name, TRUE); if (di == NULL) @@ -1778,8 +1860,7 @@ get_script_item_idx(int sid, char_u *nam // Now find the svar_T index in sn_var_vals. for (idx = 0; idx < si->sn_var_vals.ga_len; ++idx) { - svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) + idx; - + sv = ((svar_T *)si->sn_var_vals.ga_data) + idx; if (sv->sv_tv == &di->di_tv) { if (check_writable && sv->sv_const) @@ -2083,7 +2164,7 @@ compile_load_scriptvar( if (!SCRIPT_ID_VALID(current_sctx.sc_sid)) return FAIL; si = SCRIPT_ITEM(current_sctx.sc_sid); - idx = get_script_item_idx(current_sctx.sc_sid, name, FALSE); + idx = get_script_item_idx(current_sctx.sc_sid, name, FALSE, cctx); if (idx == -1 || si->sn_version != SCRIPT_VERSION_VIM9) { // variable is not in sn_var_vals: old style script. @@ -2130,7 +2211,7 @@ compile_load_scriptvar( cc = *p; *p = NUL; - idx = find_exported(import->imp_sid, exp_name, &ufunc, &type); + idx = find_exported(import->imp_sid, exp_name, &ufunc, &type, cctx); *p = cc; p = skipwhite(p); @@ -2257,7 +2338,7 @@ compile_load( if (name == NULL) return FAIL; - if (lookup_arg(*arg, len, &idx, &type, &gen_load_outer, cctx) == OK) + if (arg_exists(*arg, len, &idx, &type, &gen_load_outer, cctx) == OK) { if (!gen_load_outer) gen_load = TRUE; @@ -2279,7 +2360,7 @@ compile_load( { // "var" can be script-local even without using "s:" if it // already exists in a Vim9 script or when it's imported. - if (lookup_script(*arg, len, TRUE) == OK + if (script_var_exists(*arg, len, TRUE, cctx) == OK || find_imported(name, 0, cctx) != NULL) res = compile_load_scriptvar(cctx, name, *arg, &end, FALSE); @@ -4468,7 +4549,7 @@ compile_nested_function(exarg_T *eap, cc eap->skip = cctx->ctx_skip == SKIP_YES; eap->forceit = FALSE; lambda_name = get_lambda_name(); - ufunc = def_function(eap, lambda_name); + ufunc = define_function(eap, lambda_name); if (ufunc == NULL) return eap->skip ? (char_u *)"" : NULL; @@ -4494,12 +4575,25 @@ compile_nested_function(exarg_T *eap, cc // Define a local variable for the function reference. lvar_T *lvar = reserve_local(cctx, name_start, name_end - name_start, TRUE, ufunc->uf_func_type); + int block_depth = cctx->ctx_ufunc->uf_block_depth; if (lvar == NULL) return NULL; if (generate_FUNCREF(cctx, ufunc) == FAIL) return NULL; r = generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL); + + // copy over the block scope IDs + if (block_depth > 0) + { + ufunc->uf_block_ids = ALLOC_MULT(int, block_depth); + if (ufunc->uf_block_ids != NULL) + { + mch_memmove(ufunc->uf_block_ids, cctx->ctx_ufunc->uf_block_ids, + sizeof(int) * block_depth); + ufunc->uf_block_depth = block_depth; + } + } } // TODO: warning for trailing text? @@ -4900,7 +4994,7 @@ compile_assignment(char_u *arg, exarg_T if (lvar == NULL) { CLEAR_FIELD(arg_lvar); - if (lookup_arg(var_start, varlen, + if (arg_exists(var_start, varlen, &arg_lvar.lv_idx, &arg_lvar.lv_type, &arg_lvar.lv_from_outer, cctx) == OK) { @@ -4925,8 +5019,10 @@ compile_assignment(char_u *arg, exarg_T int script_namespace = varlen > 1 && STRNCMP(var_start, "s:", 2) == 0; int script_var = (script_namespace - ? lookup_script(var_start + 2, varlen - 2, FALSE) - : lookup_script(var_start, varlen, TRUE)) == OK; + ? script_var_exists(var_start + 2, varlen - 2, + FALSE, cctx) + : script_var_exists(var_start, varlen, + TRUE, cctx)) == OK; imported_T *import = find_imported(var_start, varlen, cctx); @@ -4962,7 +5058,7 @@ compile_assignment(char_u *arg, exarg_T if (SCRIPT_ID_VALID(scriptvar_sid)) { scriptvar_idx = get_script_item_idx(scriptvar_sid, - rawname, TRUE); + rawname, TRUE, cctx); if (scriptvar_idx >= 0) { scriptitem_T *si = SCRIPT_ITEM(scriptvar_sid); @@ -6964,9 +7060,10 @@ compile_def_function(ufunc_T *ufunc, int || *ea.cmd == '@' || ((len) > 2 && ea.cmd[1] == ':') || lookup_local(ea.cmd, len, &cctx) != NULL - || lookup_arg(ea.cmd, len, NULL, NULL, + || arg_exists(ea.cmd, len, NULL, NULL, NULL, &cctx) == OK - || lookup_script(ea.cmd, len, FALSE) == OK + || script_var_exists(ea.cmd, len, + FALSE, &cctx) == OK || find_imported(ea.cmd, len, &cctx) != NULL) { line = compile_assignment(ea.cmd, &ea, CMD_SIZE, &cctx); diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -2962,8 +2962,10 @@ ex_disassemble(exarg_T *eap) svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) + iptr->isn_arg.script.script_idx; - smsg("%4d LOADSCRIPT %s from %s", current, - sv->sv_name, si->sn_name); + smsg("%4d LOADSCRIPT %s-%d from %s", current, + sv->sv_name, + iptr->isn_arg.script.script_idx, + si->sn_name); } break; case ISN_LOADS: @@ -3054,8 +3056,10 @@ ex_disassemble(exarg_T *eap) svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) + iptr->isn_arg.script.script_idx; - smsg("%4d STORESCRIPT %s in %s", current, - sv->sv_name, si->sn_name); + smsg("%4d STORESCRIPT %s-%d in %s", current, + sv->sv_name, + iptr->isn_arg.script.script_idx, + si->sn_name); } break; case ISN_STOREOPT: diff --git a/src/vim9script.c b/src/vim9script.c --- a/src/vim9script.c +++ b/src/vim9script.c @@ -193,7 +193,8 @@ find_exported( int sid, char_u *name, ufunc_T **ufunc, - type_T **type) + type_T **type, + cctx_T *cctx) { int idx = -1; svar_T *sv; @@ -201,7 +202,7 @@ find_exported( // find name in "script" // TODO: also find script-local user function - idx = get_script_item_idx(sid, name, FALSE); + idx = get_script_item_idx(sid, name, FALSE, cctx); if (idx >= 0) { sv = ((svar_T *)script->sn_var_vals.ga_data) + idx; @@ -248,6 +249,7 @@ find_exported( /* * Handle an ":import" command and add the resulting imported_T to "gap", when * not NULL, or script "import_sid" sn_imports. + * "cctx" is NULL at the script level. * Returns a pointer to after the command or NULL in case of failure */ char_u * @@ -461,7 +463,7 @@ handle_import( ufunc_T *ufunc = NULL; type_T *type; - idx = find_exported(sid, name, &ufunc, &type); + idx = find_exported(sid, name, &ufunc, &type, cctx); if (idx < 0 && ufunc == NULL) goto erret; @@ -623,9 +625,14 @@ add_vim9_script_var(dictitem_T *di, typv } } +/* + * Hide a script variable when leaving a block. + * "idx" is de index in sn_var_vals. + */ void -hide_script_var(scriptitem_T *si, svar_T *sv) +hide_script_var(scriptitem_T *si, int idx) { + svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) + idx; hashtab_T *script_ht = get_script_local_ht(); hashtab_T *all_ht = &si->sn_all_vars.dv_hashtab; hashitem_T *script_hi; @@ -640,11 +647,19 @@ hide_script_var(scriptitem_T *si, svar_T dictitem_T *di = HI2DI(script_hi); sallvar_T *sav = HI2SAV(all_hi); - sav->sav_tv = di->di_tv; - di->di_tv.v_type = VAR_UNKNOWN; - sav->sav_flags = di->di_flags; - sav->sav_di = NULL; - delete_var(script_ht, script_hi); + // 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 = 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; + delete_var(script_ht, script_hi); + sv->sv_tv = &sav->sav_tv; + } } }