Patch 8.2.1956
Problem: Vim9: cannot specify argument types for lambda.
Solution: Allow adding argument types. Check arguments when calling a
function reference.
Files: src/userfunc.c, src/proto/userfunc.pro, src/vim9compile.c,
src/eval.c, src/testdir/test_vim9_disassemble.vim,
src/testdir/test_vim9_func.vim
*** ../vim-8.2.1955/src/userfunc.c 2020-10-20 14:25:03.930883006 +0200
--- src/userfunc.c 2020-11-05 18:08:44.696737953 +0100
***************
*** 54,64 ****
/*
* Get one function argument.
* If "argtypes" is not NULL also get the type: "arg: type".
* Return a pointer to after the type.
* When something is wrong return "arg".
*/
static char_u *
! one_function_arg(char_u *arg, garray_T *newargs, garray_T *argtypes, int skip)
{
char_u *p = arg;
char_u *arg_copy = NULL;
--- 54,70 ----
/*
* Get one function argument.
* If "argtypes" is not NULL also get the type: "arg: type".
+ * If "types_optional" is TRUE a missing type is OK, use "any".
* Return a pointer to after the type.
* When something is wrong return "arg".
*/
static char_u *
! one_function_arg(
! char_u *arg,
! garray_T *newargs,
! garray_T *argtypes,
! int types_optional,
! int skip)
{
char_u *p = arg;
char_u *arg_copy = NULL;
***************
*** 105,111 ****
}
// get any type from "arg: type"
! if (argtypes != NULL && ga_grow(argtypes, 1) == OK)
{
char_u *type = NULL;
--- 111,117 ----
}
// get any type from "arg: type"
! if (argtypes != NULL && (skip || ga_grow(argtypes, 1) == OK))
{
char_u *type = NULL;
***************
*** 118,139 ****
if (*p == ':')
{
++p;
! if (!VIM_ISWHITE(*p))
{
semsg(_(e_white_space_required_after_str), ":");
return arg;
}
type = skipwhite(p);
p = skip_type(type, TRUE);
! type = vim_strnsave(type, p - type);
}
! else if (*skipwhite(p) != '=')
{
semsg(_(e_missing_argument_type_for_str),
arg_copy == NULL ? arg : arg_copy);
return arg;
}
! ((char_u **)argtypes->ga_data)[argtypes->ga_len++] = type;
}
return p;
--- 124,152 ----
if (*p == ':')
{
++p;
! if (!skip && !VIM_ISWHITE(*p))
{
semsg(_(e_white_space_required_after_str), ":");
return arg;
}
type = skipwhite(p);
p = skip_type(type, TRUE);
! if (!skip)
! type = vim_strnsave(type, p - type);
}
! else if (*skipwhite(p) != '=' && !types_optional)
{
semsg(_(e_missing_argument_type_for_str),
arg_copy == NULL ? arg : arg_copy);
return arg;
}
! if (!skip)
! {
! if (type == NULL && types_optional)
! // lambda arguments default to "any" type
! type = vim_strsave((char_u *)"any");
! ((char_u **)argtypes->ga_data)[argtypes->ga_len++] = type;
! }
}
return p;
***************
*** 148,153 ****
--- 161,167 ----
char_u endchar,
garray_T *newargs,
garray_T *argtypes, // NULL unless using :def
+ int types_optional, // types optional if "argtypes" is not
NULL
int *varargs,
garray_T *default_args,
int skip,
***************
*** 196,202 ****
{
if (!skip)
semsg(_(e_invarg2), *argp);
! break;
}
if (*p == endchar)
break;
--- 210,216 ----
{
if (!skip)
semsg(_(e_invarg2), *argp);
! goto err_ret;
}
if (*p == endchar)
break;
***************
*** 213,224 ****
// ...name: list<type>
if (!eval_isnamec1(*p))
{
! emsg(_(e_missing_name_after_dots));
! break;
}
arg = p;
! p = one_function_arg(p, newargs, argtypes, skip);
if (p == arg)
break;
}
--- 227,240 ----
// ...name: list<type>
if (!eval_isnamec1(*p))
{
! if (!skip)
! emsg(_(e_missing_name_after_dots));
! goto err_ret;
}
arg = p;
! p = one_function_arg(p, newargs, argtypes, types_optional,
! skip);
if (p == arg)
break;
}
***************
*** 226,232 ****
else
{
arg = p;
! p = one_function_arg(p, newargs, argtypes, skip);
if (p == arg)
break;
--- 242,248 ----
else
{
arg = p;
! p = one_function_arg(p, newargs, argtypes, types_optional, skip);
if (p == arg)
break;
***************
*** 304,309 ****
--- 320,382 ----
}
/*
+ * Parse the argument types, filling "fp->uf_arg_types".
+ * Return OK or FAIL.
+ */
+ static int
+ parse_argument_types(ufunc_T *fp, garray_T *argtypes, int varargs)
+ {
+ ga_init2(&fp->uf_type_list, sizeof(type_T *), 10);
+ if (argtypes->ga_len > 0)
+ {
+ // When "varargs" is set the last name/type goes into uf_va_name
+ // and uf_va_type.
+ int len = argtypes->ga_len - (varargs ? 1 : 0);
+
+ if (len > 0)
+ fp->uf_arg_types = ALLOC_CLEAR_MULT(type_T *, len);
+ if (fp->uf_arg_types != NULL)
+ {
+ int i;
+ type_T *type;
+
+ for (i = 0; i < len; ++ i)
+ {
+ char_u *p = ((char_u **)argtypes->ga_data)[i];
+
+ if (p == NULL)
+ // will get the type from the default value
+ type = &t_unknown;
+ else
+ type = parse_type(&p, &fp->uf_type_list);
+ if (type == NULL)
+ return FAIL;
+ fp->uf_arg_types[i] = type;
+ }
+ }
+ if (varargs)
+ {
+ char_u *p;
+
+ // Move the last argument "...name: type" to uf_va_name and
+ // uf_va_type.
+ fp->uf_va_name = ((char_u **)fp->uf_args.ga_data)
+ [fp->uf_args.ga_len - 1];
+ --fp->uf_args.ga_len;
+ p = ((char_u **)argtypes->ga_data)[len];
+ if (p == NULL)
+ // todo: get type from default value
+ fp->uf_va_type = &t_any;
+ else
+ fp->uf_va_type = parse_type(&p, &fp->uf_type_list);
+ if (fp->uf_va_type == NULL)
+ return FAIL;
+ }
+ }
+ return OK;
+ }
+
+ /*
* Register function "fp" as using "current_funccal" as its scope.
*/
static int
***************
*** 386,401 ****
/*
* Parse a lambda expression and get a Funcref from "*arg".
* Return OK or FAIL. Returns NOTDONE for dict or {expr}.
*/
int
! get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
{
int evaluate = evalarg != NULL
&& (evalarg->eval_flags & EVAL_EVALUATE);
garray_T newargs;
garray_T newlines;
garray_T *pnewargs;
ufunc_T *fp = NULL;
partial_T *pt = NULL;
int varargs;
--- 459,480 ----
/*
* Parse a lambda expression and get a Funcref from "*arg".
+ * When "types_optional" is TRUE optionally take argument types.
* Return OK or FAIL. Returns NOTDONE for dict or {expr}.
*/
int
! get_lambda_tv(
! char_u **arg,
! typval_T *rettv,
! int types_optional,
! evalarg_T *evalarg)
{
int evaluate = evalarg != NULL
&& (evalarg->eval_flags & EVAL_EVALUATE);
garray_T newargs;
garray_T newlines;
garray_T *pnewargs;
+ garray_T argtypes;
ufunc_T *fp = NULL;
partial_T *pt = NULL;
int varargs;
***************
*** 411,418 ****
// First, check if this is a lambda expression. "->" must exist.
s = skipwhite(*arg + 1);
! ret = get_function_args(&s, '-', NULL, NULL, NULL, NULL, TRUE,
! NULL, NULL);
if (ret == FAIL || *s != '>')
return NOTDONE;
--- 490,498 ----
// First, check if this is a lambda expression. "->" must exist.
s = skipwhite(*arg + 1);
! ret = get_function_args(&s, '-', NULL,
! types_optional ? &argtypes : NULL, types_optional,
! NULL, NULL, TRUE, NULL, NULL);
if (ret == FAIL || *s != '>')
return NOTDONE;
***************
*** 422,430 ****
else
pnewargs = NULL;
*arg = skipwhite(*arg + 1);
! // TODO: argument types
! ret = get_function_args(arg, '-', pnewargs, NULL, &varargs, NULL, FALSE,
! NULL, NULL);
if (ret == FAIL || **arg != '>')
goto errret;
--- 502,510 ----
else
pnewargs = NULL;
*arg = skipwhite(*arg + 1);
! ret = get_function_args(arg, '-', pnewargs,
! types_optional ? &argtypes : NULL, types_optional,
! &varargs, NULL, FALSE, NULL, NULL);
if (ret == FAIL || **arg != '>')
goto errret;
***************
*** 489,494 ****
--- 569,578 ----
hash_add(&func_hashtab, UF2HIKEY(fp));
fp->uf_args = newargs;
ga_init(&fp->uf_def_args);
+ if (types_optional
+ && parse_argument_types(fp, &argtypes, FALSE) == FAIL)
+ goto errret;
+
fp->uf_lines = newlines;
if (current_funccal != NULL && eval_lavars)
{
***************
*** 523,533 ****
--- 607,621 ----
evalarg->eval_tofree = tofree;
else
vim_free(tofree);
+ if (types_optional)
+ ga_clear_strings(&argtypes);
return OK;
errret:
ga_clear_strings(&newargs);
ga_clear_strings(&newlines);
+ if (types_optional)
+ ga_clear_strings(&argtypes);
vim_free(fp);
vim_free(pt);
if (evalarg != NULL && evalarg->eval_tofree == NULL)
***************
*** 2907,2913 ****
// This may get more lines and make the pointers into the first line
// invalid.
if (get_function_args(&p, ')', &newargs,
! eap->cmdidx == CMD_def ? &argtypes : NULL,
&varargs, &default_args, eap->skip,
eap, &line_to_free) == FAIL)
goto errret_2;
--- 2995,3001 ----
// This may get more lines and make the pointers into the first line
// invalid.
if (get_function_args(&p, ')', &newargs,
! eap->cmdidx == CMD_def ? &argtypes : NULL, FALSE,
&varargs, &default_args, eap->skip,
eap, &line_to_free) == FAIL)
goto errret_2;
***************
*** 3502,3559 ****
cstack->cs_flags[i] |= CSF_FUNC_DEF;
}
! // parse the argument types
! ga_init2(&fp->uf_type_list, sizeof(type_T *), 10);
! if (argtypes.ga_len > 0)
! {
! // When "varargs" is set the last name/type goes into uf_va_name
! // and uf_va_type.
! int len = argtypes.ga_len - (varargs ? 1 : 0);
!
! if (len > 0)
! fp->uf_arg_types = ALLOC_CLEAR_MULT(type_T *, len);
! if (fp->uf_arg_types != NULL)
! {
! int i;
! type_T *type;
!
! for (i = 0; i < len; ++ i)
! {
! p = ((char_u **)argtypes.ga_data)[i];
! if (p == NULL)
! // will get the type from the default value
! type = &t_unknown;
! else
! type = parse_type(&p, &fp->uf_type_list);
! if (type == NULL)
! {
! SOURCING_LNUM = lnum_save;
! goto errret_2;
! }
! fp->uf_arg_types[i] = type;
! }
! }
! if (varargs)
! {
! // Move the last argument "...name: type" to uf_va_name and
! // uf_va_type.
! fp->uf_va_name = ((char_u **)fp->uf_args.ga_data)
! [fp->uf_args.ga_len - 1];
! --fp->uf_args.ga_len;
! p = ((char_u **)argtypes.ga_data)[len];
! if (p == NULL)
! // todo: get type from default value
! fp->uf_va_type = &t_any;
! else
! fp->uf_va_type = parse_type(&p, &fp->uf_type_list);
! if (fp->uf_va_type == NULL)
! {
! SOURCING_LNUM = lnum_save;
! goto errret_2;
! }
! }
! varargs = FALSE;
}
// parse the return type, if any
if (ret_type == NULL)
--- 3590,3601 ----
cstack->cs_flags[i] |= CSF_FUNC_DEF;
}
! if (parse_argument_types(fp, &argtypes, varargs) == FAIL)
! {
! SOURCING_LNUM = lnum_save;
! goto errret_2;
}
+ varargs = FALSE;
// parse the return type, if any
if (ret_type == NULL)
*** ../vim-8.2.1955/src/proto/userfunc.pro 2020-10-15 12:46:38.733199522
+0200
--- src/proto/userfunc.pro 2020-11-04 20:18:35.449766231 +0100
***************
*** 1,10 ****
/* userfunc.c */
void func_init(void);
hashtab_T *func_tbl_get(void);
! int get_function_args(char_u **argp, char_u endchar, garray_T *newargs,
garray_T *argtypes, int *varargs, garray_T *default_args, int skip, exarg_T
*eap, char_u **line_to_free);
char_u *get_lambda_name(void);
char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state);
! int get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg);
char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int
no_autoload);
void emsg_funcname(char *ermsg, char_u *name);
int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg,
evalarg_T *evalarg, funcexe_T *funcexe);
--- 1,10 ----
/* userfunc.c */
void func_init(void);
hashtab_T *func_tbl_get(void);
! int get_function_args(char_u **argp, char_u endchar, garray_T *newargs,
garray_T *argtypes, int types_optional, int *varargs, garray_T *default_args,
int skip, exarg_T *eap, char_u **line_to_free);
char_u *get_lambda_name(void);
char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state);
! int get_lambda_tv(char_u **arg, typval_T *rettv, int types_optional,
evalarg_T *evalarg);
char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int
no_autoload);
void emsg_funcname(char *ermsg, char_u *name);
int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg,
evalarg_T *evalarg, funcexe_T *funcexe);
*** ../vim-8.2.1955/src/vim9compile.c 2020-11-01 17:03:34.215513804 +0100
--- src/vim9compile.c 2020-11-05 18:38:55.388002674 +0100
***************
*** 1695,1700 ****
--- 1695,1724 ----
semsg(_(e_toomanyarg), name);
return FAIL;
}
+ if (type->tt_args != NULL)
+ {
+ int i;
+
+ for (i = 0; i < argcount; ++i)
+ {
+ int offset = -argcount + i - 1;
+ type_T *actual = ((type_T **)stack->ga_data)[
+ stack->ga_len + offset];
+ type_T *expected;
+
+ if (varargs && i >= type->tt_min_argcount - 1)
+ expected = type->tt_args[
+ type->tt_min_argcount - 1]->tt_member;
+ else
+ expected = type->tt_args[i];
+ if (need_type(actual, expected, offset,
+ cctx, TRUE, FALSE) == FAIL)
+ {
+ arg_type_mismatch(expected, actual, i + 1);
+ return FAIL;
+ }
+ }
+ }
}
ret_type = type->tt_member;
}
***************
*** 2835,2841 ****
evalarg.eval_cctx = cctx;
// Get the funcref in "rettv".
! if (get_lambda_tv(arg, &rettv, &evalarg) != OK)
{
clear_evalarg(&evalarg, NULL);
return FAIL;
--- 2859,2865 ----
evalarg.eval_cctx = cctx;
// Get the funcref in "rettv".
! if (get_lambda_tv(arg, &rettv, TRUE, &evalarg) != OK)
{
clear_evalarg(&evalarg, NULL);
return FAIL;
***************
*** 2844,2850 ****
ufunc = rettv.vval.v_partial->pt_func;
++ufunc->uf_refcount;
clear_tv(&rettv);
- ga_init2(&ufunc->uf_type_list, sizeof(type_T *), 10);
// The function will have one line: "return {expr}".
// Compile it into instructions.
--- 2868,2873 ----
***************
*** 2880,2886 ****
int ret = FAIL;
// Get the funcref in "rettv".
! if (get_lambda_tv(arg, &rettv, &EVALARG_EVALUATE) == FAIL)
return FAIL;
if (**arg != '(')
--- 2903,2909 ----
int ret = FAIL;
// Get the funcref in "rettv".
! if (get_lambda_tv(arg, &rettv, TRUE, &EVALARG_EVALUATE) == FAIL)
return FAIL;
if (**arg != '(')
***************
*** 3796,3805 ****
*/
case '{': {
char_u *start = skipwhite(*arg + 1);
// Find out what comes after the arguments.
ret = get_function_args(&start, '-', NULL,
! NULL, NULL, NULL, TRUE, NULL, NULL);
if (ret != FAIL && *start == '>')
ret = compile_lambda(arg, cctx);
else
--- 3819,3830 ----
*/
case '{': {
char_u *start = skipwhite(*arg + 1);
+ garray_T ga_arg;
// Find out what comes after the arguments.
ret = get_function_args(&start, '-', NULL,
! &ga_arg, TRUE, NULL, NULL,
! TRUE, NULL, NULL);
if (ret != FAIL && *start == '>')
ret = compile_lambda(arg, cctx);
else
*** ../vim-8.2.1955/src/eval.c 2020-10-28 13:53:46.549128959 +0100
--- src/eval.c 2020-11-04 20:19:02.369688601 +0100
***************
*** 3266,3272 ****
* Lambda: {arg, arg -> expr}
* Dictionary: {'key': val, 'key': val}
*/
! case '{': ret = get_lambda_tv(arg, rettv, evalarg);
if (ret == NOTDONE)
ret = eval_dict(arg, rettv, evalarg, FALSE);
break;
--- 3266,3272 ----
* Lambda: {arg, arg -> expr}
* Dictionary: {'key': val, 'key': val}
*/
! case '{': ret = get_lambda_tv(arg, rettv, FALSE, evalarg);
if (ret == NOTDONE)
ret = eval_dict(arg, rettv, evalarg, FALSE);
break;
***************
*** 3554,3560 ****
*arg += 2;
rettv->v_type = VAR_UNKNOWN;
! ret = get_lambda_tv(arg, rettv, evalarg);
if (ret != OK)
return FAIL;
else if (**arg != '(')
--- 3554,3560 ----
*arg += 2;
rettv->v_type = VAR_UNKNOWN;
! ret = get_lambda_tv(arg, rettv, FALSE, evalarg);
if (ret != OK)
return FAIL;
else if (**arg != '(')
*** ../vim-8.2.1955/src/testdir/test_vim9_disassemble.vim 2020-10-24
23:08:34.711491620 +0200
--- src/testdir/test_vim9_disassemble.vim 2020-11-04 21:42:30.000083952
+0100
***************
*** 788,793 ****
--- 788,815 ----
instr)
enddef
+ def LambdaWithType(): number
+ var Ref = {a: number -> a + 10}
+ return Ref(g:value)
+ enddef
+
+ def Test_disassemble_lambda_with_type()
+ g:value = 5
+ assert_equal(15, LambdaWithType())
+ var instr = execute('disassemble LambdaWithType')
+ assert_match('LambdaWithType\_s*' ..
+ 'var Ref = {a: number -> a + 10}\_s*' ..
+ '\d FUNCREF <lambda>\d\+\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'return Ref(g:value)\_s*' ..
+ '\d LOADG g:value\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d CHECKTYPE number stack\[-2\]\_s*' ..
+ '\d PCALL (argc 1)\_s*' ..
+ '\d RETURN',
+ instr)
+ enddef
+
def NestedOuter()
def g:Inner()
echomsg "inner"
*** ../vim-8.2.1955/src/testdir/test_vim9_func.vim 2020-10-30
21:49:36.302568284 +0100
--- src/testdir/test_vim9_func.vim 2020-11-04 21:53:54.245128579 +0100
***************
*** 322,329 ****
CheckDefFailure(['bufnr(xxx)'], 'E1001:')
CheckScriptFailure(['def Func(Ref: func(s: string))'], 'E475:')
- CheckDefFailure(['echo {i -> 0}()'], 'E119: Not enough arguments for
function: {i -> 0}()')
-
var lines =<< trim END
vim9script
def Func(s: string)
--- 322,327 ----
***************
*** 378,383 ****
--- 376,392 ----
delete('Xscript')
enddef
+ def Test_call_lambda_args()
+ CheckDefFailure(['echo {i -> 0}()'],
+ 'E119: Not enough arguments for function: {i -> 0}()')
+
+ var lines =<< trim END
+ var Ref = {x: number, y: number -> x + y}
+ echo Ref(1, 'x')
+ END
+ CheckDefFailure(lines, 'E1013: Argument 2: type mismatch, expected number
but got string')
+ enddef
+
" Default arg and varargs
def MyDefVarargs(one: string, two = 'foo', ...rest: list<string>): string
var res = one .. ',' .. two
*** ../vim-8.2.1955/src/version.c 2020-11-04 18:53:31.558111038 +0100
--- src/version.c 2020-11-04 21:55:06.412825623 +0100
***************
*** 752,753 ****
--- 752,755 ----
{ /* Add new patch number below this line */
+ /**/
+ 1956,
/**/
--
Some of the well known MS-Windows errors:
EMULTI Multitasking attempted, system confused
EKEYBOARD Keyboard locked, try getting out of this one!
EXPLAIN Unexplained error, please tell us what happened
EFUTURE Reserved for our future mistakes
/// 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/202011051746.0A5HkP4Q191502%40masaka.moolenaar.net.