Patch 8.1.1310
Problem:    Named function arguments are never optional.
Solution:   Support optional function arguments with a default value. (Andy
            Massimino, closes #3952)
Files:      runtime/doc/eval.txt, src/structs.h,
            src/testdir/test_user_func.vim, src/userfunc.c



*** ../vim-8.1.1309/runtime/doc/eval.txt        2019-05-09 14:52:22.079358841 
+0200
--- runtime/doc/eval.txt        2019-05-09 21:01:03.326681151 +0200
***************
*** 10920,10934 ****
  function add an item to it.  If you want to make sure the function cannot
  change a |List| or |Dictionary| use |:lockvar|.
  
- When not using "...", the number of arguments in a function call must be equal
- to the number of named arguments.  When using "...", the number of arguments
- may be larger.
- 
  It is also possible to define a function without any arguments.  You must
  still supply the () then.
  
  It is allowed to define another function inside a function body.
  
                                                        *local-variables*
  Inside a function local variables can be used.  These will disappear when the
  function returns.  Global variables need to be accessed with "g:".
--- 10920,10977 ----
  function add an item to it.  If you want to make sure the function cannot
  change a |List| or |Dictionary| use |:lockvar|.
  
  It is also possible to define a function without any arguments.  You must
  still supply the () then.
  
  It is allowed to define another function inside a function body.
  
+                                               *optional-function-argument*
+ You can provide default values for positional named arguments.  This makes
+ them optional for function calls.  When a positional argument is not
+ specified at a call, the default expression is used to initialize it.
+ This only works for functions declared with |function|, not for lambda
+ expressions |expr-lambda|.
+ 
+ Example: >
+   function Something(key, value = 10)
+      echo a:key .. ": " .. value
+   endfunction
+   call Something('empty')     "empty: 10"
+   call Something('key, 20)    "key: 20"
+ 
+ The argument default expressions are evaluated at the time of the function
+ call, not definition.  Thus it is possible to use an expression which is
+ invalid the moment the function is defined.  The expressions are are also only
+ evaluated when arguments are not specified during a call.
+ 
+ You can pass |v:none| to use the default expression.  Note that this means you
+ cannot pass v:none as an ordinary value when an argument has a default
+ expression.
+ 
+ Example: >
+   function Something(a = 10, b = 20, c = 30)
+   endfunction
+   call Something(1, v:none, 3)            " b = 20
+ <
+                                                               *E989*
+ Optional arguments with default expressions must occur after any mandatory
+ arguments.  You can use "..." after all optional named arguments.
+ 
+ It is possible for later argument defaults to refer to prior arguments,
+ but not the other way around.  They must be prefixed with "a:", as with all
+ arguments.
+ 
+ Example that works: >
+   :function Okay(mandatory, optional = a:mandatory)
+   :endfunction
+ Example that does NOT work: >
+   :function NoGood(first = a:second, second = 10)
+   :endfunction
+ <
+ When not using "...", the number of arguments in a function call must be equal
+ to the number of mandatory named arguments.  When using "...", the number of
+ arguments may be larger.
+ 
                                                        *local-variables*
  Inside a function local variables can be used.  These will disappear when the
  function returns.  Global variables need to be accessed with "g:".
*** ../vim-8.1.1309/src/structs.h       2019-05-07 22:06:48.679310672 +0200
--- src/structs.h       2019-05-09 20:38:28.386453855 +0200
***************
*** 1402,1443 ****
   */
  typedef struct
  {
!     int               uf_varargs;     /* variable nr of arguments */
      int               uf_flags;
!     int               uf_calls;       /* nr of active calls */
!     int               uf_cleared;     /* func_clear() was already called */
!     garray_T  uf_args;        /* arguments */
!     garray_T  uf_lines;       /* function lines */
  # ifdef FEAT_PROFILE
!     int               uf_profiling;   /* TRUE when func is being profiled */
      int               uf_prof_initialized;
!     /* profiling the function as a whole */
!     int               uf_tm_count;    /* nr of calls */
!     proftime_T        uf_tm_total;    /* time spent in function + children */
!     proftime_T        uf_tm_self;     /* time spent in function itself */
!     proftime_T        uf_tm_children; /* time spent in children this call */
!     /* profiling the function per line */
!     int               *uf_tml_count;  /* nr of times line was executed */
!     proftime_T        *uf_tml_total;  /* time spent in a line + children */
!     proftime_T        *uf_tml_self;   /* time spent in a line itself */
!     proftime_T        uf_tml_start;   /* start time for current line */
!     proftime_T        uf_tml_children; /* time spent in children for this 
line */
!     proftime_T        uf_tml_wait;    /* start wait time for current line */
!     int               uf_tml_idx;     /* index of line being timed; -1 if 
none */
!     int               uf_tml_execed;  /* line being timed was executed */
  # endif
!     sctx_T    uf_script_ctx;  /* SCTX where function was defined,
!                                  used for s: variables */
!     int               uf_refcount;    /* reference count, see 
func_name_refcount() */
!     funccall_T        *uf_scoped;     /* l: local variables for closure */
!     char_u    uf_name[1];     /* name of function (actually longer); can
!                                  start with <SNR>123_ (<SNR> is K_SPECIAL
!                                  KS_EXTRA KE_SNR) */
  } ufunc_T;
  
! #define MAX_FUNC_ARGS 20      /* maximum number of function arguments */
! #define VAR_SHORT_LEN 20      /* short variable name length */
! #define FIXVAR_CNT    12      /* number of fixed variables */
  
  /* structure to hold info for a function that is currently being executed. */
  struct funccall_S
--- 1402,1444 ----
   */
  typedef struct
  {
!     int               uf_varargs;     // variable nr of arguments
      int               uf_flags;
!     int               uf_calls;       // nr of active calls
!     int               uf_cleared;     // func_clear() was already called
!     garray_T  uf_args;        // arguments
!     garray_T  uf_def_args;    // default argument expressions
!     garray_T  uf_lines;       // function lines
  # ifdef FEAT_PROFILE
!     int               uf_profiling;   // TRUE when func is being profiled
      int               uf_prof_initialized;
!     // profiling the function as a whole
!     int               uf_tm_count;    // nr of calls
!     proftime_T        uf_tm_total;    // time spent in function + children
!     proftime_T        uf_tm_self;     // time spent in function itself
!     proftime_T        uf_tm_children; // time spent in children this call
!     // profiling the function per line
!     int               *uf_tml_count;  // nr of times line was executed
!     proftime_T        *uf_tml_total;  // time spent in a line + children
!     proftime_T        *uf_tml_self;   // time spent in a line itself
!     proftime_T        uf_tml_start;   // start time for current line
!     proftime_T        uf_tml_children; // time spent in children for this line
!     proftime_T        uf_tml_wait;    // start wait time for current line
!     int               uf_tml_idx;     // index of line being timed; -1 if none
!     int               uf_tml_execed;  // line being timed was executed
  # endif
!     sctx_T    uf_script_ctx;  // SCTX where function was defined,
!                               // used for s: variables
!     int               uf_refcount;    // reference count, see 
func_name_refcount()
!     funccall_T        *uf_scoped;     // l: local variables for closure
!     char_u    uf_name[1];     // name of function (actually longer); can
!                               // start with <SNR>123_ (<SNR> is K_SPECIAL
!                               // KS_EXTRA KE_SNR)
  } ufunc_T;
  
! #define MAX_FUNC_ARGS 20      // maximum number of function arguments
! #define VAR_SHORT_LEN 20      // short variable name length
! #define FIXVAR_CNT    12      // number of fixed variables
  
  /* structure to hold info for a function that is currently being executed. */
  struct funccall_S
*** ../vim-8.1.1309/src/testdir/test_user_func.vim      2017-10-22 
14:08:57.000000000 +0200
--- src/testdir/test_user_func.vim      2019-05-09 21:01:29.390540850 +0200
***************
*** 94,96 ****
--- 94,146 ----
    unlet g:retval g:counter
    enew!
  endfunc
+ 
+ func Log(val, base = 10)
+   return log(a:val) / log(a:base)
+ endfunc
+ 
+ func Args(mandatory, optional = v:null, ...)
+   return deepcopy(a:)
+ endfunc
+ 
+ func Args2(a = 1, b = 2, c = 3)
+   return deepcopy(a:)
+ endfunc
+ 
+ func MakeBadFunc()
+   func s:fcn(a, b=1, c)
+   endfunc
+ endfunc
+ 
+ func Test_default_arg()
+   call assert_equal(1.0, Log(10))
+   call assert_equal(log(10), Log(10, exp(1)))
+   call assert_fails("call Log(1,2,3)", 'E118')
+ 
+   let res = Args(1)
+   call assert_equal(res.mandatory, 1)
+   call assert_equal(res.optional, v:null)
+   call assert_equal(res['0'], 0)
+ 
+   let res = Args(1,2)
+   call assert_equal(res.mandatory, 1)
+   call assert_equal(res.optional, 2)
+   call assert_equal(res['0'], 0)
+ 
+   let res = Args(1,2,3)
+   call assert_equal(res.mandatory, 1)
+   call assert_equal(res.optional, 2)
+   call assert_equal(res['0'], 1)
+ 
+   call assert_fails("call MakeBadFunc()", 'E989')
+   call assert_fails("fu F(a=1 ,) | endf", 'E475')
+ 
+   let d = Args2(7, v:none, 9)
+   call assert_equal([7, 2, 9], [d.a, d.b, d.c])
+ 
+   call assert_equal("\n"
+       \ .. "   function Args2(a = 1, b = 2, c = 3)\n"
+       \ .. "1    return deepcopy(a:)\n"
+       \ .. "   endfunction",
+       \ execute('func Args2'))
+ endfunc
*** ../vim-8.1.1309/src/userfunc.c      2019-05-09 15:12:45.180723879 +0200
--- src/userfunc.c      2019-05-09 21:03:36.645848343 +0200
***************
*** 75,80 ****
--- 75,81 ----
      char_u    endchar,
      garray_T  *newargs,
      int               *varargs,
+     garray_T  *default_args,
      int               skip)
  {
      int               mustend = FALSE;
***************
*** 82,90 ****
--- 83,95 ----
      char_u    *p = arg;
      int               c;
      int               i;
+     int               any_default = FALSE;
+     char_u    *expr;
  
      if (newargs != NULL)
        ga_init2(newargs, (int)sizeof(char_u *), 3);
+     if (default_args != NULL)
+       ga_init2(default_args, (int)sizeof(char_u *), 3);
  
      if (varargs != NULL)
        *varargs = FALSE;
***************
*** 140,145 ****
--- 145,187 ----
  
                *p = c;
            }
+           if (*skipwhite(p) == '=' && default_args != NULL)
+           {
+               typval_T        rettv;
+ 
+               any_default = TRUE;
+               p = skipwhite(p) + 1;
+               p = skipwhite(p);
+               expr = p;
+               if (eval1(&p, &rettv, FALSE) != FAIL)
+               {
+                   if (ga_grow(default_args, 1) == FAIL)
+                       goto err_ret;
+ 
+                   // trim trailing whitespace
+                   while (p > expr && VIM_ISWHITE(p[-1]))
+                       p--;
+                   c = *p;
+                   *p = NUL;
+                   expr = vim_strsave(expr);
+                   if (expr == NULL)
+                   {
+                       *p = c;
+                       goto err_ret;
+                   }
+                   ((char_u **)(default_args->ga_data))
+                                                [default_args->ga_len] = expr;
+                   default_args->ga_len++;
+                   *p = c;
+               }
+               else
+                   mustend = TRUE;
+           }
+           else if (any_default)
+           {
+               emsg(_("E989: Non-default argument follows default argument"));
+               mustend = TRUE;
+           }
            if (*p == ',')
                ++p;
            else
***************
*** 163,168 ****
--- 205,212 ----
  err_ret:
      if (newargs != NULL)
        ga_clear_strings(newargs);
+     if (default_args != NULL)
+       ga_clear_strings(default_args);
      return FAIL;
  }
  
***************
*** 210,216 ****
      ga_init(&newlines);
  
      /* First, check if this is a lambda expression. "->" must exist. */
!     ret = get_function_args(&start, '-', NULL, NULL, TRUE);
      if (ret == FAIL || *start != '>')
        return NOTDONE;
  
--- 254,260 ----
      ga_init(&newlines);
  
      /* First, check if this is a lambda expression. "->" must exist. */
!     ret = get_function_args(&start, '-', NULL, NULL, NULL, TRUE);
      if (ret == FAIL || *start != '>')
        return NOTDONE;
  
***************
*** 220,226 ****
      else
        pnewargs = NULL;
      *arg = skipwhite(*arg + 1);
!     ret = get_function_args(arg, '-', pnewargs, &varargs, FALSE);
      if (ret == FAIL || **arg != '>')
        goto errret;
  
--- 264,270 ----
      else
        pnewargs = NULL;
      *arg = skipwhite(*arg + 1);
!     ret = get_function_args(arg, '-', pnewargs, &varargs, NULL, FALSE);
      if (ret == FAIL || **arg != '>')
        goto errret;
  
***************
*** 272,277 ****
--- 316,322 ----
        STRCPY(fp->uf_name, name);
        hash_add(&func_hashtab, UF2HIKEY(fp));
        fp->uf_args = newargs;
+       ga_init(&fp->uf_def_args);
        fp->uf_lines = newlines;
        if (current_funccal != NULL && eval_lavars)
        {
***************
*** 729,734 ****
--- 774,780 ----
      int               using_sandbox = FALSE;
      funccall_T        *fc;
      int               save_did_emsg;
+     int               default_arg_err = FALSE;
      static int        depth = 0;
      dictitem_T        *v;
      int               fixvar_idx = 0; /* index in fixvar[] */
***************
*** 805,816 ****
  
      /*
       * Init a: variables.
!      * Set a:0 to "argcount".
       * Set a:000 to a list with room for the "..." arguments.
       */
      init_var_dict(&fc->l_avars, &fc->l_avars_var, VAR_SCOPE);
      add_nr_var(&fc->l_avars, &fc->fixvar[fixvar_idx++].var, "0",
!                               (varnumber_T)(argcount - fp->uf_args.ga_len));
      fc->l_avars.dv_lock = VAR_FIXED;
      /* Use "name" to avoid a warning from some compiler that checks the
       * destination size. */
--- 851,863 ----
  
      /*
       * Init a: variables.
!      * Set a:0 to "argcount" less number of named arguments, if >= 0.
       * Set a:000 to a list with room for the "..." arguments.
       */
      init_var_dict(&fc->l_avars, &fc->l_avars_var, VAR_SCOPE);
      add_nr_var(&fc->l_avars, &fc->fixvar[fixvar_idx++].var, "0",
!                               (varnumber_T)(argcount >= fp->uf_args.ga_len
!                                   ? argcount - fp->uf_args.ga_len : 0));
      fc->l_avars.dv_lock = VAR_FIXED;
      /* Use "name" to avoid a warning from some compiler that checks the
       * destination size. */
***************
*** 835,843 ****
                                                      (varnumber_T)firstline);
      add_nr_var(&fc->l_avars, &fc->fixvar[fixvar_idx++].var, "lastline",
                                                       (varnumber_T)lastline);
!     for (i = 0; i < argcount; ++i)
      {
        int         addlocal = FALSE;
  
        ai = i - fp->uf_args.ga_len;
        if (ai < 0)
--- 882,892 ----
                                                      (varnumber_T)firstline);
      add_nr_var(&fc->l_avars, &fc->fixvar[fixvar_idx++].var, "lastline",
                                                       (varnumber_T)lastline);
!     for (i = 0; i < argcount || i < fp->uf_args.ga_len; ++i)
      {
        int         addlocal = FALSE;
+       typval_T    def_rettv;
+       int         isdefault = FALSE;
  
        ai = i - fp->uf_args.ga_len;
        if (ai < 0)
***************
*** 846,851 ****
--- 895,919 ----
            name = FUNCARG(fp, i);
            if (islambda)
                addlocal = TRUE;
+ 
+           // evaluate named argument default expression
+           isdefault = ai + fp->uf_def_args.ga_len >= 0
+                      && (i >= argcount || (argvars[i].v_type == VAR_SPECIAL
+                                  && argvars[i].vval.v_number == VVAL_NONE));
+           if (isdefault)
+           {
+               char_u      *default_expr = NULL;
+               def_rettv.v_type = VAR_NUMBER;
+               def_rettv.vval.v_number = -1;
+ 
+               default_expr = ((char_u **)(fp->uf_def_args.ga_data))
+                                                [ai + fp->uf_def_args.ga_len];
+               if (eval1(&default_expr, &def_rettv, TRUE) == FAIL)
+               {
+                   default_arg_err = 1;
+                   break;
+               }
+           }
        }
        else
        {
***************
*** 867,875 ****
            v->di_flags |= DI_FLAGS_RO | DI_FLAGS_FIX;
        }
  
!       /* Note: the values are copied directly to avoid alloc/free.
!        * "argvars" must have VAR_FIXED for v_lock. */
!       v->di_tv = argvars[i];
        v->di_tv.v_lock = VAR_FIXED;
  
        if (addlocal)
--- 935,946 ----
            v->di_flags |= DI_FLAGS_RO | DI_FLAGS_FIX;
        }
  
!       if (isdefault)
!           v->di_tv = def_rettv;
!       else
!           // Note: the values are copied directly to avoid alloc/free.
!           // "argvars" must have VAR_FIXED for v_lock.
!           v->di_tv = argvars[i];
        v->di_tv.v_lock = VAR_FIXED;
  
        if (addlocal)
***************
*** 988,995 ****
      save_did_emsg = did_emsg;
      did_emsg = FALSE;
  
!     /* call do_cmdline() to execute the lines */
!     do_cmdline(NULL, get_func_line, (void *)fc,
                                     DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
  
      --RedrawingDisabled;
--- 1059,1069 ----
      save_did_emsg = did_emsg;
      did_emsg = FALSE;
  
!     if (default_arg_err && (fp->uf_flags & FC_ABORT))
!       did_emsg = TRUE;
!     else
!       // call do_cmdline() to execute the lines
!       do_cmdline(NULL, get_func_line, (void *)fc,
                                     DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
  
      --RedrawingDisabled;
***************
*** 1145,1150 ****
--- 1219,1225 ----
  func_clear_items(ufunc_T *fp)
  {
      ga_clear_strings(&(fp->uf_args));
+     ga_clear_strings(&(fp->uf_def_args));
      ga_clear_strings(&(fp->uf_lines));
  #ifdef FEAT_PROFILE
      vim_free(fp->uf_tml_count);
***************
*** 1498,1504 ****
  
                if (fp->uf_flags & FC_RANGE)
                    *doesrange = TRUE;
!               if (argcount < fp->uf_args.ga_len)
                    error = ERROR_TOOFEW;
                else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len)
                    error = ERROR_TOOMANY;
--- 1573,1579 ----
  
                if (fp->uf_flags & FC_RANGE)
                    *doesrange = TRUE;
!               if (argcount < fp->uf_args.ga_len - fp->uf_def_args.ga_len)
                    error = ERROR_TOOFEW;
                else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len)
                    error = ERROR_TOOMANY;
***************
*** 1624,1629 ****
--- 1699,1710 ----
        if (j)
            msg_puts(", ");
        msg_puts((char *)FUNCARG(fp, j));
+       if (j >= fp->uf_args.ga_len - fp->uf_def_args.ga_len)
+       {
+           msg_puts(" = ");
+           msg_puts(((char **)(fp->uf_def_args.ga_data))
+                      [j - fp->uf_args.ga_len + fp->uf_def_args.ga_len]);
+       }
      }
      if (fp->uf_varargs)
      {
***************
*** 1889,1894 ****
--- 1970,1976 ----
      char_u    *arg;
      char_u    *line_arg = NULL;
      garray_T  newargs;
+     garray_T  default_args;
      garray_T  newlines;
      int               varargs = FALSE;
      int               flags = 0;
***************
*** 2103,2109 ****
            emsg(_("E862: Cannot use g: here"));
      }
  
!     if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL)
        goto errret_2;
  
      /* find extra arguments "range", "dict", "abort" and "closure" */
--- 2185,2192 ----
            emsg(_("E862: Cannot use g: here"));
      }
  
!     if (get_function_args(&p, ')', &newargs, &varargs,
!                                           &default_args, eap->skip) == FAIL)
        goto errret_2;
  
      /* find extra arguments "range", "dict", "abort" and "closure" */
***************
*** 2511,2516 ****
--- 2594,2600 ----
        fp->uf_refcount = 1;
      }
      fp->uf_args = newargs;
+     fp->uf_def_args = default_args;
      fp->uf_lines = newlines;
      if ((flags & FC_CLOSURE) != 0)
      {
***************
*** 2535,2540 ****
--- 2619,2625 ----
  
  erret:
      ga_clear_strings(&newargs);
+     ga_clear_strings(&default_args);
  errret_2:
      ga_clear_strings(&newlines);
  ret_free:
*** ../vim-8.1.1309/src/version.c       2019-05-09 20:07:30.310817540 +0200
--- src/version.c       2019-05-09 20:30:13.721679453 +0200
***************
*** 769,770 ****
--- 769,772 ----
  {   /* Add new patch number below this line */
+ /**/
+     1310,
  /**/

-- 
The MS-Windows registry is no more hostile than any other bunch of state
information... that is held in a binary format... a format that nobody
understands... and is replicated and cached in a complex and largely
undocumented way... and contains large amounts of duplicate and obfuscated
information...  (Ben Peterson)

 /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
///        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
\\\  an exciting new programming language -- http://www.Zimbu.org        ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/201905091909.x49J9EV5001570%40masaka.moolenaar.net.
For more options, visit https://groups.google.com/d/optout.

Raspunde prin e-mail lui