]> granicus.if.org Git - vim/commitdiff
patch 8.2.0222: Vim9: optional function arguments don't work yet v8.2.0222
authorBram Moolenaar <Bram@vim.org>
Thu, 6 Feb 2020 16:51:35 +0000 (17:51 +0100)
committerBram Moolenaar <Bram@vim.org>
Thu, 6 Feb 2020 16:51:35 +0000 (17:51 +0100)
Problem:    Vim9: optional function arguments don't work yet.
Solution:   Implement optional function arguments.

src/structs.h
src/testdir/test_vim9_script.vim
src/userfunc.c
src/version.c
src/vim9compile.c
src/vim9execute.c

index bb3e72f6354e83af449b62fb89447aa49d1c69b7..7aff8af201fea33db789a06cfab6ab3486a8a1c1 100644 (file)
@@ -1515,6 +1515,8 @@ 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
     char_u     *uf_va_name;    // name from "...name" or NULL
     type_T     *uf_va_type;    // type from "...name: type" or NULL
 
index 10bbf9168b0c543d9f4aefd3cea1d374f41cd8c1..d7b3b4c9950f4478ed28ee6c0d46b94c924c9547 100644 (file)
@@ -139,34 +139,48 @@ def Test_call_varargs()
   assert_equal('one,two,three', MyVarargs('one', 'two', 'three'))
 enddef
 
-"def Test_call_func_defined_later()
-"  call assert_equal('one', DefineLater('one'))
-"  call assert_fails('call NotDefined("one")', 'E99:')
-"enddef
-
-func DefineLater(arg)
-  return a:arg
-endfunc
-
 def MyDefaultArgs(name = 'string'): string
   return name
 enddef
 
+def Test_call_default_args()
+  assert_equal('string', MyDefaultArgs())
+  assert_equal('one', MyDefaultArgs('one'))
+  assert_fails('call MyDefaultArgs("one", "two")', 'E118:')
+enddef
+
 func Test_call_default_args_from_func()
-  " TODO: implement using default value for optional argument
-  "call assert_equal('string', MyDefaultArgs())
-  call assert_fails('call MyDefaultArgs()', 'optional arguments not implemented yet')
+  call assert_equal('string', MyDefaultArgs())
   call assert_equal('one', MyDefaultArgs('one'))
   call assert_fails('call MyDefaultArgs("one", "two")', 'E118:')
 endfunc
 
-def Test_call_default_args()
-  " TODO: implement using default value for optional argument
-  "assert_equal('string', MyDefaultArgs())
-  assert_equal('one', MyDefaultArgs('one'))
-  assert_fails('call MyDefaultArgs("one", "two")', 'E118:')
+" Default arg and varargs
+def MyDefVarargs(one: string, two = 'foo', ...rest: list<string>): string
+  let res = one .. ',' .. two
+  for s in rest
+    res ..= ',' .. s
+  endfor
+  return res
 enddef
 
+def Test_call_def_varargs()
+  call assert_fails('call MyDefVarargs()', 'E119:')
+  assert_equal('one,foo', MyDefVarargs('one'))
+  assert_equal('one,two', MyDefVarargs('one', 'two'))
+  assert_equal('one,two,three', MyDefVarargs('one', 'two', 'three'))
+enddef
+
+
+"def Test_call_func_defined_later()
+"  call assert_equal('one', DefineLater('one'))
+"  call assert_fails('call NotDefined("one")', 'E99:')
+"enddef
+
+func DefineLater(arg)
+  return a:arg
+endfunc
+
 def Test_return_type_wrong()
   CheckScriptFailure(['def Func(): number', 'return "a"', 'enddef'], 'expected number but got string')
   CheckScriptFailure(['def Func(): string', 'return 1', 'enddef'], 'expected string but got number')
index ef1d42d12b14ad08a25ea878177f7512e79dd242..3c2673dde6d5140364294f0d45d933cda0f5acdb 100644 (file)
@@ -200,6 +200,7 @@ get_function_args(
            {
                typval_T        rettv;
 
+               // find the end of the expression (doesn't evaluate it)
                any_default = TRUE;
                p = skipwhite(p) + 1;
                p = skipwhite(p);
index f302040e8669a645545e7943497e0ee286128e64..09f9e9c3c695cc3a5ae3550904c296c3042de71e 100644 (file)
@@ -742,6 +742,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    222,
 /**/
     221,
 /**/
index 12400a35d54102092fa42c73d966d7c97ad0d032..a362fe8b489262fac679a49389f08d25178ddabc 100644 (file)
@@ -956,11 +956,12 @@ generate_BCALL(cctx_T *cctx, int func_idx, int argcount)
  * Return FAIL if the number of arguments is wrong.
  */
     static int
-generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int argcount)
+generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount)
 {
     isn_T      *isn;
     garray_T   *stack = &cctx->ctx_type_stack;
     int                regular_args = ufunc->uf_args.ga_len;
+    int                argcount = pushed_argcount;
 
     if (argcount > regular_args && !has_varargs(ufunc))
     {
@@ -978,9 +979,13 @@ generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int argcount)
     {
        int count = argcount - regular_args;
 
-       // TODO: add default values for optional arguments?
-       generate_NEWLIST(cctx, count < 0 ? 0 : count);
-       argcount = regular_args + 1;
+       // If count is negative an empty list will be added after evaluating
+       // default values for missing optional arguments.
+       if (count >= 0)
+       {
+           generate_NEWLIST(cctx, count);
+           argcount = regular_args + 1;
+       }
     }
 
     if ((isn = generate_instr(cctx,
@@ -4600,6 +4605,44 @@ compile_def_function(ufunc_T *ufunc, int set_return_type)
     // Most modern script version.
     current_sctx.sc_version = SCRIPT_VERSION_VIM9;
 
+    if (ufunc->uf_def_args.ga_len > 0)
+    {
+       int     count = ufunc->uf_def_args.ga_len;
+       int     i;
+       char_u  *arg;
+       int     off = STACK_FRAME_SIZE + (ufunc->uf_va_name != NULL ? 1 : 0);
+
+       // 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;
+       for (i = 0; i < count; ++i)
+       {
+           ufunc->uf_def_arg_idx[i] = instr->ga_len;
+           arg = ((char_u **)(ufunc->uf_def_args.ga_data))[i];
+           if (compile_expr1(&arg, &cctx) == FAIL
+                   || generate_STORE(&cctx, ISN_STORE,
+                                               i - count - off, NULL) == FAIL)
+               goto erret;
+       }
+
+       // If a varargs is following, push an empty list.
+       if (ufunc->uf_va_name != NULL)
+       {
+           if (generate_NEWLIST(&cctx, 0) == FAIL
+                   || generate_STORE(&cctx, ISN_STORE, -off, NULL) == FAIL)
+               goto erret;
+       }
+
+       ufunc->uf_def_arg_idx[count] = instr->ga_len;
+    }
+
+    /*
+     * Loop over all the lines of the function and generate instructions.
+     */
     for (;;)
     {
        if (line != NULL && *line == '|')
index 093fbad8e3a7da90c56ee6e744b3be0603c25a81..d40320122eabad56fca834eed31de7e731994b8b 100644 (file)
@@ -70,7 +70,7 @@ typedef struct {
 #define STACK_TV_BOT(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_stack.ga_len + idx)
 
 /*
- * Return the number of arguments, including any vararg.
+ * Return the number of arguments, including optional arguments and any vararg.
  */
     static int
 ufunc_argcount(ufunc_T *ufunc)
@@ -78,6 +78,35 @@ 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];
+    }
+}
+
 /*
  * Call compiled function "cdf_idx" from compiled code.
  *
@@ -107,23 +136,15 @@ call_dfunc(int cdf_idx, int argcount, ectx_T *ectx)
     if (ga_grow(&ectx->ec_stack, optcount + 3 + dfunc->df_varcount) == FAIL)
        return FAIL;
 
-// TODO: Put omitted argument default values on the stack.
-    if (optcount > 0)
-    {
-       emsg("optional arguments not implemented yet");
-       return FAIL;
-    }
     if (optcount < 0)
     {
        emsg("argument count wrong?");
        return FAIL;
     }
-//    for (idx = argcount - dfunc->df_minarg;
-//                                  idx < dfunc->df_maxarg; ++idx)
-//    {
-//     copy_tv(&dfunc->df_defarg[idx], STACK_TV_BOT(0));
-//     ++ectx->ec_stack.ga_len;
-//    }
+
+    // Reserve space for omitted optional arguments, filled in soon.
+    // Also any empty varargs argument.
+    ectx->ec_stack.ga_len += optcount;
 
     // Store current execution state in stack frame for ISN_RETURN.
     // TODO: If the actual number of arguments doesn't match what the called
@@ -142,7 +163,9 @@ call_dfunc(int cdf_idx, int argcount, ectx_T *ectx)
     ectx->ec_dfunc_idx = cdf_idx;
     ectx->ec_instr = dfunc->df_instr;
     estack_push_ufunc(ETYPE_UFUNC, dfunc->df_ufunc, 1);
-    ectx->ec_iidx = 0;
+
+    // Decide where to start execution, handles optional arguments.
+    init_instr_idx(ufunc, argcount, ectx);
 
     return OK;
 }
@@ -156,9 +179,9 @@ call_dfunc(int cdf_idx, int argcount, ectx_T *ectx)
     static void
 func_return(ectx_T *ectx)
 {
-    int                ret_idx = ectx->ec_stack.ga_len - 1;
     int                idx;
     dfunc_T    *dfunc;
+    int                top;
 
     // execution context goes one level up
     estack_pop();
@@ -166,17 +189,27 @@ func_return(ectx_T *ectx)
     // Clear the local variables and temporary values, but not
     // the return value.
     for (idx = ectx->ec_frame + STACK_FRAME_SIZE;
-                                        idx < ectx->ec_stack.ga_len - 1; ++idx)
+                                       idx < ectx->ec_stack.ga_len - 1; ++idx)
        clear_tv(STACK_TV(idx));
+
+    // Clear the arguments.
     dfunc = ((dfunc_T *)def_functions.ga_data) + ectx->ec_dfunc_idx;
-    ectx->ec_stack.ga_len = ectx->ec_frame
-                                        - ufunc_argcount(dfunc->df_ufunc) + 1;
+    top = ectx->ec_frame - ufunc_argcount(dfunc->df_ufunc);
+    for (idx = top; idx < ectx->ec_frame; ++idx)
+       clear_tv(STACK_TV(idx));
+
+    // Restore the previous frame.
     ectx->ec_dfunc_idx = STACK_TV(ectx->ec_frame)->vval.v_number;
     ectx->ec_iidx = STACK_TV(ectx->ec_frame + 1)->vval.v_number;
     ectx->ec_frame = STACK_TV(ectx->ec_frame + 2)->vval.v_number;
-    *STACK_TV_BOT(-1) = *STACK_TV(ret_idx);
     dfunc = ((dfunc_T *)def_functions.ga_data) + ectx->ec_dfunc_idx;
     ectx->ec_instr = dfunc->df_instr;
+
+    // Reset the stack to the position before the call, move the return value
+    // to the top of the stack.
+    idx = ectx->ec_stack.ga_len - 1;
+    ectx->ec_stack.ga_len = top + 1;
+    *STACK_TV_BOT(-1) = *STACK_TV(idx);
 }
 
 #undef STACK_TV
@@ -362,7 +395,7 @@ call_def_function(
     int                idx;
     int                ret = FAIL;
     dfunc_T    *dfunc;
-    int                optcount = ufunc_argcount(ufunc) - argc;
+    int                defcount = ufunc->uf_args.ga_len - argc;
 
 // Get pointer to item in the stack.
 #define STACK_TV(idx) (((typval_T *)ectx.ec_stack.ga_data) + idx)
@@ -388,17 +421,18 @@ call_def_function(
        copy_tv(&argv[idx], STACK_TV_BOT(0));
        ++ectx.ec_stack.ga_len;
     }
+    // Make space for omitted arguments, will store default value below.
+    if (defcount > 0)
+       for (idx = 0; idx < defcount; ++idx)
+       {
+           STACK_TV_BOT(0)->v_type = VAR_UNKNOWN;
+           ++ectx.ec_stack.ga_len;
+       }
 
     // Frame pointer points to just after arguments.
     ectx.ec_frame = ectx.ec_stack.ga_len;
     initial_frame_ptr = ectx.ec_frame;
 
-// TODO: Put omitted argument default values on the stack.
-    if (optcount > 0)
-    {
-       emsg("optional arguments not implemented yet");
-       return FAIL;
-    }
     // dummy frame entries
     for (idx = 0; idx < STACK_FRAME_SIZE; ++idx)
     {
@@ -413,7 +447,10 @@ call_def_function(
     ectx.ec_stack.ga_len += dfunc->df_varcount;
 
     ectx.ec_instr = dfunc->df_instr;
-    ectx.ec_iidx = 0;
+
+    // Decide where to start execution, handles optional arguments.
+    init_instr_idx(ufunc, argc, &ectx);
+
     for (;;)
     {
        isn_T       *iptr;
@@ -1657,7 +1694,11 @@ ex_disassemble(exarg_T *eap)
                break;
 
            case ISN_STORE:
-               smsg("%4d STORE $%lld", current, iptr->isn_arg.number);
+               if (iptr->isn_arg.number < 0)
+                   smsg("%4d STORE arg[%lld]", current,
+                                     iptr->isn_arg.number + STACK_FRAME_SIZE);
+               else
+                   smsg("%4d STORE $%lld", current, iptr->isn_arg.number);
                break;
            case ISN_STOREV:
                smsg("%4d STOREV v:%s", current,