changeset 29828:6b7020f3d856 v9.0.0253

patch 9.0.0253: a symlink to an autoload script results in two entries Commit: https://github.com/vim/vim/commit/753885b6c5b9021184daa94d32fd8bf025f1b488 Author: Bram Moolenaar <Bram@vim.org> Date: Wed Aug 24 16:30:36 2022 +0100 patch 9.0.0253: a symlink to an autoload script results in two entries Problem: A symlink to an autoload script results in two entries in the list of scripts, items expected in one are actually in the other. Solution: Have one script item refer to the actually sourced one. (closes #10960)
author Bram Moolenaar <Bram@vim.org>
date Wed, 24 Aug 2022 17:45:03 +0200
parents db296237ca1d
children d3040694216f
files runtime/doc/builtin.txt runtime/doc/repeat.txt src/eval.c src/evalvars.c src/proto/vim9script.pro src/scriptfile.c src/structs.h src/testdir/test_vim9_import.vim src/version.c src/vim9compile.c src/vim9script.c
diffstat 11 files changed, 124 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -253,7 +253,7 @@ getreg([{regname} [, 1 [, {list}]]])
 				String or List   contents of a register
 getreginfo([{regname}])		Dict	information about a register
 getregtype([{regname}])		String	type of a register
-getscriptinfo()		List	list of sourced scripts
+getscriptinfo()			List	list of sourced scripts
 gettabinfo([{expr}])		List	list of tab pages
 gettabvar({nr}, {varname} [, {def}])
 				any	variable {varname} in tab {nr} or {def}
@@ -4089,17 +4089,20 @@ getregtype([{regname}])					*getregtype(
 		Can also be used as a |method|: >
 			GetRegname()->getregtype()
 
-getscriptinfo()					*getscriptinfo()*
+getscriptinfo()						*getscriptinfo()*
 		Returns a |List| with information about all the sourced Vim
-		scripts in the order they were sourced. (|:scriptinfo|)
+		scripts in the order they were sourced, like what
+		`:scriptnames` shows.
 
 		Each item in the returned List is a |Dict| with the following
 		items:
 		    autoload	set to TRUE for a script that was used with
-				|import autoload| but was not actually sourced
-				yet.
+				`import autoload` but was not actually sourced
+				yet (see |import-autoload|).
 		    name	vim script file name.
 		    sid		script ID |<SID>|.
+		    sourced	if this script is an alias this is the script
+				ID of the actually sourced script, otherwise zero
 
 gettabinfo([{tabnr}])					*gettabinfo()*
 		If {tabnr} is not specified, then information about all the
--- a/runtime/doc/repeat.txt
+++ b/runtime/doc/repeat.txt
@@ -417,6 +417,10 @@ For writing a Vim script, see chapter 41
 			For a script that was used with `import autoload` but
 			was not actually sourced yet an "A" is shown after the
 			script ID.
+			For a script that was referred to by one name but
+			after resolving symbolic links got sourced with
+			another name the other script is after "->".  E.g.
+			"20->22" means script 20 was sourced as script 22.
 			{not available when compiled without the |+eval|
 			feature}
 
--- a/src/eval.c
+++ b/src/eval.c
@@ -1039,6 +1039,7 @@ get_lval(
 	    ufunc_T *ufunc;
 	    type_T *type;
 
+	    import_check_sourced_sid(&import->imp_sid);
 	    lp->ll_sid = import->imp_sid;
 	    lp->ll_name = skipwhite(p + 1);
 	    p = find_name_end(lp->ll_name, NULL, NULL, fne_flags);
--- a/src/evalvars.c
+++ b/src/evalvars.c
@@ -2953,6 +2953,7 @@ eval_variable(
 	    {
 		if (rettv != NULL)
 		{
+		    // special value that is used in handle_subscript()
 		    rettv->v_type = VAR_ANY;
 		    rettv->vval.v_number = sid != 0 ? sid : import->imp_sid;
 		}
--- a/src/proto/vim9script.pro
+++ b/src/proto/vim9script.pro
@@ -12,6 +12,7 @@ void ex_export(exarg_T *eap);
 void free_imports_and_script_vars(int sid);
 void mark_imports_for_reload(int sid);
 void ex_import(exarg_T *eap);
+void import_check_sourced_sid(int *sid);
 int find_exported(int sid, char_u *name, ufunc_T **ufunc, type_T **type, cctx_T *cctx, cstack_T *cstack, int verbose);
 char_u *vim9_declare_scriptvar(exarg_T *eap, char_u *arg);
 void update_vim9_script_var(int create, dictitem_T *di, char_u *name, int flags, typval_T *tv, type_T **type, int do_member);
--- a/src/scriptfile.c
+++ b/src/scriptfile.c
@@ -1357,6 +1357,7 @@ do_source_ext(
 {
     source_cookie_T	    cookie;
     char_u		    *p;
+    char_u		    *fname_not_fixed = NULL;
     char_u		    *fname_exp;
     char_u		    *firstline = NULL;
     int			    retval = FAIL;
@@ -1389,13 +1390,12 @@ do_source_ext(
     }
     else
     {
-	p = expand_env_save(fname);
-	if (p == NULL)
-	    return retval;
-	fname_exp = fix_fname(p);
-	vim_free(p);
+	fname_not_fixed = expand_env_save(fname);
+	if (fname_not_fixed == NULL)
+	    goto theend;
+	fname_exp = fix_fname(fname_not_fixed);
 	if (fname_exp == NULL)
-	    return retval;
+	    goto theend;
 	if (mch_isdir(fname_exp))
 	{
 	    smsg(_("Cannot source a directory: \"%s\""), fname);
@@ -1602,14 +1602,15 @@ do_source_ext(
 	int error = OK;
 
 	// It's new, generate a new SID and initialize the scriptitem.
-	current_sctx.sc_sid = get_new_scriptitem(&error);
+	sid = get_new_scriptitem(&error);
+	current_sctx.sc_sid = sid;
 	if (error == FAIL)
 	    goto almosttheend;
-	si = SCRIPT_ITEM(current_sctx.sc_sid);
+	si = SCRIPT_ITEM(sid);
 	si->sn_name = fname_exp;
 	fname_exp = vim_strsave(si->sn_name);  // used for autocmd
 	if (ret_sid != NULL)
-	    *ret_sid = current_sctx.sc_sid;
+	    *ret_sid = sid;
 
 	// Remember the "is_vimrc" flag for when the file is sourced again.
 	si->sn_is_vimrc = is_vimrc;
@@ -1668,7 +1669,7 @@ do_source_ext(
     if (do_profiling == PROF_YES)
     {
 	// Get "si" again, "script_items" may have been reallocated.
-	si = SCRIPT_ITEM(current_sctx.sc_sid);
+	si = SCRIPT_ITEM(sid);
 	if (si->sn_prof_on)
 	{
 	    profile_end(&si->sn_pr_start);
@@ -1719,7 +1720,7 @@ almosttheend:
     // If "sn_save_cpo" is set that means we encountered "vim9script": restore
     // 'cpoptions', unless in the main .vimrc file.
     // Get "si" again, "script_items" may have been reallocated.
-    si = SCRIPT_ITEM(current_sctx.sc_sid);
+    si = SCRIPT_ITEM(sid);
     if (si->sn_save_cpo != NULL && si->sn_is_vimrc == DOSO_NONE)
     {
 	if (STRCMP(p_cpo, CPO_VIM) != 0)
@@ -1774,6 +1775,19 @@ almosttheend:
 	apply_autocmds(EVENT_SOURCEPOST, fname_exp, fname_exp, FALSE, curbuf);
 
 theend:
+    if (sid > 0 && ret_sid != NULL
+	    && fname_not_fixed != NULL && fname_exp != NULL)
+    {
+	int not_fixed_sid = find_script_by_name(fname_not_fixed);
+
+	// If "fname_not_fixed" is a symlink then we source the linked file.
+	// If the original name is in the script list we add the ID of the
+	// script that was actually sourced.
+	if (SCRIPT_ID_VALID(not_fixed_sid) && not_fixed_sid != sid)
+	    SCRIPT_ITEM(not_fixed_sid)->sn_sourced_sid = sid;
+    }
+
+    vim_free(fname_not_fixed);
     vim_free(fname_exp);
     sticky_cmdmod_flags = save_sticky_cmdmod_flags;
 #ifdef FEAT_EVAL
@@ -1787,7 +1801,7 @@ do_source(
     char_u	*fname,
     int		check_other,	    // check for .vimrc and _vimrc
     int		is_vimrc,	    // DOSO_ value
-    int		*ret_sid UNUSED)
+    int		*ret_sid)
 {
     return do_source_ext(fname, check_other, is_vimrc, ret_sid, NULL, FALSE);
 }
@@ -1828,9 +1842,16 @@ ex_scriptnames(exarg_T *eap)
 
 	if (si->sn_name != NULL)
 	{
+	    char sourced_buf[20];
+
 	    home_replace(NULL, si->sn_name, NameBuff, MAXPATHL, TRUE);
-	    vim_snprintf((char *)IObuff, IOSIZE, "%3d%s: %s",
+	    if (si->sn_sourced_sid > 0)
+		vim_snprintf(sourced_buf, 20, "->%d", si->sn_sourced_sid);
+	    else
+		sourced_buf[0] = NUL;
+	    vim_snprintf((char *)IObuff, IOSIZE, "%3d%s%s: %s",
 		    i,
+		    sourced_buf,
 		    si->sn_state == SN_STATE_NOT_LOADED ? " A" : "",
 		    NameBuff);
 	    if (!message_filtered(IObuff))
@@ -1946,6 +1967,7 @@ f_getscriptinfo(typval_T *argvars UNUSED
 		|| list_append_dict(l, d) == FAIL
 		|| dict_add_string(d, "name", si->sn_name) == FAIL
 		|| dict_add_number(d, "sid", i) == FAIL
+		|| dict_add_number(d, "sourced", si->sn_sourced_sid) == FAIL
 		|| dict_add_bool(d, "autoload",
 				si->sn_state == SN_STATE_NOT_LOADED) == FAIL)
 	    return;
--- a/src/structs.h
+++ b/src/structs.h
@@ -1850,13 +1850,19 @@ typedef struct {
 #define IMP_FLAGS_AUTOLOAD	4   // script still needs to be loaded
 
 /*
- * Info about an already sourced scripts.
+ * Info about an encoutered script.
+ * When sn_state has the SN_STATE_NOT_LOADED is has not been sourced yet.
  */
 typedef struct
 {
     char_u	*sn_name;	    // full path of script file
     int		sn_script_seq;	    // latest sctx_T sc_seq value
 
+    // When non-zero the script ID of the actually sourced script.  Used if a
+    // script is used by a name which has a symlink, we list both names, but
+    // only the linked-to script is actually sourced.
+    int		sn_sourced_sid;
+
     // "sn_vars" stores the s: variables currently valid.  When leaving a block
     // variables local to that block are removed.
     scriptvar_T	*sn_vars;
--- a/src/testdir/test_vim9_import.vim
+++ b/src/testdir/test_vim9_import.vim
@@ -2906,5 +2906,50 @@ def Test_vim9_autoload_error()
   v9.CheckScriptFailure(lines, 'E461: Illegal variable name: foo#bar', 2)
 enddef
 
+def Test_vim9_import_symlink()
+  if !has('unix')
+    CheckUnix
+  else
+    mkdir('Xto/plugin', 'p')
+    var lines =<< trim END
+        vim9script
+        import autoload 'bar.vim'
+        g:resultFunc = bar.Func()
+        g:resultValue = bar.value
+    END
+    writefile(lines, 'Xto/plugin/foo.vim')
+
+    mkdir('Xto/autoload', 'p')
+    lines =<< trim END
+        vim9script
+        export def Func(): string
+          return 'func'
+        enddef
+        export var value = 'val'
+    END
+    writefile(lines, 'Xto/autoload/bar.vim')
+
+    var save_rtp = &rtp
+    &rtp = getcwd() .. '/Xfrom'
+    system('ln -s ' .. getcwd() .. '/Xto Xfrom')
+
+    source Xfrom/plugin/foo.vim
+    assert_equal('func', g:resultFunc)
+    assert_equal('val', g:resultValue)
+
+    var infoTo = getscriptinfo()->filter((_, v) => v.name =~ 'Xto/autoload/bar')
+    var infoFrom = getscriptinfo()->filter((_, v) => v.name =~ 'Xfrom/autoload/bar')
+    assert_equal(1, len(infoTo))
+    assert_equal(1, len(infoFrom))
+    assert_equal(infoTo[0].sid, infoFrom[0].sourced)
+
+    unlet g:resultFunc
+    unlet g:resultValue
+    &rtp = save_rtp
+    delete('Xto', 'rf')
+    delete('Xfrom', 'rf')
+  endif
+enddef
+
 
 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
--- a/src/version.c
+++ b/src/version.c
@@ -732,6 +732,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    253,
+/**/
     252,
 /**/
     251,
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -610,7 +610,7 @@ find_imported(char_u *name, size_t len, 
     ret = find_imported_in_script(name, len, current_sctx.sc_sid);
     if (ret != NULL && load && (ret->imp_flags & IMP_FLAGS_AUTOLOAD))
     {
-	scid_T	dummy;
+	scid_T	actual_sid = 0;
 	int	save_emsg_off = emsg_off;
 
 	// "emsg_off" will be set when evaluating an expression silently, but
@@ -621,7 +621,11 @@ find_imported(char_u *name, size_t len, 
 	// script found before but not loaded yet
 	ret->imp_flags &= ~IMP_FLAGS_AUTOLOAD;
 	(void)do_source(SCRIPT_ITEM(ret->imp_sid)->sn_name, FALSE,
-							    DOSO_NONE, &dummy);
+						       DOSO_NONE, &actual_sid);
+	// If the script is a symlink it may be sourced with another name, may
+	// need to adjust the script ID for that.
+	if (actual_sid != 0)
+	    ret->imp_sid = actual_sid;
 
 	emsg_off = save_emsg_off;
     }
--- a/src/vim9script.c
+++ b/src/vim9script.c
@@ -697,6 +697,20 @@ ex_import(exarg_T *eap)
 }
 
 /*
+ * When a script is a symlink it may be imported with one name and sourced
+ * under another name.  Adjust the import script ID if needed.
+ * "*sid" must be a valid script ID.
+ */
+    void
+import_check_sourced_sid(int *sid)
+{
+    scriptitem_T *script = SCRIPT_ITEM(*sid);
+
+    if (script->sn_sourced_sid > 0)
+	*sid = script->sn_sourced_sid;
+}
+
+/*
  * Find an exported item in "sid" matching "name".
  * Either "cctx" or "cstack" is NULL.
  * When it is a variable return the index.