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)
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).
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*
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).
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.
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
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
: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*
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
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
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
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
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);
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);
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 2677,
/**/
2676,
/**/
// 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
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
job_T *job;
partial_T *partial;
jump_T jump;
+ jumparg_T jumparg;
forloop_T forloop;
try_T try;
trycont_T trycont;
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)
{
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)
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)
{
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,
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)
{
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);
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);
case ISN_FOR:
case ISN_GETITEM:
case ISN_JUMP:
+ case ISN_JUMP_IF_ARG_SET:
case ISN_LISTAPPEND:
case ISN_LISTINDEX:
case ISN_LISTSLICE:
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.
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;
}
&& (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;
}
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 (;;)
{
}
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:
{
}
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;