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)
diff --git a/runtime/doc/vim9.txt b/runtime/doc/vim9.txt
index 4e8016c..2425804 100644
--- a/runtime/doc/vim9.txt
+++ b/runtime/doc/vim9.txt
@@ -125,6 +125,10 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
: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 @@
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
diff --git a/src/structs.h b/src/structs.h
index 75ad127..f0296c1 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -1607,8 +1607,6 @@
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
diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim
index 407e261..e24e72f 100644
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -641,18 +641,25 @@
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
diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim
index 5faae82..957b632 100644
--- a/src/testdir/test_vim9_func.vim
+++ b/src/testdir/test_vim9_func.vim
@@ -308,21 +308,38 @@
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
diff --git a/src/userfunc.c b/src/userfunc.c
index ba7e0c3..1139573 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -1914,7 +1914,6 @@
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 @@
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);
diff --git a/src/version.c b/src/version.c
index 8053d92..012530f 100644
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 2677,
+/**/
2676,
/**/
2675,
diff --git a/src/vim9.h b/src/vim9.h
index a0757fd..0c8a949 100644
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -92,6 +92,7 @@
// 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 @@
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 @@
job_T *job;
partial_T *partial;
jump_T jump;
+ jumparg_T jumparg;
forloop_T forloop;
try_T try;
trycont_T trycont;
diff --git a/src/vim9compile.c b/src/vim9compile.c
index 26d346f..7238761 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -1629,6 +1629,22 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
case ISN_FOR:
case ISN_GETITEM:
case ISN_JUMP:
+ case ISN_JUMP_IF_ARG_SET:
case ISN_LISTAPPEND:
case ISN_LISTINDEX:
case ISN_LISTSLICE:
diff --git a/src/vim9execute.c b/src/vim9execute.c
index b80d533..f15b93d 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -97,35 +97,6 @@
}
/*
- * 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 @@
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 @@
&& (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 @@
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 @@
}
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 @@
}
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;