From: Hirohito Higashi Date: Mon, 20 Jul 2026 16:47:57 +0000 (+0000) Subject: patch 9.2.0814: Vim9: E1041 when reloading an autoload script with exported variables X-Git-Tag: v9.2.0814^0 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=cf80f134e95ead988b875e8c21813104f2575390;p=thirdparty%2Fvim.git patch 9.2.0814: Vim9: E1041 when reloading an autoload script with exported variables Problem: Sourcing a Vim9 autoload script more than once fails with E1041 for its exported variables and constants, while exported functions reload without error (ubaldot) Solution: Give exported variables and constants the same clean slate as functions on reload by removing them from the global namespace, so the script body can define them again. Keep classes and enums: objects created from the previous definition still refer to it, so they are not redefined this way (Hirohito Higashi) fixes: #19205 closes: #20726 Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Hirohito Higashi Signed-off-by: Christian Brabandt --- diff --git a/runtime/doc/vim9.txt b/runtime/doc/vim9.txt index 84502f19cf..a0357ce94b 100644 --- a/runtime/doc/vim9.txt +++ b/runtime/doc/vim9.txt @@ -1,4 +1,4 @@ -*vim9.txt* For Vim version 9.2. Last change: 2026 May 21 +*vim9.txt* For Vim version 9.2. Last change: 2026 Jul 20 VIM REFERENCE MANUAL by Bram Moolenaar @@ -404,6 +404,13 @@ and variables are deleted, thus you start with a clean slate. This is useful if you are developing a plugin and want to try a new version. If you renamed something you don't have to worry about the old name still hanging around. +The exported functions and variables of an autoload script live in the global +namespace with the autoload prefix. When such a script is sourced again they +are likewise given a clean slate, so an autoload script can be sourced more +than once. A class or enum is an exception: it cannot be redefined this way +and |E1041| is given, because objects created from the previous definition +would keep referring to it. Restart Vim to load a changed class or enum. + If you do want to keep items, use: > vim9script noclear diff --git a/src/evalvars.c b/src/evalvars.c index f74b1c76ad..854cb1fc00 100644 --- a/src/evalvars.c +++ b/src/evalvars.c @@ -3910,6 +3910,35 @@ delete_var(hashtab_T *ht, hashitem_T *hi) vim_free(di); } +/* + * Delete the exported variables of a reloaded Vim9 autoload script. They live + * in the global namespace with the autoload prefix (e.g. "foo#bar") and are + * recreated when the script body runs again. + */ + void +delete_autoload_export_vars(char_u *prefix) +{ + if (prefix == NULL) + return; + size_t prefixlen = STRLEN(prefix); + + hash_lock(&globvarht); + int todo = (int)globvarht.ht_used; + + for (hashitem_T *hi = globvarht.ht_array; todo > 0; ++hi) + if (!HASHITEM_EMPTY(hi)) + { + dictitem_T *di = HI2DI(hi); + + --todo; + // Keep a class or enum: existing objects still refer to it. + if (di->di_tv.v_type != VAR_CLASS + && STRNCMP(di->di_key, prefix, prefixlen) == 0) + delete_var(&globvarht, hi); + } + hash_unlock(&globvarht); +} + /* * List the value of one internal variable. */ diff --git a/src/proto/evalvars.pro b/src/proto/evalvars.pro index 4c62286d39..e8840d1e26 100644 --- a/src/proto/evalvars.pro +++ b/src/proto/evalvars.pro @@ -77,6 +77,7 @@ void unref_var_dict(dict_T *dict); void vars_clear(hashtab_T *ht); void vars_clear_ext(hashtab_T *ht, int free_val); void delete_var(hashtab_T *ht, hashitem_T *hi); +void delete_autoload_export_vars(char_u *prefix); int before_set_vvar(char_u *varname, dictitem_T *di, typval_T *tv, int copy, int *type_error); void set_var(char_u *name, typval_T *tv, int copy); int set_var_const(char_u *name, scid_T sid, type_T *type_arg, typval_T *tv_arg, int copy, int flags_arg, int var_idx); diff --git a/src/testdir/test_vim9_import.vim b/src/testdir/test_vim9_import.vim index a3bb60ccb1..54bbd3f2aa 100644 --- a/src/testdir/test_vim9_import.vim +++ b/src/testdir/test_vim9_import.vim @@ -3719,4 +3719,83 @@ def Test_import_name_conflict_with_local_variable() v9.CheckScriptSuccess(lines) enddef +" Directly sourcing an autoload script more than once must not fail on its +" exported variables: like functions, they get a clean slate on reload. +def Test_autoload_reload_export_var() + mkdir('Xreloaddir/autoload', 'pR') + var save_rtp = &rtp + exe 'set rtp^=' .. getcwd() .. '/Xreloaddir' + + var lines =<< trim END + vim9script + export var nr = 1 + export const cnr = 2 + export def GetNr(): number + return 3 + enddef + END + writefile(lines, 'Xreloaddir/autoload/Xreload.vim') + source Xreloaddir/autoload/Xreload.vim + assert_equal(1, g:Xreload#nr) + assert_equal(2, g:Xreload#cnr) + assert_equal(3, g:Xreload#GetNr()) + + # Sourcing again with changed values updates them, no E1041. + lines =<< trim END + vim9script + export var nr = 11 + export const cnr = 22 + export def GetNr(): number + return 33 + enddef + END + writefile(lines, 'Xreloaddir/autoload/Xreload.vim') + source Xreloaddir/autoload/Xreload.vim + assert_equal(11, g:Xreload#nr) + assert_equal(22, g:Xreload#cnr) + assert_equal(33, g:Xreload#GetNr()) + + # The type may also change on reload. + lines =<< trim END + vim9script + export var nr = 'text' + END + writefile(lines, 'Xreloaddir/autoload/Xreload.vim') + source Xreloaddir/autoload/Xreload.vim + assert_equal('text', g:Xreload#nr) + + &rtp = save_rtp +enddef + +" A class or enum cannot be redefined by sourcing the script again: existing +" instances would keep the old definition, so E1041 is given instead. +def Test_autoload_reload_export_class() + mkdir('Xreloadcldir/autoload', 'pR') + var save_rtp = &rtp + exe 'set rtp^=' .. getcwd() .. '/Xreloadcldir' + + var lines =<< trim END + vim9script + export class Cls + var v = 1 + endclass + END + writefile(lines, 'Xreloadcldir/autoload/Xcls.vim') + source Xreloadcldir/autoload/Xcls.vim + assert_fails('source Xreloadcldir/autoload/Xcls.vim', 'E1041:') + + lines =<< trim END + vim9script + export enum Color + Red, + Blue + endenum + END + writefile(lines, 'Xreloadcldir/autoload/Xenum.vim') + source Xreloadcldir/autoload/Xenum.vim + assert_fails('source Xreloadcldir/autoload/Xenum.vim', 'E1041:') + + &rtp = save_rtp +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/version.c b/src/version.c index 178090f6b1..90b5ad6dc0 100644 --- 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 */ +/**/ + 814, /**/ 813, /**/ diff --git a/src/vim9script.c b/src/vim9script.c index 4babbeea2a..4719f49f31 100644 --- a/src/vim9script.c +++ b/src/vim9script.c @@ -67,6 +67,9 @@ clear_vim9_scriptlocal_vars(int sid) hash_init(ht); delete_script_functions(sid); + // also the exported variables of an autoload script + delete_autoload_export_vars(SCRIPT_ITEM(sid)->sn_autoload_prefix); + // old imports and script variables are no longer valid free_imports_and_script_vars(sid); }