Skip to content

Commit 38a3bfa

Browse files
committed
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)
1 parent 9ea7e55 commit 38a3bfa

File tree

9 files changed

+170
-65
lines changed

9 files changed

+170
-65
lines changed

runtime/doc/vim9.txt

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ that starts a comment: >
125125
var name = value # comment
126126
var name = value# error!
127127
128+
Do not start a comment with #{, it looks like the legacy dictionary literal
129+
and produces an error where this might be confusing. #{{ or #{{{ are OK,
130+
these can be used to start a fold.
131+
128132
In legacy Vim script # is also used for the alternate file name. In Vim9
129133
script you need to use %% instead. Instead of ## use %%% (stands for all
130134
arguments).
@@ -164,6 +168,15 @@ list type, similar to TypeScript. For example, a list of numbers: >
164168
for item in itemlist
165169
...
166170
171+
When a function argument is optional (it has a default value) passing `v:none`
172+
as the argument results in using the default value. This is useful when you
173+
want to specify a value for an argument that comes after an argument that
174+
should use its default value. Example: >
175+
def MyFunc(one = 'one', last = 'last)
176+
...
177+
enddef
178+
MyFunc(v:none, 'LAST') # first argument uses default value 'one'
179+
167180
168181
Functions and variables are script-local by default ~
169182
*vim9-scopes*
@@ -190,6 +203,12 @@ search for the function:
190203
However, it is recommended to always use "g:" to refer to a global function
191204
for clarity.
192205

206+
Since a script-local function reference can be used without "s:" the name must
207+
start with an upper case letter even when using the ":s" prefix. In legacy
208+
script "s:funcref" could be used, because it could not be referred to with
209+
"funcref". In Vim9 script it can, therefore "s:Funcref" must be used to avoid
210+
that the name interferes with builtin functions.
211+
193212
In all cases the function must be defined before used. That is when it is
194213
called, when `:defcompile` causes it to be compiled, or when code that calls
195214
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
279298
variables, because they are not really declared. They can also be deleted
280299
with `:unlet`.
281300

301+
`:lockvar` does not work on local variables. Use `:const` and `:final`
302+
instead.
303+
282304
Variables, functions and function arguments cannot shadow previously defined
283305
or imported variables and functions in the same script file.
284306
Variables may shadow Ex commands, rename the variable if needed.
@@ -409,7 +431,18 @@ Additionally, a lambda can contain statements in {}: >
409431
g:was_called = 'yes'
410432
return expression
411433
}
412-
NOT IMPLEMENTED YET
434+
435+
The ending "}" must be at the start of a line. It can be followed by other
436+
characters, e.g.: >
437+
var d = mapnew(dict, (k, v): string => {
438+
return 'value'
439+
})
440+
No command can follow the "{", only a comment can be used there.
441+
442+
Rationale: The "}" cannot be after a command because it would require parsing
443+
the commands to find it. For consistency with that no command can follow the
444+
"{". Unfortunately this means using "() => { command }" does not work, line
445+
breaks are always required.
413446

414447
*vim9-curly*
415448
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
705738
script this results in the string 'á'.
706739
A negative index is counting from the end, "[-1]" is the last character.
707740
To exclude the last character use |slice()|.
741+
To count composing characters separately use |strcharpart()|.
708742
If the index is out of range then an empty string results.
709743

710744
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: >
9721006
:var mine: MyInterface<string>
9731007
{not implemented yet}
9741008

1009+
You may also find this wiki useful. It was written by an early adoptor of
1010+
Vim9 script: https://github.com/lacygoill/wiki/blob/master/vim/vim9.md
9751011

9761012
Variable types and type casting ~
9771013
*variable-types*
@@ -1044,6 +1080,27 @@ to a list of numbers.
10441080
Same for |extend()|, use |extendnew()| instead, and for |flatten()|, use
10451081
|flattennew()| instead.
10461082

1083+
Closures defined in a loop will share the same context. For example: >
1084+
var flist: list<func>
1085+
for i in range(10)
1086+
var inloop = i
1087+
flist[i] = () => inloop
1088+
endfor
1089+
1090+
The "inloop" variable will exist only once, all closures put in the list refer
1091+
to the same instance, which in the end will have the value 9. This is
1092+
efficient. If you do want a separate context for each closure call a function
1093+
to define it: >
1094+
def GetFunc(i: number): func
1095+
var inloop = i
1096+
return () => inloop
1097+
enddef
1098+
1099+
var flist: list<func>
1100+
for i in range(10)
1101+
flist[i] = GetFunc(i)
1102+
endfor
1103+
10471104
==============================================================================
10481105

10491106
5. Namespace, Import and Export

src/structs.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1607,8 +1607,6 @@ typedef struct
16071607
type_T **uf_arg_types; // argument types (count == uf_args.ga_len)
16081608
type_T *uf_ret_type; // return type
16091609
garray_T uf_type_list; // types used in arg and return types
1610-
int *uf_def_arg_idx; // instruction indexes for evaluating
1611-
// uf_def_args; length: uf_def_args.ga_len + 1
16121610
partial_T *uf_partial; // for closure created inside :def function:
16131611
// information about the context
16141612

src/testdir/test_vim9_disassemble.vim

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -641,18 +641,25 @@ def Test_disassemble_update_instr()
641641
enddef
642642

643643

644-
def FuncWithDefault(arg: string = 'default'): string
645-
return arg
644+
def FuncWithDefault(arg: string = 'default', nr = 77): string
645+
return arg .. nr
646646
enddef
647647

648648
def Test_disassemble_call_default()
649649
var res = execute('disass FuncWithDefault')
650650
assert_match('FuncWithDefault\_s*' ..
651+
'\d JUMP_IF_ARG_SET arg\[-2\] -> 3\_s*' ..
651652
'\d PUSHS "default"\_s*' ..
653+
'\d STORE arg\[-2]\_s*' ..
654+
'3 JUMP_IF_ARG_SET arg\[-1\] -> 6\_s*' ..
655+
'\d PUSHNR 77\_s*' ..
652656
'\d STORE arg\[-1]\_s*' ..
653-
'return arg\_s*' ..
657+
'return arg .. nr\_s*' ..
658+
'6 LOAD arg\[-2]\_s*' ..
654659
'\d LOAD arg\[-1]\_s*' ..
655-
'\d RETURN',
660+
'\d 2STRING stack\[-1]\_s*' ..
661+
'\d\+ CONCAT\_s*' ..
662+
'\d\+ RETURN',
656663
res)
657664
enddef
658665

src/testdir/test_vim9_func.vim

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,21 +308,38 @@ def MyDefaultSecond(name: string, second: bool = true): string
308308
return second ? name : 'none'
309309
enddef
310310

311+
311312
def Test_call_default_args()
312313
MyDefaultArgs()->assert_equal('string')
314+
MyDefaultArgs(v:none)->assert_equal('string')
313315
MyDefaultArgs('one')->assert_equal('one')
314-
assert_fails('MyDefaultArgs("one", "two")', 'E118:', '', 3, 'Test_call_default_args')
316+
assert_fails('MyDefaultArgs("one", "two")', 'E118:', '', 4, 'Test_call_default_args')
315317

316318
MyDefaultSecond('test')->assert_equal('test')
317319
MyDefaultSecond('test', true)->assert_equal('test')
318320
MyDefaultSecond('test', false)->assert_equal('none')
319321

322+
var lines =<< trim END
323+
def MyDefaultThird(name: string, aa = 'aa', bb = 'bb'): string
324+
return name .. aa .. bb
325+
enddef
326+
327+
MyDefaultThird('->')->assert_equal('->aabb')
328+
MyDefaultThird('->', v:none)->assert_equal('->aabb')
329+
MyDefaultThird('->', 'xx')->assert_equal('->xxbb')
330+
MyDefaultThird('->', v:none, v:none)->assert_equal('->aabb')
331+
MyDefaultThird('->', 'xx', v:none)->assert_equal('->xxbb')
332+
MyDefaultThird('->', v:none, 'yy')->assert_equal('->aayy')
333+
MyDefaultThird('->', 'xx', 'yy')->assert_equal('->xxyy')
334+
END
335+
CheckDefAndScriptSuccess(lines)
336+
320337
CheckScriptFailure(['def Func(arg: number = asdf)', 'enddef', 'defcompile'], 'E1001:')
321338
delfunc g:Func
322339
CheckScriptFailure(['def Func(arg: number = "text")', 'enddef', 'defcompile'], 'E1013: Argument 1: type mismatch, expected number but got string')
323340
delfunc g:Func
324341

325-
var lines =<< trim END
342+
lines =<< trim END
326343
vim9script
327344
def Func(a = b == 0 ? 1 : 2, b = 0)
328345
enddef

src/userfunc.c

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1914,7 +1914,6 @@ func_clear_items(ufunc_T *fp)
19141914
ga_clear_strings(&(fp->uf_def_args));
19151915
ga_clear_strings(&(fp->uf_lines));
19161916
VIM_CLEAR(fp->uf_arg_types);
1917-
VIM_CLEAR(fp->uf_def_arg_idx);
19181917
VIM_CLEAR(fp->uf_block_ids);
19191918
VIM_CLEAR(fp->uf_va_name);
19201919
clear_type_list(&fp->uf_type_list);
@@ -2049,14 +2048,6 @@ copy_func(char_u *lambda, char_u *global, ectx_T *ectx)
20492048
mch_memmove(fp->uf_arg_types, ufunc->uf_arg_types,
20502049
sizeof(type_T *) * fp->uf_args.ga_len);
20512050
}
2052-
if (ufunc->uf_def_arg_idx != NULL)
2053-
{
2054-
fp->uf_def_arg_idx = ALLOC_MULT(int, fp->uf_def_args.ga_len + 1);
2055-
if (fp->uf_def_arg_idx == NULL)
2056-
goto failed;
2057-
mch_memmove(fp->uf_def_arg_idx, ufunc->uf_def_arg_idx,
2058-
sizeof(int) * fp->uf_def_args.ga_len + 1);
2059-
}
20602051
if (ufunc->uf_va_name != NULL)
20612052
{
20622053
fp->uf_va_name = vim_strsave(ufunc->uf_va_name);

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,8 @@ static char *(features[]) =
750750

751751
static int included_patches[] =
752752
{ /* Add new patch number below this line */
753+
/**/
754+
2677,
753755
/**/
754756
2676,
755757
/**/

src/vim9.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ typedef enum {
9292

9393
// expression operations
9494
ISN_JUMP, // jump if condition is matched isn_arg.jump
95+
ISN_JUMP_IF_ARG_SET, // jump if argument is already set, uses isn_arg.jumparg
9596

9697
// loop
9798
ISN_FOR, // get next item from a list, uses isn_arg.forloop
@@ -203,6 +204,12 @@ typedef struct {
203204
int jump_where; // position to jump to
204205
} jump_T;
205206

207+
// arguments to ISN_JUMP_IF_ARG_SET
208+
typedef struct {
209+
int jump_arg_off; // argument index, negative
210+
int jump_where; // position to jump to
211+
} jumparg_T;
212+
206213
// arguments to ISN_FOR
207214
typedef struct {
208215
int for_idx; // loop variable index
@@ -346,6 +353,7 @@ struct isn_S {
346353
job_T *job;
347354
partial_T *partial;
348355
jump_T jump;
356+
jumparg_T jumparg;
349357
forloop_T forloop;
350358
try_T try;
351359
trycont_T trycont;

src/vim9compile.c

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1629,6 +1629,22 @@ generate_JUMP(cctx_T *cctx, jumpwhen_T when, int where)
16291629
return OK;
16301630
}
16311631

1632+
/*
1633+
* Generate an ISN_JUMP_IF_ARG_SET instruction.
1634+
*/
1635+
static int
1636+
generate_JUMP_IF_ARG_SET(cctx_T *cctx, int arg_off)
1637+
{
1638+
isn_T *isn;
1639+
1640+
RETURN_OK_IF_SKIP(cctx);
1641+
if ((isn = generate_instr(cctx, ISN_JUMP_IF_ARG_SET)) == NULL)
1642+
return FAIL;
1643+
isn->isn_arg.jumparg.jump_arg_off = arg_off;
1644+
// jump_where is set later
1645+
return OK;
1646+
}
1647+
16321648
static int
16331649
generate_FOR(cctx_T *cctx, int loop_idx)
16341650
{
@@ -1834,6 +1850,13 @@ generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount)
18341850
type_T *expected;
18351851
type_T *actual;
18361852

1853+
actual = ((type_T **)stack->ga_data)[stack->ga_len - argcount + i];
1854+
if (actual == &t_special
1855+
&& i >= regular_args - ufunc->uf_def_args.ga_len)
1856+
{
1857+
// assume v:none used for default argument value
1858+
continue;
1859+
}
18371860
if (i < regular_args)
18381861
{
18391862
if (ufunc->uf_arg_types == NULL)
@@ -1845,7 +1868,6 @@ generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount)
18451868
expected = &t_any;
18461869
else
18471870
expected = ufunc->uf_va_type->tt_member;
1848-
actual = ((type_T **)stack->ga_data)[stack->ga_len - argcount + i];
18491871
if (need_type(actual, expected, -argcount + i, i + 1, cctx,
18501872
TRUE, FALSE) == FAIL)
18511873
{
@@ -1961,6 +1983,9 @@ generate_PCALL(
19611983
if (varargs && i >= type->tt_argcount - 1)
19621984
expected = type->tt_args[
19631985
type->tt_argcount - 1]->tt_member;
1986+
else if (i >= type->tt_min_argcount
1987+
&& actual == &t_special)
1988+
expected = &t_any;
19641989
else
19651990
expected = type->tt_args[i];
19661991
if (need_type(actual, expected, offset, i + 1,
@@ -8363,12 +8388,6 @@ compile_def_function(
83638388
int did_set_arg_type = FALSE;
83648389

83658390
// Produce instructions for the default values of optional arguments.
8366-
// Store the instruction index in uf_def_arg_idx[] so that we know
8367-
// where to start when the function is called, depending on the number
8368-
// of arguments.
8369-
ufunc->uf_def_arg_idx = ALLOC_CLEAR_MULT(int, count + 1);
8370-
if (ufunc->uf_def_arg_idx == NULL)
8371-
goto erret;
83728391
SOURCING_LNUM = 0; // line number unknown
83738392
for (i = 0; i < count; ++i)
83748393
{
@@ -8377,11 +8396,16 @@ compile_def_function(
83778396
int arg_idx = first_def_arg + i;
83788397
where_T where;
83798398
int r;
8399+
int jump_instr_idx = instr->ga_len;
8400+
isn_T *isn;
8401+
8402+
// Use a JUMP_IF_ARG_SET instruction to skip if the value was given.
8403+
if (generate_JUMP_IF_ARG_SET(&cctx, i - count - off) == FAIL)
8404+
goto erret;
83808405

83818406
// Make sure later arguments are not found.
83828407
ufunc->uf_args.ga_len = i;
83838408

8384-
ufunc->uf_def_arg_idx[i] = instr->ga_len;
83858409
arg = ((char_u **)(ufunc->uf_def_args.ga_data))[i];
83868410
r = compile_expr0(&arg, &cctx);
83878411

@@ -8406,8 +8430,11 @@ compile_def_function(
84068430

84078431
if (generate_STORE(&cctx, ISN_STORE, i - count - off, NULL) == FAIL)
84088432
goto erret;
8433+
8434+
// set instruction index in JUMP_IF_ARG_SET to here
8435+
isn = ((isn_T *)instr->ga_data) + jump_instr_idx;
8436+
isn->isn_arg.jumparg.jump_where = instr->ga_len;
84098437
}
8410-
ufunc->uf_def_arg_idx[count] = instr->ga_len;
84118438

84128439
if (did_set_arg_type)
84138440
set_function_type(ufunc);
@@ -9114,6 +9141,7 @@ delete_instr(isn_T *isn)
91149141
case ISN_FOR:
91159142
case ISN_GETITEM:
91169143
case ISN_JUMP:
9144+
case ISN_JUMP_IF_ARG_SET:
91179145
case ISN_LISTAPPEND:
91189146
case ISN_LISTINDEX:
91199147
case ISN_LISTSLICE:

0 commit comments

Comments
 (0)