1
0
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:
Bram Moolenaar 2021-03-29 22:14:55 +02:00
parent 9ea7e55ab9
commit 38a3bfa9a2
9 changed files with 170 additions and 65 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -750,6 +750,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
2677,
/**/
2676,
/**/

View File

@ -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;

View File

@ -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:

View File

@ -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;