# HG changeset patch # User Bram Moolenaar # Date 1588627804 -7200 # Node ID 49b50843e725a811589e4b280796c31ff5947e94 # Parent 4148ba8690787be908471cd978ab82519f02733e patch 8.2.0695: Vim9: cannot define a function inside a function Commit: https://github.com/vim/vim/commit/04b12697838b232b8b17c553ccc74cf1f1bdb81c Author: Bram Moolenaar Date: Mon May 4 23:24:44 2020 +0200 patch 8.2.0695: Vim9: cannot define a function inside a function Problem: Vim9: cannot define a function inside a function. Solution: Initial support for :def inside :def. diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro --- a/src/proto/userfunc.pro +++ b/src/proto/userfunc.pro @@ -2,6 +2,7 @@ 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); int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate); char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload); void emsg_funcname(char *ermsg, char_u *name); @@ -22,6 +23,7 @@ void user_func_error(int error, char_u * int call_func(char_u *funcname, int len, typval_T *rettv, int argcount_in, typval_T *argvars_in, funcexe_T *funcexe); char_u *trans_function_name(char_u **pp, int *is_global, int skip, int flags, funcdict_T *fdp, partial_T **partial); char_u *untrans_function_name(char_u *name); +ufunc_T *def_function(exarg_T *eap, char_u *name_arg, void *context); void ex_function(exarg_T *eap); int eval_fname_script(char_u *p); int translated_function_exists(char_u *name, int is_global); diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -2,19 +2,7 @@ source check.vim source view_util.vim - -" Check that "lines" inside ":def" results in an "error" message. -func CheckDefFailure(lines, error) - call writefile(['def Func()'] + a:lines + ['enddef'], 'Xdef') - call assert_fails('so Xdef', a:error, a:lines) - call delete('Xdef') -endfunc - -func CheckScriptFailure(lines, error) - call writefile(a:lines, 'Xdef') - call assert_fails('so Xdef', a:error, a:lines) - call delete('Xdef') -endfunc +source vim9.vim func Test_def_basic() def SomeFunc(): string @@ -95,8 +83,17 @@ def Test_call_default_args() assert_equal('one', MyDefaultArgs('one')) assert_fails('call MyDefaultArgs("one", "two")', 'E118:') - call CheckScriptFailure(['def Func(arg: number = asdf)', 'enddef'], 'E1001:') - call CheckScriptFailure(['def Func(arg: number = "text")', 'enddef'], 'E1013: argument 1: type mismatch, expected number but got string') + CheckScriptFailure(['def Func(arg: number = asdf)', 'enddef'], 'E1001:') + CheckScriptFailure(['def Func(arg: number = "text")', 'enddef'], 'E1013: argument 1: type mismatch, expected number but got string') +enddef + +def Test_nested_function() + def Nested(arg: string): string + return 'nested ' .. arg + enddef + assert_equal('nested function', Nested('function')) + + CheckDefFailure(['func Nested()', 'endfunc'], 'E1086:') enddef func Test_call_default_args_from_func() @@ -721,5 +718,13 @@ def Test_closure_using_argument() unlet g:UseVararg enddef +def Test_nested_closure() + let local = 'text' + def Closure(arg: string): string + return local .. arg + enddef + assert_equal('text!!!', Closure('!!!')) +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/userfunc.c b/src/userfunc.c --- a/src/userfunc.c +++ b/src/userfunc.c @@ -329,6 +329,19 @@ set_ufunc_name(ufunc_T *fp, char_u *name } /* + * Get a name for a lambda. Returned in static memory. + */ + char_u * +get_lambda_name(void) +{ + static char_u name[30]; + static int lambda_no = 0; + + sprintf((char*)name, "%d", ++lambda_no); + return name; +} + +/* * Parse a lambda expression and get a Funcref from "*arg". * Return OK or FAIL. Returns NOTDONE for dict or {expr}. */ @@ -344,7 +357,6 @@ get_lambda_tv(char_u **arg, typval_T *re int ret; char_u *start = skipwhite(*arg + 1); char_u *s, *e; - static int lambda_no = 0; int *old_eval_lavars = eval_lavars_used; int eval_lavars = FALSE; @@ -392,9 +404,7 @@ get_lambda_tv(char_u **arg, typval_T *re { int len, flags = 0; char_u *p; - char_u name[20]; - - sprintf((char*)name, "%d", ++lambda_no); + char_u *name = get_lambda_name(); fp = alloc_clear(offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); if (fp == NULL) @@ -2364,10 +2374,11 @@ untrans_function_name(char_u *name) } /* - * ":function" + * ":function" also supporting nested ":def". + * Returns a pointer to the function or NULL if no function defined. */ - void -ex_function(exarg_T *eap) + ufunc_T * +def_function(exarg_T *eap, char_u *name_arg, void *context) { char_u *theline; char_u *line_to_free = NULL; @@ -2375,7 +2386,7 @@ ex_function(exarg_T *eap) int c; int saved_did_emsg; int saved_wait_return = need_wait_return; - char_u *name = NULL; + char_u *name = name_arg; int is_global = FALSE; char_u *p; char_u *arg; @@ -2387,7 +2398,7 @@ ex_function(exarg_T *eap) int varargs = FALSE; int flags = 0; char_u *ret_type = NULL; - ufunc_T *fp; + ufunc_T *fp = NULL; int overwrite = FALSE; int indent; int nesting; @@ -2429,7 +2440,7 @@ ex_function(exarg_T *eap) } } eap->nextcmd = check_nextcmd(eap->arg); - return; + return NULL; } /* @@ -2469,7 +2480,7 @@ ex_function(exarg_T *eap) if (*p == '/') ++p; eap->nextcmd = check_nextcmd(p); - return; + return NULL; } ga_init(&newargs); @@ -2493,25 +2504,34 @@ ex_function(exarg_T *eap) * g:func global function name, same as "func" */ p = eap->arg; - name = trans_function_name(&p, &is_global, eap->skip, - TFN_NO_AUTOLOAD, &fudi, NULL); - paren = (vim_strchr(p, '(') != NULL); - if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) + if (name_arg != NULL) + { + // nested function, argument is (args). + paren = TRUE; + CLEAR_FIELD(fudi); + } + else { - /* - * Return on an invalid expression in braces, unless the expression - * evaluation has been cancelled due to an aborting error, an - * interrupt, or an exception. - */ - if (!aborting()) + name = trans_function_name(&p, &is_global, eap->skip, + TFN_NO_AUTOLOAD, &fudi, NULL); + paren = (vim_strchr(p, '(') != NULL); + if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { - if (!eap->skip && fudi.fd_newkey != NULL) - semsg(_(e_dictkey), fudi.fd_newkey); - vim_free(fudi.fd_newkey); - return; + /* + * Return on an invalid expression in braces, unless the expression + * evaluation has been cancelled due to an aborting error, an + * interrupt, or an exception. + */ + if (!aborting()) + { + if (!eap->skip && fudi.fd_newkey != NULL) + semsg(_(e_dictkey), fudi.fd_newkey); + vim_free(fudi.fd_newkey); + return NULL; + } + else + eap->skip = TRUE; } - else - eap->skip = TRUE; } // An error in a function call during evaluation of an expression in magic @@ -2596,7 +2616,7 @@ ex_function(exarg_T *eap) ga_init2(&newlines, (int)sizeof(char_u *), 3); - if (!eap->skip) + if (!eap->skip && name_arg == NULL) { // Check the name of the function. Unless it's a dictionary function // (that we are overwriting). @@ -3255,7 +3275,7 @@ ex_function(exarg_T *eap) // ":def Func()" needs to be compiled if (eap->cmdidx == CMD_def) - compile_def_function(fp, FALSE, NULL); + compile_def_function(fp, FALSE, context); goto ret_free; @@ -3269,10 +3289,22 @@ ret_free: vim_free(skip_until); vim_free(line_to_free); vim_free(fudi.fd_newkey); - vim_free(name); + if (name != name_arg) + vim_free(name); vim_free(ret_type); did_emsg |= saved_did_emsg; need_wait_return |= saved_wait_return; + + return fp; +} + +/* + * ":function" + */ + void +ex_function(exarg_T *eap) +{ + def_function(eap, NULL, NULL); } /* diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -747,6 +747,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 695, +/**/ 694, /**/ 693, diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -101,6 +101,7 @@ typedef struct { int lv_from_outer; // when TRUE using ctx_outer scope int lv_const; // when TRUE cannot be assigned to int lv_arg; // when TRUE this is an argument + int lv_func_idx; // for nested function } lvar_T; /* @@ -2615,6 +2616,7 @@ compile_call(char_u **arg, size_t varlen int error = FCERR_NONE; ufunc_T *ufunc; int res = FAIL; + lvar_T *lvar; if (varlen >= sizeof(namebuf)) { @@ -2641,6 +2643,16 @@ compile_call(char_u **arg, size_t varlen goto theend; } + // Check if the name is a nested function. + lvar = lookup_local(namebuf, varlen, cctx); + if (lvar != NULL && lvar->lv_func_idx > 0) + { + dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + + lvar->lv_func_idx; + res = generate_CALL(cctx, dfunc->df_ufunc, argcount); + goto theend; + } + // If we can find the function by name generate the right call. ufunc = find_func(name, FALSE, cctx); if (ufunc != NULL) @@ -4049,6 +4061,64 @@ compile_return(char_u *arg, int set_retu } /* + * Get a line from the compilation context, compatible with exarg_T getline(). + * Return a pointer to the line in allocated memory. + * Return NULL for end-of-file or some error. + */ + static char_u * +exarg_getline( + int c UNUSED, + void *cookie, + int indent UNUSED, + int do_concat UNUSED) +{ + cctx_T *cctx = (cctx_T *)cookie; + + if (cctx->ctx_lnum == cctx->ctx_ufunc->uf_lines.ga_len) + { + iemsg("Heredoc got to end"); + return NULL; + } + ++cctx->ctx_lnum; + return vim_strsave(((char_u **)cctx->ctx_ufunc->uf_lines.ga_data) + [cctx->ctx_lnum]); +} + +/* + * Compile a nested :def command. + */ + static char_u * +compile_nested_function(exarg_T *eap, cctx_T *cctx) +{ + char_u *name_start = eap->arg; + char_u *name_end = to_name_end(eap->arg, FALSE); + char_u *name = get_lambda_name(); + lvar_T *lvar; + ufunc_T *ufunc; + + eap->arg = name_end; + eap->getline = exarg_getline; + eap->cookie = cctx; + eap->skip = cctx->ctx_skip == TRUE; + eap->forceit = FALSE; + ufunc = def_function(eap, name, cctx); + + if (ufunc == NULL) + return NULL; + + // Define a local variable for the function, but change the index to -1 to + // mark it as a function name. + lvar = reserve_local(cctx, name_start, name_end - name_start, + TRUE, &t_func_unknown); + lvar->lv_idx = 0; + ++cctx->ctx_locals_count; // doesn't count as a local variable + lvar->lv_func_idx = ufunc->uf_dfunc_idx; + + // TODO: warning for trailing? + return (char_u *)""; +} + +/* * Return the length of an assignment operator, or zero if there isn't one. */ int @@ -4077,30 +4147,6 @@ static char *reserved[] = { NULL }; -/* - * Get a line for "=<<". - * Return a pointer to the line in allocated memory. - * Return NULL for end-of-file or some error. - */ - static char_u * -heredoc_getline( - int c UNUSED, - void *cookie, - int indent UNUSED, - int do_concat UNUSED) -{ - cctx_T *cctx = (cctx_T *)cookie; - - if (cctx->ctx_lnum == cctx->ctx_ufunc->uf_lines.ga_len) - { - iemsg("Heredoc got to end"); - return NULL; - } - ++cctx->ctx_lnum; - return vim_strsave(((char_u **)cctx->ctx_ufunc->uf_lines.ga_data) - [cctx->ctx_lnum]); -} - typedef enum { dest_local, dest_option, @@ -4394,7 +4440,7 @@ compile_assignment(char_u *arg, exarg_T listitem_T *li; // [let] varname =<< [trim] {end} - eap->getline = heredoc_getline; + eap->getline = exarg_getline; eap->cookie = cctx; l = heredoc_get(eap, op + 3, FALSE); @@ -6299,9 +6345,12 @@ compile_def_function(ufunc_T *ufunc, int switch (ea.cmdidx) { case CMD_def: + ea.arg = p; + line = compile_nested_function(&ea, &cctx); + break; + case CMD_function: - // TODO: Nested function - emsg("Nested function not implemented yet"); + emsg(_("E1086: Cannot use :function inside :def")); goto erret; case CMD_return: diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -206,6 +206,11 @@ call_dfunc(int cdf_idx, int argcount_arg + dfunc->df_varcount + dfunc->df_closure_count) == FAIL) return FAIL; + // Closure may need the function context where it was defined. + // TODO: assuming current context. + ectx->ec_outer_stack = &ectx->ec_stack; + ectx->ec_outer_frame = ectx->ec_frame_idx; + // Move the vararg-list to below the missing optional arguments. if (vararg_count > 0 && arg_to_add > 0) *STACK_TV_BOT(arg_to_add - 1) = *STACK_TV_BOT(-1);