changeset 28249:4b322951ebac v8.2.4650

patch 8.2.4650: "import autoload" only works with using 'runtimepath' Commit: https://github.com/vim/vim/commit/c0ceeeb839b8c6bebd3a2abd1c07d40ec3c6edca Author: Bram Moolenaar <Bram@vim.org> Date: Wed Mar 30 21:12:27 2022 +0100 patch 8.2.4650: "import autoload" only works with using 'runtimepath' Problem: "import autoload" only works with using 'runtimepath'. Solution: Also support a relative and absolute file name.
author Bram Moolenaar <Bram@vim.org>
date Wed, 30 Mar 2022 22:15:04 +0200
parents 08964a0fe111
children efcfbd8ca1ea
files src/proto/scriptfile.pro src/proto/userfunc.pro src/proto/vim9instr.pro src/scriptfile.c src/structs.h src/testdir/test_vim9_disassemble.vim src/testdir/test_vim9_import.vim src/userfunc.c src/version.c src/vim.h src/vim9.h src/vim9execute.c src/vim9expr.c src/vim9instr.c src/vim9script.c
diffstat 15 files changed, 442 insertions(+), 82 deletions(-) [+]
line wrap: on
line diff
--- a/src/proto/scriptfile.pro
+++ b/src/proto/scriptfile.pro
@@ -6,6 +6,8 @@ int estack_top_is_ufunc(ufunc_T *ufunc, 
 estack_T *estack_pop(void);
 char_u *estack_sfile(estack_arg_T which);
 void ex_runtime(exarg_T *eap);
+int find_script_by_name(char_u *name);
+int get_new_scriptitem_for_fname(int *error, char_u *fname);
 int do_in_path(char_u *path, char_u *name, int flags, void (*callback)(char_u *fname, void *ck), void *cookie);
 int do_in_runtimepath(char_u *name, int flags, void (*callback)(char_u *fname, void *ck), void *cookie);
 int source_runtime(char_u *name, int flags);
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -8,6 +8,7 @@ char_u *deref_func_name(char_u *name, in
 void emsg_funcname(char *ermsg, char_u *name);
 int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, evalarg_T *evalarg, funcexe_T *funcexe);
 char_u *fname_trans_sid(char_u *name, char_u *fname_buf, char_u **tofree, int *error);
+void func_name_with_sid(char_u *name, int sid, char_u *buffer);
 ufunc_T *find_func_even_dead(char_u *name, int flags);
 ufunc_T *find_func(char_u *name, int is_global);
 int func_is_global(ufunc_T *ufunc);
--- a/src/proto/vim9instr.pro
+++ b/src/proto/vim9instr.pro
@@ -54,6 +54,7 @@ int generate_PCALL(cctx_T *cctx, int arg
 int generate_STRINGMEMBER(cctx_T *cctx, char_u *name, size_t len);
 int generate_ECHO(cctx_T *cctx, int with_white, int count);
 int generate_MULT_EXPR(cctx_T *cctx, isntype_T isn_type, int count);
+int generate_SOURCE(cctx_T *cctx, int sid);
 int generate_PUT(cctx_T *cctx, int regname, linenr_T lnum);
 int generate_EXEC_copy(cctx_T *cctx, isntype_T isntype, char_u *line);
 int generate_EXEC(cctx_T *cctx, isntype_T isntype, char_u *str);
--- a/src/scriptfile.c
+++ b/src/scriptfile.c
@@ -251,7 +251,7 @@ source_callback(char_u *fname, void *coo
  * Find an already loaded script "name".
  * If found returns its script ID.  If not found returns -1.
  */
-    static int
+    int
 find_script_by_name(char_u *name)
 {
     int		    sid;
@@ -320,6 +320,21 @@ get_new_scriptitem(int *error)
     return sid;
 }
 
+    int
+get_new_scriptitem_for_fname(int *error, char_u *fname)
+{
+    int sid = get_new_scriptitem(error);
+
+    if (*error == OK)
+    {
+	scriptitem_T *si = SCRIPT_ITEM(sid);
+
+	si->sn_name = vim_strsave(fname);
+	si->sn_state = SN_STATE_NOT_LOADED;
+    }
+    return sid;
+}
+
     static void
 find_script_callback(char_u *fname, void *cookie)
 {
@@ -329,17 +344,8 @@ find_script_callback(char_u *fname, void
 
     sid = find_script_by_name(fname);
     if (sid < 0)
-    {
 	// script does not exist yet, create a new scriptitem
-	sid = get_new_scriptitem(&error);
-	if (error == OK)
-	{
-	    scriptitem_T *si = SCRIPT_ITEM(sid);
-
-	    si->sn_name = vim_strsave(fname);
-	    si->sn_state = SN_STATE_NOT_LOADED;
-	}
-    }
+	sid = get_new_scriptitem_for_fname(&error, fname);
     *ret_sid = sid;
 }
 #endif
--- a/src/structs.h
+++ b/src/structs.h
@@ -1833,7 +1833,7 @@ typedef struct {
  */
 typedef struct
 {
-    char_u	*sn_name;
+    char_u	*sn_name;	    // full path of script file
     int		sn_script_seq;	    // latest sctx_T sc_seq value
 
     // "sn_vars" stores the s: variables currently valid.  When leaving a block
@@ -1864,9 +1864,12 @@ typedef struct
     char_u	*sn_save_cpo;	// 'cpo' value when :vim9script found
     char	sn_is_vimrc;	// .vimrc file, do not restore 'cpo'
 
-    // for "vim9script autoload" this is "dir#scriptname#"
+    // for a Vim9 script under "rtp/autoload/" this is "dir#scriptname#"
     char_u	*sn_autoload_prefix;
 
+    // TRUE for a script used with "import autoload './dirname/script.vim'"
+    int		sn_import_autoload;
+
 # ifdef FEAT_PROFILE
     int		sn_prof_on;	// TRUE when script is/was profiled
     int		sn_pr_force;	// forceit: profile functions in this script
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -318,6 +318,46 @@ def Test_disassemble_push()
   &rtp = save_rtp
 enddef
 
+def Test_disassemble_import_autoload()
+  writefile(['vim9script'], 'XimportAL.vim')
+
+  var lines =<< trim END
+      vim9script
+      import autoload './XimportAL.vim'
+
+      def AutoloadFunc()
+        echo XimportAL.SomeFunc()
+        echo XimportAL.someVar
+        XimportAL.someVar = "yes"
+      enddef
+
+      var res = execute('disass AutoloadFunc')
+      assert_match('<SNR>\d*_AutoloadFunc.*' ..
+            'echo XimportAL.SomeFunc()\_s*' ..
+            '\d SOURCE /home/mool/vim/vim82/src/testdir/XimportAL.vim\_s*' ..
+            '\d PUSHFUNC "<80><fd>R\d\+_SomeFunc"\_s*' ..
+            '\d PCALL top (argc 0)\_s*' ..
+            '\d PCALL end\_s*' ..
+            '\d ECHO 1\_s*' ..
+
+            'echo XimportAL.someVar\_s*' ..
+            '\d SOURCE .*/testdir/XimportAL.vim\_s*' ..
+            '\d LOADEXPORT s:someVar from .*/testdir/XimportAL.vim\_s*' ..
+            '\d ECHO 1\_s*' ..
+
+            'XimportAL.someVar = "yes"\_s*' ..
+            '\d\+ PUSHS "yes"\_s*' ..
+            '\d\+ SOURCE .*/testdir/XimportAL.vim\_s*' ..
+            '\d\+ STOREEXPORT someVar in .*/testdir/XimportAL.vim\_s*' ..
+
+            '\d\+ RETURN void',
+            res)
+  END
+  v9.CheckScriptSuccess(lines)
+
+  delete('XimportAL.vim')
+enddef
+
 def s:ScriptFuncStore()
   var localnr = 1
   localnr = 2
--- a/src/testdir/test_vim9_import.vim
+++ b/src/testdir/test_vim9_import.vim
@@ -840,6 +840,135 @@ def Test_use_autoload_import_in_fold_exp
   &rtp = save_rtp
 enddef
 
+def Test_autoload_import_relative()
+  var lines =<< trim END
+      vim9script
+
+      g:loaded = 'yes'
+      export def RelFunc(): string
+        return 'relfunc'
+      enddef
+      def NotExported()
+        echo 'not'
+      enddef
+
+      export var someText = 'some text'
+      var notexp = 'bad'
+  END
+  writefile(lines, 'XimportRel.vim')
+  writefile(lines, 'XimportRel2.vim')
+  writefile(lines, 'XimportRel3.vim')
+
+  lines =<< trim END
+      vim9script
+      g:loaded = 'no'
+      import autoload './XimportRel.vim'
+      assert_equal('no', g:loaded)
+
+      def AFunc(): string
+        var res = ''
+        res ..= XimportRel.RelFunc()
+        res ..= '/'
+        res ..= XimportRel.someText
+        XimportRel.someText = 'from AFunc'
+        return res
+      enddef
+      # script not loaded when compiling
+      defcompile
+      assert_equal('no', g:loaded)
+
+      assert_equal('relfunc/some text', AFunc())
+      assert_equal('yes', g:loaded)
+      unlet g:loaded
+
+      assert_equal('from AFunc', XimportRel.someText)
+      XimportRel.someText = 'from script'
+      assert_equal('from script', XimportRel.someText)
+  END
+  v9.CheckScriptSuccess(lines)
+
+  lines =<< trim END
+      vim9script
+      import autoload './XimportRel.vim'
+      echo XimportRel.NotExported()
+  END
+  v9.CheckScriptFailure(lines, 'E1049: Item not exported in script: NotExported', 3)
+
+  lines =<< trim END
+      vim9script
+      import autoload './XimportRel.vim'
+      echo XimportRel.notexp
+  END
+  v9.CheckScriptFailure(lines, 'E1049: Item not exported in script: notexp', 3)
+
+  lines =<< trim END
+      vim9script
+      import autoload './XimportRel.vim'
+      XimportRel.notexp = 'bad'
+  END
+  v9.CheckScriptFailure(lines, 'E1049: Item not exported in script: notexp', 3)
+
+  lines =<< trim END
+      vim9script
+      import autoload './XimportRel.vim'
+      def Func()
+        echo XimportRel.NotExported()
+      enddef
+      Func()
+  END
+  v9.CheckScriptFailure(lines, 'E1049: Item not exported in script: NotExported', 1)
+
+  lines =<< trim END
+      vim9script
+      import autoload './XimportRel.vim'
+      def Func()
+        echo XimportRel.notexp
+      enddef
+      Func()
+  END
+  v9.CheckScriptFailure(lines, 'E1049: Item not exported in script: notexp', 1)
+
+  lines =<< trim END
+      vim9script
+      import autoload './XimportRel.vim'
+      def Func()
+        XimportRel.notexp = 'bad'
+      enddef
+      Func()
+  END
+  v9.CheckScriptFailure(lines, 'E1049: Item not exported in script: notexp', 1)
+
+  # does not fail if the script wasn't loaded yet
+  g:loaded = 'no'
+  lines =<< trim END
+      vim9script
+      import autoload './XimportRel2.vim'
+      def Func()
+        echo XimportRel2.notexp
+      enddef
+      defcompile
+  END
+  v9.CheckScriptSuccess(lines)
+  assert_equal('no', g:loaded)
+
+  # fails with a not loaded import
+  lines =<< trim END
+      vim9script
+      import autoload './XimportRel3.vim'
+      def Func()
+        XimportRel3.notexp = 'bad'
+      enddef
+      Func()
+  END
+  v9.CheckScriptFailure(lines, 'E1049: Item not exported in script: notexp', 1)
+  assert_equal('yes', g:loaded)
+  unlet g:loaded
+
+  delete('XimportRel.vim')
+  delete('XimportRel2.vim')
+  delete('XimportRel3.vim')
+enddef
+
 func Test_import_in_diffexpr()
   CheckExecutable diff
 
@@ -2379,13 +2508,13 @@ def Test_import_autoload_fails()
       vim9script
       import autoload './doesNotExist.vim'
   END
-  v9.CheckScriptFailure(lines, 'E1264:')
+  v9.CheckScriptSuccess(lines)
 
   lines =<< trim END
       vim9script
       import autoload '/dir/doesNotExist.vim'
   END
-  v9.CheckScriptFailure(lines, 'E1264:')
+  v9.CheckScriptSuccess(lines)
 
   lines =<< trim END
       vim9script
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -1884,23 +1884,33 @@ fname_trans_sid(char_u *name, char_u *fn
 }
 
 /*
+ * Concatenate the script ID and function name into  "<SNR>99_name".
+ * "buffer" must have size MAX_FUNC_NAME_LEN.
+ */
+    void
+func_name_with_sid(char_u *name, int sid, char_u *buffer)
+{
+    // A script-local function is stored as "<SNR>99_name".
+    buffer[0] = K_SPECIAL;
+    buffer[1] = KS_EXTRA;
+    buffer[2] = (int)KE_SNR;
+    vim_snprintf((char *)buffer + 3, MAX_FUNC_NAME_LEN - 3, "%ld_%s",
+							      (long)sid, name);
+}
+
+/*
  * Find a function "name" in script "sid".
  */
     static ufunc_T *
 find_func_with_sid(char_u *name, int sid)
 {
     hashitem_T	    *hi;
-    char_u	    buffer[200];
+    char_u	    buffer[MAX_FUNC_NAME_LEN];
 
     if (!SCRIPT_ID_VALID(sid))
 	return NULL;	// not in a script
 
-    // A script-local function is stored as "<SNR>99_name".
-    buffer[0] = K_SPECIAL;
-    buffer[1] = KS_EXTRA;
-    buffer[2] = (int)KE_SNR;
-    vim_snprintf((char *)buffer + 3, sizeof(buffer) - 3, "%ld_%s",
-							      (long)sid, name);
+    func_name_with_sid(name, sid, buffer);
     hi = hash_find(&func_hashtab, buffer);
     if (!HASHITEM_EMPTY(hi))
 	return HI2UF(hi);
@@ -1914,7 +1924,7 @@ find_func_with_sid(char_u *name, int sid
 find_func_with_prefix(char_u *name, int sid)
 {
     hashitem_T	    *hi;
-    char_u	    buffer[200];
+    char_u	    buffer[MAX_FUNC_NAME_LEN];
     scriptitem_T    *si;
 
     if (vim_strchr(name, AUTOLOAD_CHAR) != NULL)
--- 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 */
 /**/
+    4650,
+/**/
     4649,
 /**/
     4648,
--- a/src/vim.h
+++ b/src/vim.h
@@ -1571,6 +1571,9 @@ typedef UINT32_TYPEDEF UINT32_T;
  */
 #define MAXMAPLEN   50
 
+// maximum length of a function name, including SID and NUL
+#define MAX_FUNC_NAME_LEN   200
+
 // Size in bytes of the hash used in the undo file.
 #define UNDO_HASH_SIZE 32
 
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -28,6 +28,8 @@ typedef enum {
     ISN_ECHOERR,    // :echoerr with isn_arg.number items on top of stack
     ISN_RANGE,	    // compute range from isn_arg.string, push to stack
     ISN_SUBSTITUTE, // :s command with expression
+
+    ISN_SOURCE,	    // source autoload script, isn_arg.number is the script ID
     ISN_INSTR,	    // instructions compiled from expression
 
     // get and set variables
@@ -43,6 +45,7 @@ typedef enum {
     ISN_LOADWDICT,  // push w: dict
     ISN_LOADTDICT,  // push t: dict
     ISN_LOADS,	    // push s: variable isn_arg.loadstore
+    ISN_LOADEXPORT, // push exported variable isn_arg.loadstore
     ISN_LOADOUTER,  // push variable from outer scope isn_arg.outer
     ISN_LOADSCRIPT, // push script-local variable isn_arg.script.
     ISN_LOADOPT,    // push option isn_arg.string
@@ -57,6 +60,7 @@ typedef enum {
     ISN_STOREW,	    // pop into window-local variable isn_arg.string
     ISN_STORET,	    // pop into tab-local variable isn_arg.string
     ISN_STORES,	    // pop into script variable isn_arg.loadstore
+    ISN_STOREEXPORT, // pop into exported script variable isn_arg.loadstore
     ISN_STOREOUTER,  // pop variable into outer scope isn_arg.outer
     ISN_STORESCRIPT, // pop into script variable isn_arg.script
     ISN_STOREOPT,    // pop into option isn_arg.storeopt
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -1510,6 +1510,13 @@ get_script_svar(scriptref_T *sref, int d
 	    emsg(_(e_script_variable_type_changed));
 	return NULL;
     }
+
+    if (!sv->sv_export && sref->sref_sid != current_sctx.sc_sid)
+    {
+	if (dfunc != NULL)
+	    semsg(_(e_item_not_exported_in_script_str), sv->sv_name);
+	return NULL;
+    }
     return sv;
 }
 
@@ -2623,6 +2630,20 @@ exec_instructions(ectx_T *ectx)
 		}
 		break;
 
+	    case ISN_SOURCE:
+		{
+		    scriptitem_T *si = SCRIPT_ITEM(iptr->isn_arg.number);
+
+		    if (si->sn_state == SN_STATE_NOT_LOADED)
+		    {
+			SOURCING_LNUM = iptr->isn_lnum;
+			if (do_source(si->sn_name, FALSE, DOSO_NONE, NULL)
+								       == FAIL)
+			    goto on_error;
+		    }
+		}
+		break;
+
 	    // execute :substitute with an expression
 	    case ISN_SUBSTITUTE:
 		{
@@ -2902,11 +2923,12 @@ exec_instructions(ectx_T *ectx)
 		}
 		break;
 
-	    // load s: variable in old script
+	    // load s: variable in old script or autoload import
 	    case ISN_LOADS:
+	    case ISN_LOADEXPORT:
 		{
-		    hashtab_T	*ht = &SCRIPT_VARS(
-					       iptr->isn_arg.loadstore.ls_sid);
+		    int		sid = iptr->isn_arg.loadstore.ls_sid;
+		    hashtab_T	*ht = &SCRIPT_VARS(sid);
 		    char_u	*name = iptr->isn_arg.loadstore.ls_name;
 		    dictitem_T	*di = find_var_in_ht(ht, 0, name, TRUE);
 
@@ -2918,6 +2940,25 @@ exec_instructions(ectx_T *ectx)
 		    }
 		    else
 		    {
+			if (iptr->isn_type == ISN_LOADEXPORT)
+			{
+			    int idx = get_script_item_idx(sid, name, 0,
+								   NULL, NULL);
+			    svar_T	*sv;
+
+			    if (idx >= 0)
+			    {
+				sv = ((svar_T *)SCRIPT_ITEM(sid)
+						  ->sn_var_vals.ga_data) + idx;
+				if (!sv->sv_export)
+				{
+				    SOURCING_LNUM = iptr->isn_lnum;
+				    semsg(_(e_item_not_exported_in_script_str),
+									 name);
+				    goto on_error;
+				}
+			    }
+			}
 			if (GA_GROW_FAILS(&ectx->ec_stack, 1))
 			    goto theend;
 			copy_tv(&di->di_tv, STACK_TV_BOT(0));
@@ -3039,20 +3080,48 @@ exec_instructions(ectx_T *ectx)
 		*tv = *STACK_TV_BOT(0);
 		break;
 
-	    // store s: variable in old script
+	    // store s: variable in old script or autoload import
 	    case ISN_STORES:
+	    case ISN_STOREEXPORT:
 		{
-		    hashtab_T	*ht = &SCRIPT_VARS(
-					       iptr->isn_arg.loadstore.ls_sid);
+		    int		sid = iptr->isn_arg.loadstore.ls_sid;
+		    hashtab_T	*ht = &SCRIPT_VARS(sid);
 		    char_u	*name = iptr->isn_arg.loadstore.ls_name;
-		    dictitem_T	*di = find_var_in_ht(ht, 0, name + 2, TRUE);
+		    dictitem_T	*di = find_var_in_ht(ht, 0,
+					    iptr->isn_type == ISN_STORES
+						     ? name + 2 : name, TRUE);
 
 		    --ectx->ec_stack.ga_len;
+		    SOURCING_LNUM = iptr->isn_lnum;
 		    if (di == NULL)
+		    {
+			if (iptr->isn_type == ISN_STOREEXPORT)
+			{
+			    semsg(_(e_undefined_variable_str), name);
+			    goto on_error;
+			}
 			store_var(name, STACK_TV_BOT(0));
+		    }
 		    else
 		    {
-			SOURCING_LNUM = iptr->isn_lnum;
+			if (iptr->isn_type == ISN_STOREEXPORT)
+			{
+			    int idx = get_script_item_idx(sid, name, 0,
+								   NULL, NULL);
+
+			    if (idx >= 0)
+			    {
+				svar_T	*sv = ((svar_T *)SCRIPT_ITEM(sid)
+						  ->sn_var_vals.ga_data) + idx;
+
+				if (!sv->sv_export)
+				{
+				    semsg(_(e_item_not_exported_in_script_str),
+									 name);
+				    goto on_error;
+				}
+			    }
+			}
 			if (var_check_permission(di, name) == FAIL)
 			{
 			    clear_tv(STACK_TV_BOT(0));
@@ -5409,11 +5478,15 @@ list_instructions(char *pfx, isn_T *inst
 #endif
 		break;
 	    case ISN_INSTR:
+		smsg("%s%4d INSTR", pfx, current);
+		list_instructions("    ", iptr->isn_arg.instr, INT_MAX, NULL);
+		msg("     -------------");
+		break;
+	    case ISN_SOURCE:
 		{
-		    smsg("%s%4d INSTR", pfx, current);
-		    list_instructions("    ", iptr->isn_arg.instr,
-								INT_MAX, NULL);
-		    msg("     -------------");
+		    scriptitem_T    *si = SCRIPT_ITEM(iptr->isn_arg.number);
+
+		    smsg("%s%4d SOURCE %s", pfx, current, si->sn_name);
 		}
 		break;
 	    case ISN_SUBSTITUTE:
@@ -5500,12 +5573,15 @@ list_instructions(char *pfx, isn_T *inst
 		}
 		break;
 	    case ISN_LOADS:
+	    case ISN_LOADEXPORT:
 		{
 		    scriptitem_T *si = SCRIPT_ITEM(
 					       iptr->isn_arg.loadstore.ls_sid);
 
-		    smsg("%s%4d LOADS s:%s from %s", pfx, current,
-				 iptr->isn_arg.loadstore.ls_name, si->sn_name);
+		    smsg("%s%4d %s s:%s from %s", pfx, current,
+			    iptr->isn_type == ISN_LOADS ? "LOADS"
+								: "LOADEXPORT",
+			    iptr->isn_arg.loadstore.ls_name, si->sn_name);
 		}
 		break;
 	    case ISN_LOADAUTO:
@@ -5586,11 +5662,14 @@ list_instructions(char *pfx, isn_T *inst
 		smsg("%s%4d STORET %s", pfx, current, iptr->isn_arg.string);
 		break;
 	    case ISN_STORES:
+	    case ISN_STOREEXPORT:
 		{
 		    scriptitem_T *si = SCRIPT_ITEM(
 					       iptr->isn_arg.loadstore.ls_sid);
 
-		    smsg("%s%4d STORES %s in %s", pfx, current,
+		    smsg("%s%4d %s %s in %s", pfx, current,
+				iptr->isn_type == ISN_STORES
+						    ? "STORES" : "STOREEXPORT",
 				 iptr->isn_arg.loadstore.ls_name, si->sn_name);
 		}
 		break;
--- a/src/vim9expr.c
+++ b/src/vim9expr.c
@@ -313,6 +313,27 @@ compile_load_scriptvar(
 	    vim_free(auto_name);
 	    done = TRUE;
 	}
+	else if (si->sn_import_autoload && si->sn_state == SN_STATE_NOT_LOADED)
+	{
+	    // "import autoload './dir/script.vim'" - load script first
+	    res = generate_SOURCE(cctx, import->imp_sid);
+	    if (res == OK)
+	    {
+		// If a '(' follows it must be a function.  Otherwise we don't
+		// know, it can be "script.Func".
+		if (cc == '(' || paren_follows_after_expr)
+		{
+		    char_u sid_name[MAX_FUNC_NAME_LEN];
+
+		    func_name_with_sid(exp_name, import->imp_sid, sid_name);
+		    res = generate_PUSHFUNC(cctx, sid_name, &t_func_any);
+		}
+		else
+		    res = generate_OLDSCRIPT(cctx, ISN_LOADEXPORT, exp_name,
+						      import->imp_sid, &t_any);
+	    }
+	    done = TRUE;
+	}
 	else
 	{
 	    idx = find_exported(import->imp_sid, exp_name, &ufunc, &type,
--- a/src/vim9instr.c
+++ b/src/vim9instr.c
@@ -1066,7 +1066,7 @@ generate_OLDSCRIPT(
     isn_T	*isn;
 
     RETURN_OK_IF_SKIP(cctx);
-    if (isn_type == ISN_LOADS)
+    if (isn_type == ISN_LOADS || isn_type == ISN_LOADEXPORT)
 	isn = generate_instr_type(cctx, isn_type, type);
     else
 	isn = generate_instr_drop(cctx, isn_type, 1);
@@ -1728,6 +1728,21 @@ generate_MULT_EXPR(cctx_T *cctx, isntype
 }
 
 /*
+ * Generate an ISN_SOURCE instruction.
+ */
+    int
+generate_SOURCE(cctx_T *cctx, int sid)
+{
+    isn_T	*isn;
+
+    if ((isn = generate_instr(cctx, ISN_SOURCE)) == NULL)
+	return FAIL;
+    isn->isn_arg.number = sid;
+
+    return OK;
+}
+
+/*
  * Generate an ISN_PUT instruction.
  */
     int
@@ -1913,9 +1928,22 @@ generate_store_var(
 	    return generate_STORE(cctx, ISN_STOREV, vimvaridx, NULL);
 	case dest_script:
 	    if (scriptvar_idx < 0)
+	    {
+		isntype_T isn_type = ISN_STORES;
+
+		if (SCRIPT_ID_VALID(scriptvar_sid)
+			 && SCRIPT_ITEM(scriptvar_sid)->sn_import_autoload)
+		{
+		    // "import autoload './dir/script.vim'" - load script first
+		    if (generate_SOURCE(cctx, scriptvar_sid) == FAIL)
+			return FAIL;
+		    isn_type = ISN_STOREEXPORT;
+		}
+
 		// "s:" may be included in the name.
-		return generate_OLDSCRIPT(cctx, ISN_STORES, name,
-							  scriptvar_sid, type);
+		return generate_OLDSCRIPT(cctx, isn_type, name,
+						      scriptvar_sid, type);
+	    }
 	    return generate_VIM9SCRIPT(cctx, ISN_STORESCRIPT,
 					   scriptvar_sid, scriptvar_idx, type);
 	case dest_local:
@@ -2062,7 +2090,9 @@ delete_instr(isn_T *isn)
 	    break;
 
 	case ISN_LOADS:
+	case ISN_LOADEXPORT:
 	case ISN_STORES:
+	case ISN_STOREEXPORT:
 	    vim_free(isn->isn_arg.loadstore.ls_name);
 	    break;
 
@@ -2089,7 +2119,7 @@ delete_instr(isn_T *isn)
 		if (isn->isn_arg.funcref.fr_func_name == NULL)
 		{
 		    dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
-					   + isn->isn_arg.funcref.fr_dfunc_idx;
+			+ isn->isn_arg.funcref.fr_dfunc_idx;
 		    ufunc_T *ufunc = dfunc->df_ufunc;
 
 		    if (ufunc != NULL && func_name_refcount(ufunc->uf_name))
@@ -2109,10 +2139,10 @@ delete_instr(isn_T *isn)
 	case ISN_DCALL:
 	    {
 		dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
-					       + isn->isn_arg.dfunc.cdf_idx;
+		    + isn->isn_arg.dfunc.cdf_idx;
 
 		if (dfunc->df_ufunc != NULL
-			       && func_name_refcount(dfunc->df_ufunc->uf_name))
+			&& func_name_refcount(dfunc->df_ufunc->uf_name))
 		    func_ptr_unref(dfunc->df_ufunc);
 	    }
 	    break;
@@ -2140,7 +2170,7 @@ delete_instr(isn_T *isn)
 
 	case ISN_CMDMOD:
 	    vim_regfree(isn->isn_arg.cmdmod.cf_cmdmod
-					       ->cmod_filter_regmatch.regprog);
+		    ->cmod_filter_regmatch.regprog);
 	    vim_free(isn->isn_arg.cmdmod.cf_cmdmod);
 	    break;
 
@@ -2243,21 +2273,22 @@ delete_instr(isn_T *isn)
 	case ISN_STORE:
 	case ISN_STOREINDEX:
 	case ISN_STORENR:
-	case ISN_STOREOUTER:
-	case ISN_STORERANGE:
-	case ISN_STOREREG:
-	case ISN_STOREV:
-	case ISN_STRINDEX:
-	case ISN_STRSLICE:
-	case ISN_THROW:
-	case ISN_TRYCONT:
-	case ISN_UNLETINDEX:
-	case ISN_UNLETRANGE:
-	case ISN_UNPACK:
-	case ISN_USEDICT:
-	    // nothing allocated
-	    break;
-    }
+	case ISN_SOURCE:
+    case ISN_STOREOUTER:
+    case ISN_STORERANGE:
+    case ISN_STOREREG:
+    case ISN_STOREV:
+    case ISN_STRINDEX:
+    case ISN_STRSLICE:
+    case ISN_THROW:
+    case ISN_TRYCONT:
+    case ISN_UNLETINDEX:
+    case ISN_UNLETRANGE:
+    case ISN_UNPACK:
+    case ISN_USEDICT:
+	// nothing allocated
+	break;
+}
 }
 
     void
--- a/src/vim9script.c
+++ b/src/vim9script.c
@@ -384,6 +384,38 @@ mark_imports_for_reload(int sid)
 }
 
 /*
+ * Part of "import" that handles a relative or absolute file name/
+ * Returns OK or FAIL.
+ */
+    static int
+handle_import_fname(char_u *fname, int is_autoload, int *sid)
+{
+    if (is_autoload)
+    {
+	scriptitem_T	*si;
+
+	*sid = find_script_by_name(fname);
+	if (*sid < 0)
+	{
+	    int error = OK;
+
+	    // script does not exist yet, create a new scriptitem
+	    *sid = get_new_scriptitem_for_fname(&error, fname);
+	    if (error == FAIL)
+		return FAIL;
+	}
+
+	si = SCRIPT_ITEM(*sid);
+	si->sn_import_autoload = TRUE;
+
+	// with testing override: load autoload script right away
+	if (!override_autoload || si->sn_state != SN_STATE_NOT_LOADED)
+	    return OK;
+    }
+    return do_source(fname, FALSE, DOSO_NONE, sid);
+}
+
+/*
  * 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.
@@ -442,25 +474,18 @@ handle_import(
 	char_u		*tail = gettail(si->sn_name);
 	char_u		*from_name;
 
-	if (is_autoload)
-	    res = FAIL;
-	else
-	{
+	// Relative to current script: "./name.vim", "../../name.vim".
+	len = STRLEN(si->sn_name) - STRLEN(tail) + STRLEN(tv.vval.v_string) + 2;
+	from_name = alloc((int)len);
+	if (from_name == NULL)
+	    goto erret;
+	vim_strncpy(from_name, si->sn_name, tail - si->sn_name);
+	add_pathsep(from_name);
+	STRCAT(from_name, tv.vval.v_string);
+	simplify_filename(from_name);
 
-	    // Relative to current script: "./name.vim", "../../name.vim".
-	    len = STRLEN(si->sn_name) - STRLEN(tail)
-						+ STRLEN(tv.vval.v_string) + 2;
-	    from_name = alloc((int)len);
-	    if (from_name == NULL)
-		goto erret;
-	    vim_strncpy(from_name, si->sn_name, tail - si->sn_name);
-	    add_pathsep(from_name);
-	    STRCAT(from_name, tv.vval.v_string);
-	    simplify_filename(from_name);
-
-	    res = do_source(from_name, FALSE, DOSO_NONE, &sid);
-	    vim_free(from_name);
-	}
+	res = handle_import_fname(from_name, is_autoload, &sid);
+	vim_free(from_name);
     }
     else if (mch_isFullName(tv.vval.v_string)
 #ifdef BACKSLASH_IN_FILENAME
@@ -471,10 +496,7 @@ handle_import(
 	    )
     {
 	// Absolute path: "/tmp/name.vim"
-	if (is_autoload)
-	    res = FAIL;
-	else
-	    res = do_source(tv.vval.v_string, FALSE, DOSO_NONE, &sid);
+	res = handle_import_fname(tv.vval.v_string, is_autoload, &sid);
     }
     else if (is_autoload)
     {
@@ -677,6 +699,12 @@ find_exported(
     svar_T	*sv;
     scriptitem_T *script = SCRIPT_ITEM(sid);
 
+    if (script->sn_import_autoload && script->sn_state == SN_STATE_NOT_LOADED)
+    {
+	if (do_source(script->sn_name, FALSE, DOSO_NONE, NULL) == FAIL)
+	    return -1;
+    }
+
     // Find name in "script".
     idx = get_script_item_idx(sid, name, 0, cctx, cstack);
     if (idx >= 0)