Skip to content
Permalink
Browse files

patch 8.1.1310: named function arguments are never optional

Problem:    Named function arguments are never optional.
Solution:   Support optional function arguments with a default value. (Andy
            Massimino, closes #3952)
  • Loading branch information...
brammool committed May 9, 2019
1 parent 6b528fa commit 42ae78cfff171fbd7412306083fe200245d7a7a6
Showing with 227 additions and 46 deletions.
  1. +47 −4 runtime/doc/eval.txt
  2. +31 −30 src/structs.h
  3. +50 −0 src/testdir/test_user_func.vim
  4. +97 −12 src/userfunc.c
  5. +2 −0 src/version.c
@@ -10920,15 +10920,58 @@ change their contents. Thus you can pass a |List| to a function and have the
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.

*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

This comment has been minimized.

Copy link
@chdiza

chdiza May 9, 2019

Shouldn't that be a:value and not value?

This comment has been minimized.

Copy link
@brammool

brammool May 9, 2019

Author Contributor

Yes

endfunction
call Something('empty') "empty: 10"
call Something('key, 20) "key: 20"

This comment has been minimized.

Copy link
@k-takata

k-takata May 9, 2019

Member

Missing ending '.

This comment has been minimized.

Copy link
@brammool

brammool May 9, 2019

Author Contributor

I'll fix it.


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:".
@@ -1402,42 +1402,43 @@ typedef struct funccall_S funccall_T;
*/
typedef struct
{
int uf_varargs; /* variable nr of arguments */
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 */
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_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 */
// 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) */
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 */
#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
@@ -94,3 +94,53 @@ func Test_user_func()
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

0 comments on commit 42ae78c

Please sign in to comment.
You can’t perform that action at this time.