Patch 8.2.2677
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)
Files: runtime/doc/vim9.txt, src/vim9compile.c, src/vim9execute.c,
src/userfunc.c, src/structs.h, src/vim9.h,
src/testdir/test_vim9_disassemble.vim,
src/testdir/test_vim9_func.vim
*** ../vim-8.2.2676/runtime/doc/vim9.txt 2021-03-17 17:45:55.349935903
+0100
--- runtime/doc/vim9.txt 2021-03-29 22:11:41.209266675 +0200
***************
*** 125,130 ****
--- 125,134 ----
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,169 ****
--- 168,182 ----
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,195 ****
--- 203,214 ----
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,284 ****
--- 298,306 ----
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,415 ****
g:was_called = 'yes'
return expression
}
! NOT IMPLEMENTED YET
*vim9-curly*
To avoid the "{" of a dictionary literal to be recognized as a statement block
--- 431,448 ----
g:was_called = 'yes'
return expression
}
!
! 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,710 ****
--- 738,744 ----
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
***************
*** 847,852 ****
--- 881,888 ----
:enddef End of a function defined with `:def`. It
should be on
a line by its own.
+ 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
If the script the function is defined in is Vim9 script, then script-local
variables can be accessed without the "s:" prefix. They must be defined
***************
*** 890,895 ****
--- 926,952 ----
g/pattern/s/^/`=newText`/
enddef
+ 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
+
==============================================================================
4. Types *vim9-types*
*** ../vim-8.2.2676/src/vim9compile.c 2021-03-28 20:38:30.540591499 +0200
--- src/vim9compile.c 2021-03-29 22:06:12.234122708 +0200
***************
*** 1629,1634 ****
--- 1629,1650 ----
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,1839 ****
--- 1850,1862 ----
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,1851 ****
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)
{
--- 1868,1873 ----
***************
*** 1961,1966 ****
--- 1983,1991 ----
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,8374 ****
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)
{
--- 8388,8393 ----
***************
*** 8377,8387 ****
int arg_idx = first_def_arg + i;
where_T where;
int r;
// 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);
--- 8396,8411 ----
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;
arg = ((char_u **)(ufunc->uf_def_args.ga_data))[i];
r = compile_expr0(&arg, &cctx);
***************
*** 8406,8413 ****
if (generate_STORE(&cctx, ISN_STORE, i - count - off, NULL) == FAIL)
goto erret;
}
- ufunc->uf_def_arg_idx[count] = instr->ga_len;
if (did_set_arg_type)
set_function_type(ufunc);
--- 8430,8440 ----
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;
}
if (did_set_arg_type)
set_function_type(ufunc);
***************
*** 9114,9119 ****
--- 9141,9147 ----
case ISN_FOR:
case ISN_GETITEM:
case ISN_JUMP:
+ case ISN_JUMP_IF_ARG_SET:
case ISN_LISTAPPEND:
case ISN_LISTINDEX:
case ISN_LISTSLICE:
*** ../vim-8.2.2676/src/vim9execute.c 2021-03-26 20:41:24.773620612 +0100
--- src/vim9execute.c 2021-03-29 21:52:38.004281729 +0200
***************
*** 97,131 ****
}
/*
- * 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.
*/
--- 97,102 ----
***************
*** 363,370 ****
current_sctx.sc_sid = ufunc->uf_script_ctx.sc_sid;
}
! // Decide where to start execution, handles optional arguments.
! init_instr_idx(ufunc, argcount, ectx);
return OK;
}
--- 334,341 ----
current_sctx.sc_sid = ufunc->uf_script_ctx.sc_sid;
}
! // Start execution at the first instruction.
! ectx->ec_iidx = 0;
return OK;
}
***************
*** 1367,1377 ****
&& (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));
++ectx.ec_stack.ga_len;
}
--- 1338,1358 ----
&& (ufunc->uf_va_name != NULL || idx < ufunc->uf_args.ga_len);
++idx)
{
! 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,1512 ****
where.wt_index = 0;
where.wt_variable = FALSE;
! // Decide where to start execution, handles optional arguments.
! init_instr_idx(ufunc, argc, &ectx);
for (;;)
{
--- 1486,1493 ----
where.wt_index = 0;
where.wt_variable = FALSE;
! // Start execution at the first instruction.
! ectx.ec_iidx = 0;
for (;;)
{
***************
*** 2738,2743 ****
--- 2719,2734 ----
}
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,4522 ****
--- 4508,4519 ----
}
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;
*** ../vim-8.2.2676/src/userfunc.c 2021-03-27 15:40:07.979976135 +0100
--- src/userfunc.c 2021-03-28 21:56:22.752634642 +0200
***************
*** 1914,1920 ****
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);
--- 1914,1919 ----
***************
*** 2049,2062 ****
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);
--- 2048,2053 ----
*** ../vim-8.2.2676/src/structs.h 2021-03-21 22:12:31.448826619 +0100
--- src/structs.h 2021-03-28 22:01:11.331474367 +0200
***************
*** 1607,1614 ****
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
--- 1607,1612 ----
*** ../vim-8.2.2676/src/vim9.h 2021-03-24 22:00:52.042056113 +0100
--- src/vim9.h 2021-03-28 22:03:55.790858534 +0200
***************
*** 92,97 ****
--- 92,98 ----
// 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,208 ****
--- 204,215 ----
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,351 ****
--- 353,359 ----
job_T *job;
partial_T *partial;
jump_T jump;
+ jumparg_T jumparg;
forloop_T forloop;
try_T try;
trycont_T trycont;
*** ../vim-8.2.2676/src/testdir/test_vim9_disassemble.vim 2021-03-26
20:41:24.773620612 +0100
--- src/testdir/test_vim9_disassemble.vim 2021-03-28 22:37:46.539166248
+0200
***************
*** 641,658 ****
enddef
! def FuncWithDefault(arg: string = 'default'): string
! return arg
enddef
def Test_disassemble_call_default()
var res = execute('disass FuncWithDefault')
assert_match('FuncWithDefault\_s*' ..
'\d PUSHS "default"\_s*' ..
'\d STORE arg\[-1]\_s*' ..
! 'return arg\_s*' ..
'\d LOAD arg\[-1]\_s*' ..
! '\d RETURN',
res)
enddef
--- 641,665 ----
enddef
! 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 .. nr\_s*' ..
! '6 LOAD arg\[-2]\_s*' ..
'\d LOAD arg\[-1]\_s*' ..
! '\d 2STRING stack\[-1]\_s*' ..
! '\d\+ CONCAT\_s*' ..
! '\d\+ RETURN',
res)
enddef
*** ../vim-8.2.2676/src/testdir/test_vim9_func.vim 2021-03-24
22:00:52.042056113 +0100
--- src/testdir/test_vim9_func.vim 2021-03-29 21:58:20.319362951 +0200
***************
*** 308,328 ****
return second ? name : 'none'
enddef
def Test_call_default_args()
MyDefaultArgs()->assert_equal('string')
MyDefaultArgs('one')->assert_equal('one')
! assert_fails('MyDefaultArgs("one", "two")', 'E118:', '', 3,
'Test_call_default_args')
MyDefaultSecond('test')->assert_equal('test')
MyDefaultSecond('test', true)->assert_equal('test')
MyDefaultSecond('test', false)->assert_equal('none')
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
vim9script
def Func(a = b == 0 ? 1 : 2, b = 0)
enddef
--- 308,345 ----
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:', '', 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
! lines =<< trim END
vim9script
def Func(a = b == 0 ? 1 : 2, b = 0)
enddef
*** ../vim-8.2.2676/src/version.c 2021-03-29 21:06:01.171475448 +0200
--- src/version.c 2021-03-29 22:07:08.389976243 +0200
***************
*** 752,753 ****
--- 752,755 ----
{ /* Add new patch number below this line */
+ /**/
+ 2677,
/**/
--
hundred-and-one symptoms of being an internet addict:
36. You miss more than five meals a week downloading the latest games from
Apogee.
/// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net \\\
/// \\\
\\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
\\\ help me help AIDS victims -- http://ICCF-Holland.org ///
--
--
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php
---
You received this message because you are subscribed to the Google Groups
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/vim_dev/202103292015.12TKFMGf1241005%40masaka.moolenaar.net.