# HG changeset patch # User Bram Moolenaar # Date 1643126406 -3600 # Node ID 1a6421c5be20db8194444f23c0083fba238611ac # Parent 90523078c8e29b202f7cc2181a0d00f803ac615d 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 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) diff --git a/src/proto/vim9instr.pro b/src/proto/vim9instr.pro --- 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); diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim --- 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('\d*_ScriptFuncPush.*' .. - 'localbool = true.*' .. - ' PUSH true.*' .. - 'localspec = v:none.*' .. - ' PUSH v:none.*' .. - 'localblob = 0z1234.*' .. - ' PUSHBLOB 0z1234.*', - res) - if has('float') - assert_match('\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('\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() diff --git a/src/testdir/test_vim9_import.vim b/src/testdir/test_vim9_import.vim --- 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 + &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 TheFunc() + def TheFunc(): string + &operatorfunc = opfunc.Opfunc + return 'g@' + enddef + feedkeys("\l", 'xt') + assert_equal('yes', g:opfunc_called) + END + CheckScriptSuccess(lines) + + set opfunc= + bwipe! + delete('Xdir', 'rf') + nunmap &rtp = save_rtp enddef diff --git a/src/version.c b/src/version.c --- 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, diff --git a/src/vim9.h b/src/vim9.h --- 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 diff --git a/src/vim9execute.c b/src/vim9execute.c --- 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 ? "!" : "", diff --git a/src/vim9expr.c b/src/vim9expr.c --- 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; } diff --git a/src/vim9instr.c b/src/vim9instr.c --- 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: