changeset 27376:1a6421c5be20 v8.2.4216

patch 8.2.4216: Vim9: cannot use a function from an autoload import directly Commit: https://github.com/vim/vim/commit/06b77229ca704d00c4f138ed0377556e54d5851f Author: Bram Moolenaar <Bram@vim.org> Date: Tue Jan 25 15:51:56 2022 +0000 patch 8.2.4216: Vim9: cannot use a function from an autoload import directly Problem: Vim9: cannot use a function from an autoload import directly. Solution: Add the AUTOLOAD instruction to figure out at runtime. (closes #9620)
author Bram Moolenaar <Bram@vim.org>
date Tue, 25 Jan 2022 17:00:06 +0100
parents 90523078c8e2
children 94507d9cc370
files src/proto/vim9instr.pro src/testdir/test_vim9_disassemble.vim src/testdir/test_vim9_import.vim src/version.c src/vim9.h src/vim9execute.c src/vim9expr.c src/vim9instr.c
diffstat 8 files changed, 197 insertions(+), 76 deletions(-) [+]
line wrap: on
line diff
--- a/src/proto/vim9instr.pro
+++ b/src/proto/vim9instr.pro
@@ -23,6 +23,7 @@ int generate_PUSHCHANNEL(cctx_T *cctx, c
 int generate_PUSHJOB(cctx_T *cctx, job_T *job);
 int generate_PUSHBLOB(cctx_T *cctx, blob_T *blob);
 int generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type);
+int generate_AUTOLOAD(cctx_T *cctx, char_u *name, type_T *type);
 int generate_GETITEM(cctx_T *cctx, int index, int with_op);
 int generate_SLICE(cctx_T *cctx, int count);
 int generate_CHECKLEN(cctx_T *cctx, int min_len, int more_OK);
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -1,6 +1,7 @@
 " Test the :disassemble command, and compilation as a side effect
 
 source check.vim
+source vim9.vim
 
 func NotCompiled()
   echo "not"
@@ -286,21 +287,35 @@ def s:ScriptFuncPush()
 enddef
 
 def Test_disassemble_push()
-  var res = execute('disass s:ScriptFuncPush')
-  assert_match('<SNR>\d*_ScriptFuncPush.*' ..
-        'localbool = true.*' ..
-        ' PUSH true.*' ..
-        'localspec = v:none.*' ..
-        ' PUSH v:none.*' ..
-        'localblob = 0z1234.*' ..
-        ' PUSHBLOB 0z1234.*',
-        res)
-  if has('float')
-    assert_match('<SNR>\d*_ScriptFuncPush.*' ..
-          'localfloat = 1.234.*' ..
-          ' PUSHF 1.234.*',
-          res)
-  endif
+  mkdir('Xdir/autoload', 'p')
+  var save_rtp = &rtp
+  exe 'set rtp^=' .. getcwd() .. '/Xdir'
+
+  var lines =<< trim END
+      vim9script
+  END
+  writefile(lines, 'Xdir/autoload/autoscript.vim')
+
+  lines =<< trim END
+      vim9script
+      import autoload 'autoscript.vim'
+
+      def s:AutoloadFunc()
+        &operatorfunc = autoscript.Opfunc
+      enddef
+
+      var res = execute('disass s:AutoloadFunc')
+      assert_match('<SNR>\d*_AutoloadFunc.*' ..
+            '&operatorfunc = autoscript.Opfunc\_s*' ..
+            '0 AUTOLOAD autoscript#Opfunc\_s*' ..
+            '1 STOREFUNCOPT &operatorfunc\_s*' ..
+            '2 RETURN void',
+            res)
+  END
+  CheckScriptSuccess(lines)
+
+  delete('Xdir', 'rf')
+  &rtp = save_rtp
 enddef
 
 def s:ScriptFuncStore()
--- a/src/testdir/test_vim9_import.vim
+++ b/src/testdir/test_vim9_import.vim
@@ -703,6 +703,41 @@ def Test_use_autoload_import_partial_in_
   set opfunc=
   bwipe!
   delete('Xdir', 'rf')
+  nunmap <F3>
+  &rtp = save_rtp
+enddef
+
+def Test_set_opfunc_to_autoload_func_directly()
+  mkdir('Xdir/autoload', 'p')
+  var save_rtp = &rtp
+  exe 'set rtp^=' .. getcwd() .. '/Xdir'
+
+  var lines =<< trim END
+      vim9script
+      export def Opfunc(..._)
+        g:opfunc_called = 'yes'
+      enddef
+  END
+  writefile(lines, 'Xdir/autoload/opfunc.vim')
+
+  new
+  lines =<< trim END
+      vim9script
+      import autoload 'opfunc.vim'
+      nnoremap <expr> <F3> TheFunc()
+      def TheFunc(): string
+        &operatorfunc = opfunc.Opfunc
+        return 'g@'
+      enddef
+      feedkeys("\<F3>l", 'xt')
+      assert_equal('yes', g:opfunc_called)
+  END
+  CheckScriptSuccess(lines)
+
+  set opfunc=
+  bwipe!
+  delete('Xdir', 'rf')
+  nunmap <F3>
   &rtp = save_rtp
 enddef
 
--- 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 */
 /**/
+    4216,
+/**/
     4215,
 /**/
     4214,
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -92,6 +92,8 @@ typedef enum {
     ISN_NEWLIST,	// push list from stack items, size is isn_arg.number
     ISN_NEWDICT,	// push dict from stack items, size is isn_arg.number
 
+    ISN_AUTOLOAD,	// get item from autoload import, function or variable
+
     // function call
     ISN_BCALL,	    // call builtin function isn_arg.bfunc
     ISN_DCALL,	    // call def function isn_arg.dfunc
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -2260,6 +2260,77 @@ execute_for(isn_T *iptr, ectx_T *ectx)
 }
 
 /*
+ * Load instruction for w:/b:/g:/t: variable.
+ * "isn_type" is used instead of "iptr->isn_type".
+ */
+    static int
+load_namespace_var(ectx_T *ectx, isntype_T isn_type, isn_T *iptr)
+{
+    dictitem_T	*di = NULL;
+    hashtab_T	*ht = NULL;
+    char	namespace;
+
+    if (GA_GROW_FAILS(&ectx->ec_stack, 1))
+	return NOTDONE;
+    switch (isn_type)
+    {
+	case ISN_LOADG:
+	    ht = get_globvar_ht();
+	    namespace = 'g';
+	    break;
+	case ISN_LOADB:
+	    ht = &curbuf->b_vars->dv_hashtab;
+	    namespace = 'b';
+	    break;
+	case ISN_LOADW:
+	    ht = &curwin->w_vars->dv_hashtab;
+	    namespace = 'w';
+	    break;
+	case ISN_LOADT:
+	    ht = &curtab->tp_vars->dv_hashtab;
+	    namespace = 't';
+	    break;
+	default:  // Cannot reach here
+	    return NOTDONE;
+    }
+    di = find_var_in_ht(ht, 0, iptr->isn_arg.string, TRUE);
+
+    if (di == NULL && ht == get_globvar_ht()
+			    && vim_strchr(iptr->isn_arg.string,
+					AUTOLOAD_CHAR) != NULL)
+    {
+	// Global variable has an autoload name, may still need
+	// to load the script.
+	if (script_autoload(iptr->isn_arg.string, FALSE))
+	    di = find_var_in_ht(ht, 0,
+				   iptr->isn_arg.string, TRUE);
+	if (did_emsg)
+	    return FAIL;
+    }
+
+    if (di == NULL)
+    {
+	SOURCING_LNUM = iptr->isn_lnum;
+	if (vim_strchr(iptr->isn_arg.string,
+					AUTOLOAD_CHAR) != NULL)
+	    // no check if the item exists in the script but
+	    // isn't exported, it is too complicated
+	    semsg(_(e_item_not_found_in_script_str),
+					 iptr->isn_arg.string);
+	else
+	    semsg(_(e_undefined_variable_char_str),
+			     namespace, iptr->isn_arg.string);
+	return FAIL;
+    }
+    else
+    {
+	copy_tv(&di->di_tv, STACK_TV_BOT(0));
+	++ectx->ec_stack.ga_len;
+    }
+    return OK;
+}
+
+/*
  * Execute instructions in execution context "ectx".
  * Return OK or FAIL;
  */
@@ -2772,68 +2843,14 @@ exec_instructions(ectx_T *ectx)
 	    case ISN_LOADW:
 	    case ISN_LOADT:
 		{
-		    dictitem_T	*di = NULL;
-		    hashtab_T	*ht = NULL;
-		    char	namespace;
-
-		    if (GA_GROW_FAILS(&ectx->ec_stack, 1))
+		    int res = load_namespace_var(ectx, iptr->isn_type, iptr);
+
+		    if (res == NOTDONE)
 			goto theend;
-		    switch (iptr->isn_type)
-		    {
-			case ISN_LOADG:
-			    ht = get_globvar_ht();
-			    namespace = 'g';
-			    break;
-			case ISN_LOADB:
-			    ht = &curbuf->b_vars->dv_hashtab;
-			    namespace = 'b';
-			    break;
-			case ISN_LOADW:
-			    ht = &curwin->w_vars->dv_hashtab;
-			    namespace = 'w';
-			    break;
-			case ISN_LOADT:
-			    ht = &curtab->tp_vars->dv_hashtab;
-			    namespace = 't';
-			    break;
-			default:  // Cannot reach here
-			    goto theend;
-		    }
-		    di = find_var_in_ht(ht, 0, iptr->isn_arg.string, TRUE);
-
-		    if (di == NULL && ht == get_globvar_ht()
-					    && vim_strchr(iptr->isn_arg.string,
-							AUTOLOAD_CHAR) != NULL)
-		    {
-			// Global variable has an autoload name, may still need
-			// to load the script.
-			if (script_autoload(iptr->isn_arg.string, FALSE))
-			    di = find_var_in_ht(ht, 0,
-						   iptr->isn_arg.string, TRUE);
-			if (did_emsg)
-			    goto on_error;
-		    }
-
-		    if (di == NULL)
-		    {
-			SOURCING_LNUM = iptr->isn_lnum;
-			if (vim_strchr(iptr->isn_arg.string,
-							AUTOLOAD_CHAR) != NULL)
-			    // no check if the item exists in the script but
-			    // isn't exported, it is too complicated
-			    semsg(_(e_item_not_found_in_script_str),
-							 iptr->isn_arg.string);
-			else
-			    semsg(_(e_undefined_variable_char_str),
-					     namespace, iptr->isn_arg.string);
+		    if (res == FAIL)
 			goto on_error;
-		    }
-		    else
-		    {
-			copy_tv(&di->di_tv, STACK_TV_BOT(0));
-			++ectx->ec_stack.ga_len;
-		    }
 		}
+
 		break;
 
 	    // load autoload variable
@@ -3264,6 +3281,33 @@ exec_instructions(ectx_T *ectx)
 		}
 		break;
 
+	    case ISN_AUTOLOAD:
+		{
+		    char_u  *name = iptr->isn_arg.string;
+
+		    (void)script_autoload(name, FALSE);
+		    if (find_func(name, TRUE))
+		    {
+			if (GA_GROW_FAILS(&ectx->ec_stack, 1))
+			    goto theend;
+			tv = STACK_TV_BOT(0);
+			tv->v_lock = 0;
+			++ectx->ec_stack.ga_len;
+			tv->v_type = VAR_FUNC;
+			tv->vval.v_string = vim_strsave(name);
+		    }
+		    else
+		    {
+			int res = load_namespace_var(ectx, ISN_LOADG, iptr);
+
+			if (res == NOTDONE)
+			    goto theend;
+			if (res == FAIL)
+			    goto on_error;
+		    }
+		}
+		break;
+
 	    case ISN_UNLET:
 		if (do_unlet(iptr->isn_arg.unlet.ul_name,
 				       iptr->isn_arg.unlet.ul_forceit) == FAIL)
@@ -5596,6 +5640,9 @@ list_instructions(char *pfx, isn_T *inst
 	    case ISN_PUSHEXC:
 		smsg("%s%4d PUSH v:exception", pfx, current);
 		break;
+	    case ISN_AUTOLOAD:
+		smsg("%s%4d AUTOLOAD %s", pfx, current, iptr->isn_arg.string);
+		break;
 	    case ISN_UNLET:
 		smsg("%s%4d UNLET%s %s", pfx, current,
 			iptr->isn_arg.unlet.ul_forceit ? "!" : "",
--- a/src/vim9expr.c
+++ b/src/vim9expr.c
@@ -307,11 +307,12 @@ compile_load_scriptvar(
 	    char_u  *auto_name = concat_str(si->sn_autoload_prefix, exp_name);
 
 	    // autoload script must be loaded later, access by the autoload
-	    // name.
+	    // name.  If a '(' follows it must be a function.  Otherwise we
+	    // don't know, it can be "script.Func".
 	    if (cc == '(' || paren_follows_after_expr)
 		res = generate_PUSHFUNC(cctx, auto_name, &t_func_any);
 	    else
-		res = generate_LOAD(cctx, ISN_LOADG, 0, auto_name, &t_any);
+		res = generate_AUTOLOAD(cctx, auto_name, &t_any);
 	    vim_free(auto_name);
 	    done = TRUE;
 	}
--- a/src/vim9instr.c
+++ b/src/vim9instr.c
@@ -744,6 +744,23 @@ generate_PUSHFUNC(cctx_T *cctx, char_u *
 }
 
 /*
+ * Generate an ISN_AUTOLOAD instruction.
+ */
+    int
+generate_AUTOLOAD(cctx_T *cctx, char_u *name, type_T *type)
+{
+    isn_T	*isn;
+
+    RETURN_OK_IF_SKIP(cctx);
+    if ((isn = generate_instr_type(cctx, ISN_AUTOLOAD, type)) == NULL)
+	return FAIL;
+    isn->isn_arg.string = vim_strsave(name);
+    if (isn->isn_arg.string == NULL)
+	return FAIL;
+    return OK;
+}
+
+/*
  * Generate an ISN_GETITEM instruction with "index".
  * "with_op" is TRUE for "+=" and other operators, the stack has the current
  * value below the list with values.
@@ -1929,6 +1946,7 @@ delete_instr(isn_T *isn)
 {
     switch (isn->isn_type)
     {
+	case ISN_AUTOLOAD:
 	case ISN_DEF:
 	case ISN_EXEC:
 	case ISN_EXECRANGE: