# HG changeset patch # User Bram Moolenaar # Date 1617048903 -7200 # Node ID cabed216cc2fb2db4483e8db03d94ec3de9d3bc6 # Parent b10dcae9e9a13ef9eb35c3b93d8e1c3d8b6303ae patch 8.2.2677: Vim9: cannot use only some of the default arguments Commit: https://github.com/vim/vim/commit/38a3bfa9a2931729a5e0c28dc087f745b68988ef Author: Bram Moolenaar Date: Mon Mar 29 22:14:55 2021 +0200 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 --- a/runtime/doc/vim9.txt +++ b/runtime/doc/vim9.txt @@ -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 e 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 globa 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 state 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 cha 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 a :var mine: MyInterface {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 + 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 + 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 --- a/src/structs.h +++ b/src/structs.h @@ -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 diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim --- a/src/testdir/test_vim9_disassemble.vim +++ b/src/testdir/test_vim9_disassemble.vim @@ -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 diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -308,21 +308,38 @@ def MyDefaultSecond(name: string, second 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 --- a/src/userfunc.c +++ b/src/userfunc.c @@ -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 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 --- a/src/version.c +++ b/src/version.c @@ -751,6 +751,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 2677, +/**/ 2676, /**/ 2675, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -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; diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -1629,6 +1629,22 @@ generate_JUMP(cctx_T *cctx, jumpwhen_T w 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 *ufu 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 *ufu 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; - } - ufunc->uf_def_arg_idx[count] = instr->ga_len; + + // 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,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: diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -97,35 +97,6 @@ ufunc_argcount(ufunc_T *ufunc) } /* - * 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;