forked from aniani/vim
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)
This commit is contained in:
@@ -23,6 +23,7 @@ int generate_PUSHCHANNEL(cctx_T *cctx, channel_T *channel);
|
|||||||
int generate_PUSHJOB(cctx_T *cctx, job_T *job);
|
int generate_PUSHJOB(cctx_T *cctx, job_T *job);
|
||||||
int generate_PUSHBLOB(cctx_T *cctx, blob_T *blob);
|
int generate_PUSHBLOB(cctx_T *cctx, blob_T *blob);
|
||||||
int generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type);
|
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_GETITEM(cctx_T *cctx, int index, int with_op);
|
||||||
int generate_SLICE(cctx_T *cctx, int count);
|
int generate_SLICE(cctx_T *cctx, int count);
|
||||||
int generate_CHECKLEN(cctx_T *cctx, int min_len, int more_OK);
|
int generate_CHECKLEN(cctx_T *cctx, int min_len, int more_OK);
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
" Test the :disassemble command, and compilation as a side effect
|
" Test the :disassemble command, and compilation as a side effect
|
||||||
|
|
||||||
source check.vim
|
source check.vim
|
||||||
|
source vim9.vim
|
||||||
|
|
||||||
func NotCompiled()
|
func NotCompiled()
|
||||||
echo "not"
|
echo "not"
|
||||||
@@ -286,21 +287,35 @@ def s:ScriptFuncPush()
|
|||||||
enddef
|
enddef
|
||||||
|
|
||||||
def Test_disassemble_push()
|
def Test_disassemble_push()
|
||||||
var res = execute('disass s:ScriptFuncPush')
|
mkdir('Xdir/autoload', 'p')
|
||||||
assert_match('<SNR>\d*_ScriptFuncPush.*' ..
|
var save_rtp = &rtp
|
||||||
'localbool = true.*' ..
|
exe 'set rtp^=' .. getcwd() .. '/Xdir'
|
||||||
' PUSH true.*' ..
|
|
||||||
'localspec = v:none.*' ..
|
var lines =<< trim END
|
||||||
' PUSH v:none.*' ..
|
vim9script
|
||||||
'localblob = 0z1234.*' ..
|
END
|
||||||
' PUSHBLOB 0z1234.*',
|
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)
|
res)
|
||||||
if has('float')
|
END
|
||||||
assert_match('<SNR>\d*_ScriptFuncPush.*' ..
|
CheckScriptSuccess(lines)
|
||||||
'localfloat = 1.234.*' ..
|
|
||||||
' PUSHF 1.234.*',
|
delete('Xdir', 'rf')
|
||||||
res)
|
&rtp = save_rtp
|
||||||
endif
|
|
||||||
enddef
|
enddef
|
||||||
|
|
||||||
def s:ScriptFuncStore()
|
def s:ScriptFuncStore()
|
||||||
|
@@ -703,6 +703,41 @@ def Test_use_autoload_import_partial_in_opfunc()
|
|||||||
set opfunc=
|
set opfunc=
|
||||||
bwipe!
|
bwipe!
|
||||||
delete('Xdir', 'rf')
|
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
|
&rtp = save_rtp
|
||||||
enddef
|
enddef
|
||||||
|
|
||||||
|
@@ -750,6 +750,8 @@ static char *(features[]) =
|
|||||||
|
|
||||||
static int included_patches[] =
|
static int included_patches[] =
|
||||||
{ /* Add new patch number below this line */
|
{ /* Add new patch number below this line */
|
||||||
|
/**/
|
||||||
|
4216,
|
||||||
/**/
|
/**/
|
||||||
4215,
|
4215,
|
||||||
/**/
|
/**/
|
||||||
|
@@ -92,6 +92,8 @@ typedef enum {
|
|||||||
ISN_NEWLIST, // push list from stack items, size is isn_arg.number
|
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_NEWDICT, // push dict from stack items, size is isn_arg.number
|
||||||
|
|
||||||
|
ISN_AUTOLOAD, // get item from autoload import, function or variable
|
||||||
|
|
||||||
// function call
|
// function call
|
||||||
ISN_BCALL, // call builtin function isn_arg.bfunc
|
ISN_BCALL, // call builtin function isn_arg.bfunc
|
||||||
ISN_DCALL, // call def function isn_arg.dfunc
|
ISN_DCALL, // call def function isn_arg.dfunc
|
||||||
|
@@ -2259,6 +2259,77 @@ execute_for(isn_T *iptr, ectx_T *ectx)
|
|||||||
return OK;
|
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".
|
* Execute instructions in execution context "ectx".
|
||||||
* Return OK or FAIL;
|
* Return OK or FAIL;
|
||||||
@@ -2772,68 +2843,14 @@ exec_instructions(ectx_T *ectx)
|
|||||||
case ISN_LOADW:
|
case ISN_LOADW:
|
||||||
case ISN_LOADT:
|
case ISN_LOADT:
|
||||||
{
|
{
|
||||||
dictitem_T *di = NULL;
|
int res = load_namespace_var(ectx, iptr->isn_type, iptr);
|
||||||
hashtab_T *ht = NULL;
|
|
||||||
char namespace;
|
|
||||||
|
|
||||||
if (GA_GROW_FAILS(&ectx->ec_stack, 1))
|
if (res == NOTDONE)
|
||||||
goto theend;
|
goto theend;
|
||||||
switch (iptr->isn_type)
|
if (res == FAIL)
|
||||||
{
|
|
||||||
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;
|
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);
|
|
||||||
goto on_error;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
copy_tv(&di->di_tv, STACK_TV_BOT(0));
|
|
||||||
++ectx->ec_stack.ga_len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// load autoload variable
|
// load autoload variable
|
||||||
@@ -3264,6 +3281,33 @@ exec_instructions(ectx_T *ectx)
|
|||||||
}
|
}
|
||||||
break;
|
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:
|
case ISN_UNLET:
|
||||||
if (do_unlet(iptr->isn_arg.unlet.ul_name,
|
if (do_unlet(iptr->isn_arg.unlet.ul_name,
|
||||||
iptr->isn_arg.unlet.ul_forceit) == FAIL)
|
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:
|
case ISN_PUSHEXC:
|
||||||
smsg("%s%4d PUSH v:exception", pfx, current);
|
smsg("%s%4d PUSH v:exception", pfx, current);
|
||||||
break;
|
break;
|
||||||
|
case ISN_AUTOLOAD:
|
||||||
|
smsg("%s%4d AUTOLOAD %s", pfx, current, iptr->isn_arg.string);
|
||||||
|
break;
|
||||||
case ISN_UNLET:
|
case ISN_UNLET:
|
||||||
smsg("%s%4d UNLET%s %s", pfx, current,
|
smsg("%s%4d UNLET%s %s", pfx, current,
|
||||||
iptr->isn_arg.unlet.ul_forceit ? "!" : "",
|
iptr->isn_arg.unlet.ul_forceit ? "!" : "",
|
||||||
|
@@ -307,11 +307,12 @@ compile_load_scriptvar(
|
|||||||
char_u *auto_name = concat_str(si->sn_autoload_prefix, exp_name);
|
char_u *auto_name = concat_str(si->sn_autoload_prefix, exp_name);
|
||||||
|
|
||||||
// autoload script must be loaded later, access by the autoload
|
// 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)
|
if (cc == '(' || paren_follows_after_expr)
|
||||||
res = generate_PUSHFUNC(cctx, auto_name, &t_func_any);
|
res = generate_PUSHFUNC(cctx, auto_name, &t_func_any);
|
||||||
else
|
else
|
||||||
res = generate_LOAD(cctx, ISN_LOADG, 0, auto_name, &t_any);
|
res = generate_AUTOLOAD(cctx, auto_name, &t_any);
|
||||||
vim_free(auto_name);
|
vim_free(auto_name);
|
||||||
done = TRUE;
|
done = TRUE;
|
||||||
}
|
}
|
||||||
|
@@ -743,6 +743,23 @@ generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type)
|
|||||||
return OK;
|
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".
|
* Generate an ISN_GETITEM instruction with "index".
|
||||||
* "with_op" is TRUE for "+=" and other operators, the stack has the current
|
* "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)
|
switch (isn->isn_type)
|
||||||
{
|
{
|
||||||
|
case ISN_AUTOLOAD:
|
||||||
case ISN_DEF:
|
case ISN_DEF:
|
||||||
case ISN_EXEC:
|
case ISN_EXEC:
|
||||||
case ISN_EXECRANGE:
|
case ISN_EXECRANGE:
|
||||||
|
Reference in New Issue
Block a user