]> granicus.if.org Git - vim/commitdiff
patch 8.2.2677: Vim9: cannot use only some of the default arguments v8.2.2677
authorBram Moolenaar <Bram@vim.org>
Mon, 29 Mar 2021 20:14:55 +0000 (22:14 +0200)
committerBram Moolenaar <Bram@vim.org>
Mon, 29 Mar 2021 20:14:55 +0000 (22:14 +0200)
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)

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

index 4e8016c9dba51c85d38cf5a4c03a914468c84e21..2425804053f000f84189f742acf4a4fff880501a 100644 (file)
@@ -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 example, a list of numbers: >
           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 global, window, tab, buffer and Vim
 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 statements in {}: >
                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 character 0xc3 (an illegal byte), in Vim9
 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 as types: >
        :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
index 75ad127810b5c808e3da0068a1fa144f34e7bff3..f0296c1e31588392f7ace1d03d83177d139afbbb 100644 (file)
@@ -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
 
index 407e2611ffe9d186f986c593debbe6d76756086d..e24e72f06b05b272136df9f3fa488b7377631b81 100644 (file)
@@ -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
 
index 5faae827052ba41152bdbed72611ba388a7ef9a5..957b632e1260cbc24c245dfbe537480ac63eb660 100644 (file)
@@ -308,21 +308,38 @@ def MyDefaultSecond(name: string, second: bool  = true): string
   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
index ba7e0c3da2be4ba84533af9f9ab630c5556131ac..1139573c638a549458b8b600c40f9fcd700d8fb7 100644 (file)
@@ -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, ectx_T *ectx)
        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);
index 8053d921968b57db3364c8fba820678dcd49eb1c..012530f43e4cebcbbf2c2750cd9577b19e3e902b 100644 (file)
@@ -750,6 +750,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    2677,
 /**/
     2676,
 /**/
index a0757fd31f5022ac47298683d2736bf4cc6c086c..0c8a949e87500353aa9d531807c5dd031ff7dd68 100644 (file)
@@ -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;
index 26d346f4344323ec854c77ea3d45dbcfd5754314..72387610dfa620cf8cefb85fd40d57cd005a7dca 100644 (file)
@@ -1629,6 +1629,22 @@ generate_JUMP(cctx_T *cctx, jumpwhen_T when, int where)
     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 *ufunc, int pushed_argcount)
            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 *ufunc, int pushed_argcount)
                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;
+
+           // 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);
@@ -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:
index b80d533c910d1f307c1ea41fba3a67e67a44c090..f15b93d867b515a14732d07ee05b9ea3b6b20278 100644 (file)
@@ -96,35 +96,6 @@ ufunc_argcount(ufunc_T *ufunc)
     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.
@@ -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;