forked from aniani/vim
patch 8.2.2677: Vim9: cannot use only some of the default arguments
Problem: Vim9: cannot use only some of the default arguments. Solution: Use v:none to use default argument value. Remove uf_def_arg_idx[], use JUMP_IF_ARG_SET. (closes #6504)
This commit is contained in:
parent
9ea7e55ab9
commit
38a3bfa9a2
@ -125,6 +125,10 @@ that starts a comment: >
|
||||
var name = value # comment
|
||||
var name = value# error!
|
||||
|
||||
Do not start a comment with #{, it looks like the legacy dictionary literal
|
||||
and produces an error where this might be confusing. #{{ or #{{{ are OK,
|
||||
these can be used to start a fold.
|
||||
|
||||
In legacy Vim script # is also used for the alternate file name. In Vim9
|
||||
script you need to use %% instead. Instead of ## use %%% (stands for all
|
||||
arguments).
|
||||
@ -164,6 +168,15 @@ list type, similar to TypeScript. For example, a list of numbers: >
|
||||
for item in itemlist
|
||||
...
|
||||
|
||||
When a function argument is optional (it has a default value) passing `v:none`
|
||||
as the argument results in using the default value. This is useful when you
|
||||
want to specify a value for an argument that comes after an argument that
|
||||
should use its default value. Example: >
|
||||
def MyFunc(one = 'one', last = 'last)
|
||||
...
|
||||
enddef
|
||||
MyFunc(v:none, 'LAST') # first argument uses default value 'one'
|
||||
|
||||
|
||||
Functions and variables are script-local by default ~
|
||||
*vim9-scopes*
|
||||
@ -190,6 +203,12 @@ search for the function:
|
||||
However, it is recommended to always use "g:" to refer to a global function
|
||||
for clarity.
|
||||
|
||||
Since a script-local function reference can be used without "s:" the name must
|
||||
start with an upper case letter even when using the ":s" prefix. In legacy
|
||||
script "s:funcref" could be used, because it could not be referred to with
|
||||
"funcref". In Vim9 script it can, therefore "s:Funcref" must be used to avoid
|
||||
that the name interferes with builtin functions.
|
||||
|
||||
In all cases the function must be defined before used. That is when it is
|
||||
called, when `:defcompile` causes it to be compiled, or when code that calls
|
||||
it is being compiled (to figure out the return type).
|
||||
@ -279,6 +298,9 @@ without any command. The same for global, window, tab, buffer and Vim
|
||||
variables, because they are not really declared. They can also be deleted
|
||||
with `:unlet`.
|
||||
|
||||
`:lockvar` does not work on local variables. Use `:const` and `:final`
|
||||
instead.
|
||||
|
||||
Variables, functions and function arguments cannot shadow previously defined
|
||||
or imported variables and functions in the same script file.
|
||||
Variables may shadow Ex commands, rename the variable if needed.
|
||||
@ -409,7 +431,18 @@ Additionally, a lambda can contain statements in {}: >
|
||||
g:was_called = 'yes'
|
||||
return expression
|
||||
}
|
||||
NOT IMPLEMENTED YET
|
||||
|
||||
The ending "}" must be at the start of a line. It can be followed by other
|
||||
characters, e.g.: >
|
||||
var d = mapnew(dict, (k, v): string => {
|
||||
return 'value'
|
||||
})
|
||||
No command can follow the "{", only a comment can be used there.
|
||||
|
||||
Rationale: The "}" cannot be after a command because it would require parsing
|
||||
the commands to find it. For consistency with that no command can follow the
|
||||
"{". Unfortunately this means using "() => { command }" does not work, line
|
||||
breaks are always required.
|
||||
|
||||
*vim9-curly*
|
||||
To avoid the "{" of a dictionary literal to be recognized as a statement block
|
||||
@ -705,6 +738,7 @@ In legacy script this results in the character 0xc3 (an illegal byte), in Vim9
|
||||
script this results in the string 'á'.
|
||||
A negative index is counting from the end, "[-1]" is the last character.
|
||||
To exclude the last character use |slice()|.
|
||||
To count composing characters separately use |strcharpart()|.
|
||||
If the index is out of range then an empty string results.
|
||||
|
||||
In legacy script "++var" and "--var" would be silently accepted and have no
|
||||
@ -972,6 +1006,8 @@ And classes and interfaces can be used as types: >
|
||||
:var mine: MyInterface<string>
|
||||
{not implemented yet}
|
||||
|
||||
You may also find this wiki useful. It was written by an early adoptor of
|
||||
Vim9 script: https://github.com/lacygoill/wiki/blob/master/vim/vim9.md
|
||||
|
||||
Variable types and type casting ~
|
||||
*variable-types*
|
||||
@ -1044,6 +1080,27 @@ to a list of numbers.
|
||||
Same for |extend()|, use |extendnew()| instead, and for |flatten()|, use
|
||||
|flattennew()| instead.
|
||||
|
||||
Closures defined in a loop will share the same context. For example: >
|
||||
var flist: list<func>
|
||||
for i in range(10)
|
||||
var inloop = i
|
||||
flist[i] = () => inloop
|
||||
endfor
|
||||
|
||||
The "inloop" variable will exist only once, all closures put in the list refer
|
||||
to the same instance, which in the end will have the value 9. This is
|
||||
efficient. If you do want a separate context for each closure call a function
|
||||
to define it: >
|
||||
def GetFunc(i: number): func
|
||||
var inloop = i
|
||||
return () => inloop
|
||||
enddef
|
||||
|
||||
var flist: list<func>
|
||||
for i in range(10)
|
||||
flist[i] = GetFunc(i)
|
||||
endfor
|
||||
|
||||
==============================================================================
|
||||
|
||||
5. Namespace, Import and Export
|
||||
|
@ -1607,8 +1607,6 @@ typedef struct
|
||||
type_T **uf_arg_types; // argument types (count == uf_args.ga_len)
|
||||
type_T *uf_ret_type; // return type
|
||||
garray_T uf_type_list; // types used in arg and return types
|
||||
int *uf_def_arg_idx; // instruction indexes for evaluating
|
||||
// uf_def_args; length: uf_def_args.ga_len + 1
|
||||
partial_T *uf_partial; // for closure created inside :def function:
|
||||
// information about the context
|
||||
|
||||
|
@ -641,18 +641,25 @@ def Test_disassemble_update_instr()
|
||||
enddef
|
||||
|
||||
|
||||
def FuncWithDefault(arg: string = 'default'): string
|
||||
return arg
|
||||
def FuncWithDefault(arg: string = 'default', nr = 77): string
|
||||
return arg .. nr
|
||||
enddef
|
||||
|
||||
def Test_disassemble_call_default()
|
||||
var res = execute('disass FuncWithDefault')
|
||||
assert_match('FuncWithDefault\_s*' ..
|
||||
'\d JUMP_IF_ARG_SET arg\[-2\] -> 3\_s*' ..
|
||||
'\d PUSHS "default"\_s*' ..
|
||||
'\d STORE arg\[-2]\_s*' ..
|
||||
'3 JUMP_IF_ARG_SET arg\[-1\] -> 6\_s*' ..
|
||||
'\d PUSHNR 77\_s*' ..
|
||||
'\d STORE arg\[-1]\_s*' ..
|
||||
'return arg\_s*' ..
|
||||
'return arg .. nr\_s*' ..
|
||||
'6 LOAD arg\[-2]\_s*' ..
|
||||
'\d LOAD arg\[-1]\_s*' ..
|
||||
'\d RETURN',
|
||||
'\d 2STRING stack\[-1]\_s*' ..
|
||||
'\d\+ CONCAT\_s*' ..
|
||||
'\d\+ RETURN',
|
||||
res)
|
||||
enddef
|
||||
|
||||
|
@ -308,21 +308,38 @@ def MyDefaultSecond(name: string, second: bool = true): string
|
||||
return second ? name : 'none'
|
||||
enddef
|
||||
|
||||
|
||||
def Test_call_default_args()
|
||||
MyDefaultArgs()->assert_equal('string')
|
||||
MyDefaultArgs(v:none)->assert_equal('string')
|
||||
MyDefaultArgs('one')->assert_equal('one')
|
||||
assert_fails('MyDefaultArgs("one", "two")', 'E118:', '', 3, 'Test_call_default_args')
|
||||
assert_fails('MyDefaultArgs("one", "two")', 'E118:', '', 4, 'Test_call_default_args')
|
||||
|
||||
MyDefaultSecond('test')->assert_equal('test')
|
||||
MyDefaultSecond('test', true)->assert_equal('test')
|
||||
MyDefaultSecond('test', false)->assert_equal('none')
|
||||
|
||||
var lines =<< trim END
|
||||
def MyDefaultThird(name: string, aa = 'aa', bb = 'bb'): string
|
||||
return name .. aa .. bb
|
||||
enddef
|
||||
|
||||
MyDefaultThird('->')->assert_equal('->aabb')
|
||||
MyDefaultThird('->', v:none)->assert_equal('->aabb')
|
||||
MyDefaultThird('->', 'xx')->assert_equal('->xxbb')
|
||||
MyDefaultThird('->', v:none, v:none)->assert_equal('->aabb')
|
||||
MyDefaultThird('->', 'xx', v:none)->assert_equal('->xxbb')
|
||||
MyDefaultThird('->', v:none, 'yy')->assert_equal('->aayy')
|
||||
MyDefaultThird('->', 'xx', 'yy')->assert_equal('->xxyy')
|
||||
END
|
||||
CheckDefAndScriptSuccess(lines)
|
||||
|
||||
CheckScriptFailure(['def Func(arg: number = asdf)', 'enddef', 'defcompile'], 'E1001:')
|
||||
delfunc g:Func
|
||||
CheckScriptFailure(['def Func(arg: number = "text")', 'enddef', 'defcompile'], 'E1013: Argument 1: type mismatch, expected number but got string')
|
||||
delfunc g:Func
|
||||
|
||||
var lines =<< trim END
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
def Func(a = b == 0 ? 1 : 2, b = 0)
|
||||
enddef
|
||||
|
@ -1914,7 +1914,6 @@ func_clear_items(ufunc_T *fp)
|
||||
ga_clear_strings(&(fp->uf_def_args));
|
||||
ga_clear_strings(&(fp->uf_lines));
|
||||
VIM_CLEAR(fp->uf_arg_types);
|
||||
VIM_CLEAR(fp->uf_def_arg_idx);
|
||||
VIM_CLEAR(fp->uf_block_ids);
|
||||
VIM_CLEAR(fp->uf_va_name);
|
||||
clear_type_list(&fp->uf_type_list);
|
||||
@ -2049,14 +2048,6 @@ copy_func(char_u *lambda, char_u *global, ectx_T *ectx)
|
||||
mch_memmove(fp->uf_arg_types, ufunc->uf_arg_types,
|
||||
sizeof(type_T *) * fp->uf_args.ga_len);
|
||||
}
|
||||
if (ufunc->uf_def_arg_idx != NULL)
|
||||
{
|
||||
fp->uf_def_arg_idx = ALLOC_MULT(int, fp->uf_def_args.ga_len + 1);
|
||||
if (fp->uf_def_arg_idx == NULL)
|
||||
goto failed;
|
||||
mch_memmove(fp->uf_def_arg_idx, ufunc->uf_def_arg_idx,
|
||||
sizeof(int) * fp->uf_def_args.ga_len + 1);
|
||||
}
|
||||
if (ufunc->uf_va_name != NULL)
|
||||
{
|
||||
fp->uf_va_name = vim_strsave(ufunc->uf_va_name);
|
||||
|
@ -750,6 +750,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
2677,
|
||||
/**/
|
||||
2676,
|
||||
/**/
|
||||
|
@ -92,6 +92,7 @@ typedef enum {
|
||||
|
||||
// expression operations
|
||||
ISN_JUMP, // jump if condition is matched isn_arg.jump
|
||||
ISN_JUMP_IF_ARG_SET, // jump if argument is already set, uses isn_arg.jumparg
|
||||
|
||||
// loop
|
||||
ISN_FOR, // get next item from a list, uses isn_arg.forloop
|
||||
@ -203,6 +204,12 @@ typedef struct {
|
||||
int jump_where; // position to jump to
|
||||
} jump_T;
|
||||
|
||||
// arguments to ISN_JUMP_IF_ARG_SET
|
||||
typedef struct {
|
||||
int jump_arg_off; // argument index, negative
|
||||
int jump_where; // position to jump to
|
||||
} jumparg_T;
|
||||
|
||||
// arguments to ISN_FOR
|
||||
typedef struct {
|
||||
int for_idx; // loop variable index
|
||||
@ -346,6 +353,7 @@ struct isn_S {
|
||||
job_T *job;
|
||||
partial_T *partial;
|
||||
jump_T jump;
|
||||
jumparg_T jumparg;
|
||||
forloop_T forloop;
|
||||
try_T try;
|
||||
trycont_T trycont;
|
||||
|
@ -1629,6 +1629,22 @@ generate_JUMP(cctx_T *cctx, jumpwhen_T when, int where)
|
||||
return OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate an ISN_JUMP_IF_ARG_SET instruction.
|
||||
*/
|
||||
static int
|
||||
generate_JUMP_IF_ARG_SET(cctx_T *cctx, int arg_off)
|
||||
{
|
||||
isn_T *isn;
|
||||
|
||||
RETURN_OK_IF_SKIP(cctx);
|
||||
if ((isn = generate_instr(cctx, ISN_JUMP_IF_ARG_SET)) == NULL)
|
||||
return FAIL;
|
||||
isn->isn_arg.jumparg.jump_arg_off = arg_off;
|
||||
// jump_where is set later
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int
|
||||
generate_FOR(cctx_T *cctx, int loop_idx)
|
||||
{
|
||||
@ -1834,6 +1850,13 @@ generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount)
|
||||
type_T *expected;
|
||||
type_T *actual;
|
||||
|
||||
actual = ((type_T **)stack->ga_data)[stack->ga_len - argcount + i];
|
||||
if (actual == &t_special
|
||||
&& i >= regular_args - ufunc->uf_def_args.ga_len)
|
||||
{
|
||||
// assume v:none used for default argument value
|
||||
continue;
|
||||
}
|
||||
if (i < regular_args)
|
||||
{
|
||||
if (ufunc->uf_arg_types == NULL)
|
||||
@ -1845,7 +1868,6 @@ generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount)
|
||||
expected = &t_any;
|
||||
else
|
||||
expected = ufunc->uf_va_type->tt_member;
|
||||
actual = ((type_T **)stack->ga_data)[stack->ga_len - argcount + i];
|
||||
if (need_type(actual, expected, -argcount + i, i + 1, cctx,
|
||||
TRUE, FALSE) == FAIL)
|
||||
{
|
||||
@ -1961,6 +1983,9 @@ generate_PCALL(
|
||||
if (varargs && i >= type->tt_argcount - 1)
|
||||
expected = type->tt_args[
|
||||
type->tt_argcount - 1]->tt_member;
|
||||
else if (i >= type->tt_min_argcount
|
||||
&& actual == &t_special)
|
||||
expected = &t_any;
|
||||
else
|
||||
expected = type->tt_args[i];
|
||||
if (need_type(actual, expected, offset, i + 1,
|
||||
@ -8363,12 +8388,6 @@ compile_def_function(
|
||||
int did_set_arg_type = FALSE;
|
||||
|
||||
// Produce instructions for the default values of optional arguments.
|
||||
// Store the instruction index in uf_def_arg_idx[] so that we know
|
||||
// where to start when the function is called, depending on the number
|
||||
// of arguments.
|
||||
ufunc->uf_def_arg_idx = ALLOC_CLEAR_MULT(int, count + 1);
|
||||
if (ufunc->uf_def_arg_idx == NULL)
|
||||
goto erret;
|
||||
SOURCING_LNUM = 0; // line number unknown
|
||||
for (i = 0; i < count; ++i)
|
||||
{
|
||||
@ -8377,11 +8396,16 @@ compile_def_function(
|
||||
int arg_idx = first_def_arg + i;
|
||||
where_T where;
|
||||
int r;
|
||||
int jump_instr_idx = instr->ga_len;
|
||||
isn_T *isn;
|
||||
|
||||
// Use a JUMP_IF_ARG_SET instruction to skip if the value was given.
|
||||
if (generate_JUMP_IF_ARG_SET(&cctx, i - count - off) == FAIL)
|
||||
goto erret;
|
||||
|
||||
// Make sure later arguments are not found.
|
||||
ufunc->uf_args.ga_len = i;
|
||||
|
||||
ufunc->uf_def_arg_idx[i] = instr->ga_len;
|
||||
arg = ((char_u **)(ufunc->uf_def_args.ga_data))[i];
|
||||
r = compile_expr0(&arg, &cctx);
|
||||
|
||||
@ -8406,8 +8430,11 @@ compile_def_function(
|
||||
|
||||
if (generate_STORE(&cctx, ISN_STORE, i - count - off, NULL) == FAIL)
|
||||
goto erret;
|
||||
|
||||
// set instruction index in JUMP_IF_ARG_SET to here
|
||||
isn = ((isn_T *)instr->ga_data) + jump_instr_idx;
|
||||
isn->isn_arg.jumparg.jump_where = instr->ga_len;
|
||||
}
|
||||
ufunc->uf_def_arg_idx[count] = instr->ga_len;
|
||||
|
||||
if (did_set_arg_type)
|
||||
set_function_type(ufunc);
|
||||
@ -9114,6 +9141,7 @@ delete_instr(isn_T *isn)
|
||||
case ISN_FOR:
|
||||
case ISN_GETITEM:
|
||||
case ISN_JUMP:
|
||||
case ISN_JUMP_IF_ARG_SET:
|
||||
case ISN_LISTAPPEND:
|
||||
case ISN_LISTINDEX:
|
||||
case ISN_LISTSLICE:
|
||||
|
@ -96,35 +96,6 @@ ufunc_argcount(ufunc_T *ufunc)
|
||||
return ufunc->uf_args.ga_len + (ufunc->uf_va_name != NULL ? 1 : 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the instruction index, depending on omitted arguments, where the default
|
||||
* values are to be computed. If all optional arguments are present, start
|
||||
* with the function body.
|
||||
* The expression evaluation is at the start of the instructions:
|
||||
* 0 -> EVAL default1
|
||||
* STORE arg[-2]
|
||||
* 1 -> EVAL default2
|
||||
* STORE arg[-1]
|
||||
* 2 -> function body
|
||||
*/
|
||||
static void
|
||||
init_instr_idx(ufunc_T *ufunc, int argcount, ectx_T *ectx)
|
||||
{
|
||||
if (ufunc->uf_def_args.ga_len == 0)
|
||||
ectx->ec_iidx = 0;
|
||||
else
|
||||
{
|
||||
int defcount = ufunc->uf_args.ga_len - argcount;
|
||||
|
||||
// If there is a varargs argument defcount can be negative, no defaults
|
||||
// to evaluate then.
|
||||
if (defcount < 0)
|
||||
defcount = 0;
|
||||
ectx->ec_iidx = ufunc->uf_def_arg_idx[
|
||||
ufunc->uf_def_args.ga_len - defcount];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a new list from "count" items at the bottom of the stack.
|
||||
* When "count" is zero an empty list is added to the stack.
|
||||
@ -363,8 +334,8 @@ call_dfunc(
|
||||
current_sctx.sc_sid = ufunc->uf_script_ctx.sc_sid;
|
||||
}
|
||||
|
||||
// Decide where to start execution, handles optional arguments.
|
||||
init_instr_idx(ufunc, argcount, ectx);
|
||||
// Start execution at the first instruction.
|
||||
ectx->ec_iidx = 0;
|
||||
|
||||
return OK;
|
||||
}
|
||||
@ -1367,11 +1338,21 @@ call_def_function(
|
||||
&& (ufunc->uf_va_name != NULL || idx < ufunc->uf_args.ga_len);
|
||||
++idx)
|
||||
{
|
||||
if (ufunc->uf_arg_types != NULL && idx < ufunc->uf_args.ga_len
|
||||
&& check_typval_arg_type(ufunc->uf_arg_types[idx], &argv[idx],
|
||||
idx + 1) == FAIL)
|
||||
goto failed_early;
|
||||
copy_tv(&argv[idx], STACK_TV_BOT(0));
|
||||
if (idx >= ufunc->uf_args.ga_len - ufunc->uf_def_args.ga_len
|
||||
&& argv[idx].v_type == VAR_SPECIAL
|
||||
&& argv[idx].vval.v_number == VVAL_NONE)
|
||||
{
|
||||
// Use the default value.
|
||||
STACK_TV_BOT(0)->v_type = VAR_UNKNOWN;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ufunc->uf_arg_types != NULL && idx < ufunc->uf_args.ga_len
|
||||
&& check_typval_arg_type(
|
||||
ufunc->uf_arg_types[idx], &argv[idx], idx + 1) == FAIL)
|
||||
goto failed_early;
|
||||
copy_tv(&argv[idx], STACK_TV_BOT(0));
|
||||
}
|
||||
++ectx.ec_stack.ga_len;
|
||||
}
|
||||
|
||||
@ -1505,8 +1486,8 @@ call_def_function(
|
||||
where.wt_index = 0;
|
||||
where.wt_variable = FALSE;
|
||||
|
||||
// Decide where to start execution, handles optional arguments.
|
||||
init_instr_idx(ufunc, argc, &ectx);
|
||||
// Start execution at the first instruction.
|
||||
ectx.ec_iidx = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
@ -2738,6 +2719,16 @@ call_def_function(
|
||||
}
|
||||
break;
|
||||
|
||||
// Jump if an argument with a default value was already set and not
|
||||
// v:none.
|
||||
case ISN_JUMP_IF_ARG_SET:
|
||||
tv = STACK_TV_VAR(iptr->isn_arg.jumparg.jump_arg_off);
|
||||
if (tv->v_type != VAR_UNKNOWN
|
||||
&& !(tv->v_type == VAR_SPECIAL
|
||||
&& tv->vval.v_number == VVAL_NONE))
|
||||
ectx.ec_iidx = iptr->isn_arg.jumparg.jump_where;
|
||||
break;
|
||||
|
||||
// top of a for loop
|
||||
case ISN_FOR:
|
||||
{
|
||||
@ -4517,6 +4508,12 @@ ex_disassemble(exarg_T *eap)
|
||||
}
|
||||
break;
|
||||
|
||||
case ISN_JUMP_IF_ARG_SET:
|
||||
smsg("%4d JUMP_IF_ARG_SET arg[%d] -> %d", current,
|
||||
iptr->isn_arg.jumparg.jump_arg_off + STACK_FRAME_SIZE,
|
||||
iptr->isn_arg.jump.jump_where);
|
||||
break;
|
||||
|
||||
case ISN_FOR:
|
||||
{
|
||||
forloop_T *forloop = &iptr->isn_arg.forloop;
|
||||
|
Loading…
x
Reference in New Issue
Block a user