To: vim_dev@googlegroups.com Subject: Patch 8.2.2677 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ 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 + 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 + ============================================================================== 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 -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///