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
index 81d6938..44ed397 100644
--- 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 @@
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
index c06cc23..7127331 100644
--- 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 @@
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 @@
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
index 4dac281..1d5f5d5 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -329,6 +329,19 @@
}
/*
+ * 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, "<lambda>%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 @@
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 @@
{
int len, flags = 0;
char_u *p;
- char_u name[20];
-
- sprintf((char*)name, "<lambda>%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 @@
}
/*
- * ":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 @@
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 @@
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 @@
}
}
eap->nextcmd = check_nextcmd(eap->arg);
- return;
+ return NULL;
}
/*
@@ -2469,7 +2480,7 @@
if (*p == '/')
++p;
eap->nextcmd = check_nextcmd(p);
- return;
+ return NULL;
}
ga_init(&newargs);
@@ -2493,25 +2504,34 @@
* 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)
{
- /*
- * 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())
+ // nested function, argument is (args).
+ paren = TRUE;
+ CLEAR_FIELD(fudi);
+ }
+ else
+ {
+ 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 @@
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 @@
// ":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 @@
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
index 982170e..da1a20f 100644
--- a/src/version.c
+++ b/src/version.c
@@ -747,6 +747,8 @@
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 695,
+/**/
694,
/**/
693,
diff --git a/src/vim9compile.c b/src/vim9compile.c
index 300adfb..7e700d5 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -101,6 +101,7 @@
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 @@
int error = FCERR_NONE;
ufunc_T *ufunc;
int res = FAIL;
+ lvar_T *lvar;
if (varlen >= sizeof(namebuf))
{
@@ -2641,6 +2643,16 @@
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 @@
}
/*
+ * 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 @@
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 @@
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 @@
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
index c74240f..665c6af 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -206,6 +206,11 @@
+ 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);