]> granicus.if.org Git - vim/commitdiff
patch 8.2.4216: Vim9: cannot use a function from an autoload import directly v8.2.4216
authorBram Moolenaar <Bram@vim.org>
Tue, 25 Jan 2022 15:51:56 +0000 (15:51 +0000)
committerBram Moolenaar <Bram@vim.org>
Tue, 25 Jan 2022 15:51:56 +0000 (15:51 +0000)
Problem:    Vim9: cannot use a function from an autoload import directly.
Solution:   Add the AUTOLOAD instruction to figure out at runtime.
            (closes #9620)

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

index 04ac89c7a83442aef328e8344f0686c963ba8d38..be41d7c04202f53a7b2ac88b6d6068c6331de204 100644 (file)
@@ -23,6 +23,7 @@ int generate_PUSHCHANNEL(cctx_T *cctx, channel_T *channel);
 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);
index 3a7642f7c27e026c606f015b6fe2f38da8867884..8731052aa31d05482d3d0129994496f862aafbea 100644 (file)
@@ -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()
index 506b0ee8d74a49a0b42c9a8ab6ae705419ea106c..7878788a0dc90b49601ecc78d1f56e0103b4312a 100644 (file)
@@ -703,6 +703,41 @@ def Test_use_autoload_import_partial_in_opfunc()
   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
 
index ddc34d864be9863bf0bb4d4d15c07e693398c9ee..f5f9a2ef9427053c675ddfcf3f8395361891ee61 100644 (file)
@@ -750,6 +750,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    4216,
 /**/
     4215,
 /**/
index 912af1f5d513ef8a9868cbb9926785ccf259e620..45e97a2db94feda2a1d7cf3c5cb1a6f31610a6b0 100644 (file)
@@ -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
index e58da5b5e883b9baf6c751fb6a2a3fafa96034d9..1232b35bb52f8ce310aa2201eb20fc20c98ffbeb 100644 (file)
@@ -2259,6 +2259,77 @@ execute_for(isn_T *iptr, ectx_T *ectx)
     return OK;
 }
 
+/*
+ * 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;
+                   int res = load_namespace_var(ectx, iptr->isn_type, iptr);
 
-                   if (GA_GROW_FAILS(&ectx->ec_stack, 1))
+                   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 *instr, int instr_count, ufunc_T *ufunc)
            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 ? "!" : "",
index d5bb276b7df68d23670edb7d4e57a446c4217656..a56219fb349f8d58b30802035a4b280d5b673344 100644 (file)
@@ -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;
        }
index b6e28b0b948eca29d0a1a6419e40f0f05359e42d..d000c4acd68a4a2168d5c0f48b36f32de7723fb5 100644 (file)
@@ -743,6 +743,23 @@ generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type)
     return OK;
 }
 
+/*
+ * 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
@@ -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: