To: vim_dev@googlegroups.com Subject: Patch 8.2.1956 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ 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 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 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 \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 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 -- Bram@Moolenaar.net -- 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 ///