changeset 24272:cabed216cc2f v8.2.2677

patch 8.2.2677: Vim9: cannot use only some of the default arguments Commit: https://github.com/vim/vim/commit/38a3bfa9a2931729a5e0c28dc087f745b68988ef Author: Bram Moolenaar <Bram@vim.org> 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)
author Bram Moolenaar <Bram@vim.org>
date Mon, 29 Mar 2021 22:15:03 +0200
parents b10dcae9e9a1
children 477b0a892a85
files runtime/doc/vim9.txt src/structs.h src/testdir/test_vim9_disassemble.vim src/testdir/test_vim9_func.vim src/userfunc.c src/version.c src/vim9.h src/vim9compile.c src/vim9execute.c
diffstat 9 files changed, 171 insertions(+), 66 deletions(-) [+]
line wrap: on
line diff
--- 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<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*
@@ -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<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
--- 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
 
--- 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
 
--- 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
--- 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);
--- 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,
--- 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;
--- 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:
--- 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;