To: vim_dev@googlegroups.com Subject: Patch 8.2.1845 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.1845 Problem: Vim9: function defined in a block can't use variables defined in that block. Solution: First step: Make a second hashtab that holds all script variables, also block-local ones, with more information. Files: src/structs.h, src/evalvars.c, src/ex_eval.c, src/vim9script.c, src/proto/vim9script.pro, src/scriptfile.c *** ../vim-8.2.1844/src/structs.h 2020-10-10 21:33:42.403033529 +0200 --- src/structs.h 2020-10-14 15:40:16.240730744 +0200 *************** *** 889,894 **** --- 889,895 ---- } cs_pend; void *cs_forinfo[CSTACK_LEN]; // info used by ":for" int cs_line[CSTACK_LEN]; // line nr of ":while"/":for" line + int cs_block_id[CSTACK_LEN]; // block ID stack int cs_script_var_len[CSTACK_LEN]; // value of sn_var_vals.ga_len // when entering the block int cs_idx; // current entry, or -1 if none *************** *** 1701,1718 **** * Holds the hashtab with variables local to each sourced script. * Each item holds a variable (nameless) that points to the dict_T. */ ! typedef struct ! { dictitem_T sv_var; dict_T sv_dict; } scriptvar_T; /* * Entry for "sn_var_vals". Used for script-local variables. */ typedef struct { ! char_u *sv_name; // points into "sn_vars" di_key ! typval_T *sv_tv; // points into "sn_vars" di_tv type_T *sv_type; int sv_const; int sv_export; // "export let var = val" --- 1702,1748 ---- * Holds the hashtab with variables local to each sourced script. * Each item holds a variable (nameless) that points to the dict_T. */ ! typedef struct { dictitem_T sv_var; dict_T sv_dict; } scriptvar_T; /* + * Entry for "sn_all_vars". Contains the s: variables from sn_vars plus the + * block-local ones. + */ + typedef struct sallvar_S sallvar_T; + struct sallvar_S { + sallvar_T *sav_next; // var with same name but different block + int sav_block_id; // block ID where declared + int sav_var_vals_idx; // index in sn_var_vals + + // So long as the variable is valid (block it was defined in is still + // active) "sav_di" is used. It is set to NULL when leaving the block, + // then sav_tv and sav_flags are used. + dictitem_T *sav_di; // dictitem with di_key and di_tv + typval_T sav_tv; // type and value of the variable + char_u sav_flags; // DI_FLAGS_ flags (only used for variable) + char_u sav_key[1]; // key (actually longer!) + }; + + /* + * In the sn_all_vars hashtab item "hi_key" points to "sav_key" in a sallvar_T. + * This makes it possible to store and find the sallvar_T. + * SAV2HIKEY() converts a sallvar_T pointer to a hashitem key pointer. + * HIKEY2SAV() converts a hashitem key pointer to a sallvar_T pointer. + * HI2SAV() converts a hashitem pointer to a sallvar_T pointer. + */ + #define SAV2HIKEY(sav) ((sav)->sav_key) + #define HIKEY2SAV(p) ((sallvar_T *)(p - offsetof(sallvar_T, sav_key))) + #define HI2SAV(hi) HIKEY2SAV((hi)->hi_key) + + /* * Entry for "sn_var_vals". Used for script-local variables. */ typedef struct { ! char_u *sv_name; // points into "sn_all_vars" di_key ! typval_T *sv_tv; // points into "sn_vars" or "sn_all_vars" di_tv type_T *sv_type; int sv_const; int sv_export; // "export let var = val" *************** *** 1742,1753 **** { char_u *sn_name; ! scriptvar_T *sn_vars; // stores s: variables for this script ! garray_T sn_var_vals; // same variables as a list of svar_T garray_T sn_imports; // imported items, imported_T - garray_T sn_type_list; // keeps types used by variables int sn_version; // :scriptversion int sn_had_command; // TRUE if any command was executed --- 1772,1798 ---- { char_u *sn_name; ! // "sn_vars" stores the s: variables currently valid. When leaving a block ! // variables local to that block are removed. ! scriptvar_T *sn_vars; ! ! // Specific for a Vim9 script. ! // "sn_all_vars" stores all script variables ever declared. So long as the ! // variable is still valid the value is in "sn_vars->sv_dict...di_tv". ! // When the block of a declaration is left the value is moved to ! // "sn_all_vars..sav_tv". ! // Variables with duplicate names are possible, the sav_block_id must be ! // used to check that which variable is valid. ! dict_T sn_all_vars; // all script variables, dict of sallvar_T ! ! // Stores the same variables as in "sn_all_vars" as a list of svar_T, so ! // that they can be quickly found by index instead of a hash table lookup. ! // Also stores the type. ! garray_T sn_var_vals; 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_version; // :scriptversion int sn_had_command; // TRUE if any command was executed *** ../vim-8.2.1844/src/evalvars.c 2020-10-10 19:07:04.187713866 +0200 --- src/evalvars.c 2020-10-14 18:51:10.342095521 +0200 *************** *** 2882,2888 **** } } hash_clear(ht); ! ht->ht_used = 0; } /* --- 2882,2888 ---- } } hash_clear(ht); ! hash_init(ht); } /* *************** *** 3142,3171 **** if (flags & ASSIGN_CONST) di->di_flags |= DI_FLAGS_LOCK; if (is_script_local && vim9script) ! { ! scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid); ! ! // Store a pointer to the typval_T, so that it can be found by ! // index instead of using a hastab lookup. ! if (ga_grow(&si->sn_var_vals, 1) == OK) ! { ! svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) ! + si->sn_var_vals.ga_len; ! sv->sv_name = di->di_key; ! sv->sv_tv = &di->di_tv; ! if (type == NULL) ! sv->sv_type = typval2type(tv, &si->sn_type_list); ! else ! sv->sv_type = type; ! sv->sv_const = (flags & ASSIGN_CONST); ! sv->sv_export = is_export; ! ++si->sn_var_vals.ga_len; ! ! // let ex_export() know the export worked. ! is_export = FALSE; ! } ! } } if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) --- 3142,3151 ---- if (flags & ASSIGN_CONST) di->di_flags |= DI_FLAGS_LOCK; + // A Vim9 script-local variable is also added to sn_all_vars and + // sn_var_vals. if (is_script_local && vim9script) ! add_vim9_script_var(di, tv, type); } if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) *** ../vim-8.2.1844/src/ex_eval.c 2020-10-10 21:33:42.403033529 +0200 --- src/ex_eval.c 2020-10-14 19:29:29.494435197 +0200 *************** *** 914,954 **** { ++cstack->cs_idx; if (in_vim9script()) ! cstack->cs_script_var_len[cstack->cs_idx] = ! SCRIPT_ITEM(current_sctx.sc_sid)->sn_var_vals.ga_len; } static void leave_block(cstack_T *cstack) { ! int i; ! ! if (in_vim9script()) { scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid); ! hashtab_T *ht = get_script_local_ht(); ! if (ht != NULL) ! { ! 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; ! hashitem_T *hi; ! if (sv->sv_name != NULL) ! { ! // Remove a variable declared inside the block, if it still ! // exists. ! hi = hash_find(ht, sv->sv_name); ! if (!HASHITEM_EMPTY(hi)) ! { ! delete_var(ht, hi); ! sv->sv_name = NULL; ! } ! } ! } } } --cstack->cs_idx; } --- 914,948 ---- { ++cstack->cs_idx; if (in_vim9script()) ! { ! 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; ! } } static void leave_block(cstack_T *cstack) { ! if (in_vim9script() && SCRIPT_ID_VALID(current_sctx.sc_sid)) { scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid); ! int i; ! 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; ! 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); } + + // TODO: is this needed? + cstack->cs_script_var_len[cstack->cs_idx] = si->sn_var_vals.ga_len; } --cstack->cs_idx; } *** ../vim-8.2.1844/src/vim9script.c 2020-10-08 21:16:38.643643838 +0200 --- src/vim9script.c 2020-10-14 19:28:58.062506167 +0200 *************** *** 134,140 **** * Free all imported items in script "sid". */ void ! free_imports(int sid) { scriptitem_T *si = SCRIPT_ITEM(sid); int idx; --- 134,140 ---- * Free all imported items in script "sid". */ void ! free_imports_and_script_vars(int sid) { scriptitem_T *si = SCRIPT_ITEM(sid); int idx; *************** *** 146,152 **** vim_free(imp->imp_name); } ga_clear(&si->sn_imports); ! ga_clear(&si->sn_var_vals); clear_type_list(&si->sn_type_list); } --- 146,154 ---- vim_free(imp->imp_name); } ga_clear(&si->sn_imports); ! ! free_all_script_vars(si); ! clear_type_list(&si->sn_type_list); } *************** *** 565,570 **** --- 567,693 ---- } /* + * Vim9 part of adding a script variable: add it to sn_all_vars and + * sn_var_vals. + * When "type" is NULL use "tv" for the type. + */ + void + add_vim9_script_var(dictitem_T *di, typval_T *tv, type_T *type) + { + scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid); + + // Store a pointer to the typval_T, so that it can be found by + // index instead of using a hastab lookup. + if (ga_grow(&si->sn_var_vals, 1) == OK) + { + svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) + + si->sn_var_vals.ga_len; + hashitem_T *hi; + sallvar_T *newsav = (sallvar_T *)alloc_clear( + sizeof(sallvar_T) + STRLEN(di->di_key)); + + if (newsav == NULL) + return; + + sv->sv_tv = &di->di_tv; + if (type == NULL) + sv->sv_type = typval2type(tv, &si->sn_type_list); + else + sv->sv_type = type; + sv->sv_const = (di->di_flags & DI_FLAGS_LOCK) ? ASSIGN_CONST : 0; + sv->sv_export = is_export; + newsav->sav_var_vals_idx = si->sn_var_vals.ga_len; + ++si->sn_var_vals.ga_len; + + STRCPY(&newsav->sav_key, di->di_key); + sv->sv_name = newsav->sav_key; + newsav->sav_di = di; + newsav->sav_block_id = si->sn_current_block_id; + + hi = hash_find(&si->sn_all_vars.dv_hashtab, newsav->sav_key); + if (!HASHITEM_EMPTY(hi)) + { + sallvar_T *sav = HI2SAV(hi); + + // variable with this name exists in another block + while (sav->sav_next != NULL) + sav = sav->sav_next; + sav->sav_next = newsav; + } + else + // new variable name + hash_add(&si->sn_all_vars.dv_hashtab, newsav->sav_key); + + // let ex_export() know the export worked. + is_export = FALSE; + } + } + + void + hide_script_var(scriptitem_T *si, svar_T *sv) + { + hashtab_T *script_ht = get_script_local_ht(); + hashtab_T *all_ht = &si->sn_all_vars.dv_hashtab; + hashitem_T *script_hi; + hashitem_T *all_hi; + + // Remove a variable declared inside the block, if it still exists. + // 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); + if (!HASHITEM_EMPTY(script_hi) && !HASHITEM_EMPTY(all_hi)) + { + 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); + } + } + + /* + * Free the script variables from "sn_all_vars". + */ + void + free_all_script_vars(scriptitem_T *si) + { + int todo; + hashtab_T *ht = &si->sn_all_vars.dv_hashtab; + hashitem_T *hi; + sallvar_T *sav; + sallvar_T *sav_next; + + hash_lock(ht); + todo = (int)ht->ht_used; + for (hi = ht->ht_array; todo > 0; ++hi) + { + if (!HASHITEM_EMPTY(hi)) + { + --todo; + + // Free the variable. Don't remove it from the hashtab, ht_array + // might change then. hash_clear() takes care of it later. + sav = HI2SAV(hi); + while (sav != NULL) + { + sav_next = sav->sav_next; + if (sav->sav_di == NULL) + clear_tv(&sav->sav_tv); + vim_free(sav); + sav = sav_next; + } + } + } + hash_clear(ht); + hash_init(ht); + + ga_clear(&si->sn_var_vals); + } + + /* * Find the script-local variable that links to "dest". * Returns NULL if not found. */ *** ../vim-8.2.1844/src/proto/vim9script.pro 2020-10-08 21:16:38.643643838 +0200 --- src/proto/vim9script.pro 2020-10-14 18:51:51.665872905 +0200 *************** *** 3,13 **** void ex_vim9script(exarg_T *eap); int not_in_vim9(exarg_T *eap); void ex_export(exarg_T *eap); ! void free_imports(int sid); void ex_import(exarg_T *eap); int find_exported(int sid, char_u *name, ufunc_T **ufunc, type_T **type); 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); svar_T *find_typval_in_script(typval_T *dest); int check_script_var_type(typval_T *dest, typval_T *value, char_u *name); /* vim: set ft=c : */ --- 3,16 ---- void ex_vim9script(exarg_T *eap); 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); 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 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); /* vim: set ft=c : */ *** ../vim-8.2.1844/src/scriptfile.c 2020-09-11 17:59:19.032235034 +0200 --- src/scriptfile.c 2020-10-14 17:52:46.130048446 +0200 *************** *** 1348,1355 **** } } ! // old imports are no longer valid ! free_imports(sid); // in Vim9 script functions are marked deleted if (is_vim9) --- 1348,1355 ---- } } ! // old imports and script variables are no longer valid ! free_imports_and_script_vars(sid); // in Vim9 script functions are marked deleted if (is_vim9) *************** *** 1375,1380 **** --- 1375,1381 ---- // Allocate the local script variables to use for this script. new_script_vars(script_items.ga_len); ga_init2(&si->sn_var_vals, sizeof(svar_T), 10); + hash_init(&si->sn_all_vars.dv_hashtab); ga_init2(&si->sn_imports, sizeof(imported_T), 10); ga_init2(&si->sn_type_list, sizeof(type_T), 10); # ifdef FEAT_PROFILE *************** *** 1592,1598 **** vim_free(si->sn_vars); vim_free(si->sn_name); ! free_imports(i); free_string_option(si->sn_save_cpo); # ifdef FEAT_PROFILE ga_clear(&si->sn_prl_ga); --- 1593,1599 ---- vim_free(si->sn_vars); vim_free(si->sn_name); ! free_imports_and_script_vars(i); free_string_option(si->sn_save_cpo); # ifdef FEAT_PROFILE ga_clear(&si->sn_prl_ga); *** ../vim-8.2.1844/src/version.c 2020-10-13 22:15:37.486726632 +0200 --- src/version.c 2020-10-14 19:38:11.441167357 +0200 *************** *** 752,753 **** --- 752,755 ---- { /* Add new patch number below this line */ + /**/ + 1845, /**/ -- From "know your smileys": y:-) Bad toupee /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///