changeset 24188:c20e763bc73c v8.2.2635

patch 8.2.2635: Vim9: cannot define an inline function Commit: https://github.com/vim/vim/commit/7a6eaa06f98cef61d2131c25b4b47b8262cb8f59 Author: Bram Moolenaar <Bram@vim.org> Date: Sun Mar 21 20:53:29 2021 +0100 patch 8.2.2635: Vim9: cannot define an inline function Problem: Vim9: cannot define an inline function. Solution: Make an inline function mostly work.
author Bram Moolenaar <Bram@vim.org>
date Sun, 21 Mar 2021 21:00:05 +0100
parents 451e31607885
children 60c5b6cb5130
files src/errors.h src/misc2.c src/proto/vim9compile.pro src/testdir/test_vim9_expr.vim src/userfunc.c src/version.c src/vim9compile.c
diffstat 7 files changed, 573 insertions(+), 362 deletions(-) [+]
line wrap: on
line diff
--- a/src/errors.h
+++ b/src/errors.h
@@ -377,3 +377,7 @@ EXTERN char e_import_as_name_not_support
 	INIT(= N_("E1169: 'import * as {name}' not supported here"));
 EXTERN char e_cannot_use_hash_curly_to_start_comment[]
 	INIT(= N_("E1170: Cannot use #{ to start a comment"));
+EXTERN char e_missing_end_block[]
+	INIT(= N_("E1171: Missing } after inline function"));
+EXTERN char e_cannot_use_default_values_in_lambda[]
+	INIT(= N_("E1172: Cannot use default values in a lambda"));
--- a/src/misc2.c
+++ b/src/misc2.c
@@ -2026,8 +2026,9 @@ ga_clear_strings(garray_T *gap)
 {
     int		i;
 
-    for (i = 0; i < gap->ga_len; ++i)
-	vim_free(((char_u **)(gap->ga_data))[i]);
+    if (gap->ga_data != NULL)
+	for (i = 0; i < gap->ga_len; ++i)
+	    vim_free(((char_u **)(gap->ga_data))[i]);
     ga_clear(gap);
 }
 
--- a/src/proto/vim9compile.pro
+++ b/src/proto/vim9compile.pro
@@ -14,6 +14,7 @@ char_u *to_name_end(char_u *arg, int use
 char_u *to_name_const_end(char_u *arg);
 exprtype_T get_compare_type(char_u *p, int *len, int *type_is);
 void error_white_both(char_u *op, int len);
+void fill_exarg_from_cctx(exarg_T *eap, cctx_T *cctx);
 int assignment_len(char_u *p, int *heredoc);
 void vim9_declare_error(char_u *name);
 int check_vim9_unlet(char_u *name);
--- a/src/testdir/test_vim9_expr.vim
+++ b/src/testdir/test_vim9_expr.vim
@@ -1946,6 +1946,25 @@ def Test_expr7_lambda()
   CheckScriptSuccess(lines)
 enddef
 
+def Test_expr7_lambda_block()
+  var lines =<< trim END
+      var Func = (s: string): string => {
+                      return 'hello ' .. s
+                    }
+      assert_equal('hello there', Func('there'))
+
+      var ll = range(3)
+      var dll = mapnew(ll, (k, v): string => {
+          if v % 2
+            return 'yes'
+          endif
+          return 'no'
+        })
+      assert_equal(['no', 'yes', 'no'], dll)
+  END
+  CheckDefAndScriptSuccess(lines)
+enddef
+
 def NewLambdaWithComments(): func
   return (x) =>
             # some comment
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -397,6 +397,25 @@ parse_argument_types(ufunc_T *fp, garray
     return OK;
 }
 
+    static int
+parse_return_type(ufunc_T *fp, char_u *ret_type)
+{
+    if (ret_type == NULL)
+	fp->uf_ret_type = &t_void;
+    else
+    {
+	char_u *p = ret_type;
+
+	fp->uf_ret_type = parse_type(&p, &fp->uf_type_list, TRUE);
+	if (fp->uf_ret_type == NULL)
+	{
+	    fp->uf_ret_type = &t_void;
+	    return FAIL;
+	}
+    }
+    return OK;
+}
+
 /*
  * Register function "fp" as using "current_funccal" as its scope.
  */
@@ -536,7 +555,501 @@ skip_arrow(
 }
 
 /*
- * Parse a lambda expression and get a Funcref from "*arg".
+ * Check if "*cmd" points to a function command and if so advance "*cmd" and
+ * return TRUE.
+ * Otherwise return FALSE;
+ * Do not consider "function(" to be a command.
+ */
+    static int
+is_function_cmd(char_u **cmd)
+{
+    char_u *p = *cmd;
+
+    if (checkforcmd(&p, "function", 2))
+    {
+	if (*p == '(')
+	    return FALSE;
+	*cmd = p;
+	return TRUE;
+    }
+    return FALSE;
+}
+
+/*
+ * Read the body of a function, put every line in "newlines".
+ * "newlines" must already have been initialized.
+ * "eap->cmdidx" is CMD_function, CMD_def or CMD_block;
+ */
+    static int
+get_function_body(
+	exarg_T	    *eap,
+	garray_T    *newlines,
+	char_u	    *line_arg_in,
+	char_u	    **line_to_free)
+{
+    linenr_T	sourcing_lnum_top = SOURCING_LNUM;
+    linenr_T	sourcing_lnum_off;
+    int		saved_wait_return = need_wait_return;
+    char_u	*line_arg = line_arg_in;
+    int		vim9_function = eap->cmdidx == CMD_def
+						   || eap->cmdidx == CMD_block;
+#define MAX_FUNC_NESTING 50
+    char	nesting_def[MAX_FUNC_NESTING];
+    int		nesting = 0;
+    getline_opt_T getline_options;
+    int		indent = 2;
+    char_u	*skip_until = NULL;
+    int		ret = FAIL;
+    int		is_heredoc = FALSE;
+    char_u	*heredoc_trimmed = NULL;
+
+    // Detect having skipped over comment lines to find the return
+    // type.  Add NULL lines to keep the line count correct.
+    sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie);
+    if (SOURCING_LNUM < sourcing_lnum_off)
+    {
+	sourcing_lnum_off -= SOURCING_LNUM;
+	if (ga_grow(newlines, sourcing_lnum_off) == FAIL)
+	    goto theend;
+	while (sourcing_lnum_off-- > 0)
+	    ((char_u **)(newlines->ga_data))[newlines->ga_len++] = NULL;
+    }
+
+    nesting_def[nesting] = vim9_function;
+    getline_options = vim9_function
+				? GETLINE_CONCAT_CONTBAR : GETLINE_CONCAT_CONT;
+    for (;;)
+    {
+	char_u	*theline;
+	char_u	*p;
+	char_u	*arg;
+
+	if (KeyTyped)
+	{
+	    msg_scroll = TRUE;
+	    saved_wait_return = FALSE;
+	}
+	need_wait_return = FALSE;
+
+	if (line_arg != NULL)
+	{
+	    // Use eap->arg, split up in parts by line breaks.
+	    theline = line_arg;
+	    p = vim_strchr(theline, '\n');
+	    if (p == NULL)
+		line_arg += STRLEN(line_arg);
+	    else
+	    {
+		*p = NUL;
+		line_arg = p + 1;
+	    }
+	}
+	else
+	{
+	    vim_free(*line_to_free);
+	    if (eap->getline == NULL)
+		theline = getcmdline(':', 0L, indent, getline_options);
+	    else
+		theline = eap->getline(':', eap->cookie, indent,
+							      getline_options);
+	    *line_to_free = theline;
+	}
+	if (KeyTyped)
+	    lines_left = Rows - 1;
+	if (theline == NULL)
+	{
+	    // Use the start of the function for the line number.
+	    SOURCING_LNUM = sourcing_lnum_top;
+	    if (skip_until != NULL)
+		semsg(_(e_missing_heredoc_end_marker_str), skip_until);
+	    else if (eap->cmdidx == CMD_def)
+		emsg(_(e_missing_enddef));
+	    else if (eap->cmdidx == CMD_block)
+		emsg(_(e_missing_end_block));
+	    else
+		emsg(_("E126: Missing :endfunction"));
+	    goto theend;
+	}
+
+	// Detect line continuation: SOURCING_LNUM increased more than one.
+	sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie);
+	if (SOURCING_LNUM < sourcing_lnum_off)
+	    sourcing_lnum_off -= SOURCING_LNUM;
+	else
+	    sourcing_lnum_off = 0;
+
+	if (skip_until != NULL)
+	{
+	    // Don't check for ":endfunc"/":enddef" between
+	    // * ":append" and "."
+	    // * ":python <<EOF" and "EOF"
+	    // * ":let {var-name} =<< [trim] {marker}" and "{marker}"
+	    if (heredoc_trimmed == NULL
+		    || (is_heredoc && skipwhite(theline) == theline)
+		    || STRNCMP(theline, heredoc_trimmed,
+						 STRLEN(heredoc_trimmed)) == 0)
+	    {
+		if (heredoc_trimmed == NULL)
+		    p = theline;
+		else if (is_heredoc)
+		    p = skipwhite(theline) == theline
+				 ? theline : theline + STRLEN(heredoc_trimmed);
+		else
+		    p = theline + STRLEN(heredoc_trimmed);
+		if (STRCMP(p, skip_until) == 0)
+		{
+		    VIM_CLEAR(skip_until);
+		    VIM_CLEAR(heredoc_trimmed);
+		    getline_options = vim9_function
+				? GETLINE_CONCAT_CONTBAR : GETLINE_CONCAT_CONT;
+		    is_heredoc = FALSE;
+		}
+	    }
+	}
+	else
+	{
+	    int c;
+
+	    // skip ':' and blanks
+	    for (p = theline; VIM_ISWHITE(*p) || *p == ':'; ++p)
+		;
+
+	    // Check for "endfunction", "enddef" or "}".
+	    // When a ":" follows it must be a dict key; "enddef: value,"
+	    if ((nesting == 0 && eap->cmdidx == CMD_block)
+		    ? *p == '}'
+		    : (checkforcmd(&p, nesting_def[nesting]
+						? "enddef" : "endfunction", 4)
+			&& *p != ':'))
+	    {
+		if (nesting-- == 0)
+		{
+		    char_u *nextcmd = NULL;
+
+		    if (*p == '|' || *p == '}')
+			nextcmd = p + 1;
+		    else if (line_arg != NULL && *skipwhite(line_arg) != NUL)
+			nextcmd = line_arg;
+		    else if (*p != NUL && *p != (vim9_function ? '#' : '"')
+					&& p_verbose > 0
+					&& eap->cmdidx != CMD_block)
+			give_warning2(eap->cmdidx == CMD_def
+			    ? (char_u *)_("W1001: Text found after :enddef: %s")
+			    : (char_u *)_("W22: Text found after :endfunction: %s"),
+			     p, TRUE);
+		    if (nextcmd != NULL)
+		    {
+			// Another command follows. If the line came from "eap"
+			// we can simply point into it, otherwise we need to
+			// change "eap->cmdlinep".
+			eap->nextcmd = nextcmd;
+			if (*line_to_free != NULL)
+			{
+			    vim_free(*eap->cmdlinep);
+			    *eap->cmdlinep = *line_to_free;
+			    *line_to_free = NULL;
+			}
+		    }
+		    break;
+		}
+	    }
+
+	    // Check for mismatched "endfunc" or "enddef".
+	    // We don't check for "def" inside "func" thus we also can't check
+	    // for "enddef".
+	    // We continue to find the end of the function, although we might
+	    // not find it.
+	    else if (nesting_def[nesting])
+	    {
+		if (checkforcmd(&p, "endfunction", 4) && *p != ':')
+		    emsg(_(e_mismatched_endfunction));
+	    }
+	    else if (eap->cmdidx == CMD_def && checkforcmd(&p, "enddef", 4))
+		emsg(_(e_mismatched_enddef));
+
+	    // Increase indent inside "if", "while", "for" and "try", decrease
+	    // at "end".
+	    if (indent > 2 && (*p == '}' || STRNCMP(p, "end", 3) == 0))
+		indent -= 2;
+	    else if (STRNCMP(p, "if", 2) == 0
+		    || STRNCMP(p, "wh", 2) == 0
+		    || STRNCMP(p, "for", 3) == 0
+		    || STRNCMP(p, "try", 3) == 0)
+		indent += 2;
+
+	    // Check for defining a function inside this function.
+	    // Only recognize "def" inside "def", not inside "function",
+	    // For backwards compatibility, see Test_function_python().
+	    c = *p;
+	    if (is_function_cmd(&p)
+		    || (eap->cmdidx == CMD_def && checkforcmd(&p, "def", 3)))
+	    {
+		if (*p == '!')
+		    p = skipwhite(p + 1);
+		p += eval_fname_script(p);
+		vim_free(trans_function_name(&p, NULL, TRUE, 0, NULL,
+								  NULL, NULL));
+		if (*skipwhite(p) == '(')
+		{
+		    if (nesting == MAX_FUNC_NESTING - 1)
+			emsg(_(e_function_nesting_too_deep));
+		    else
+		    {
+			++nesting;
+			nesting_def[nesting] = (c == 'd');
+			indent += 2;
+		    }
+		}
+	    }
+
+	    // Check for ":append", ":change", ":insert".  Not for :def.
+	    p = skip_range(p, FALSE, NULL);
+	    if (!vim9_function
+		&& ((p[0] == 'a' && (!ASCII_ISALPHA(p[1]) || p[1] == 'p'))
+		    || (p[0] == 'c'
+			&& (!ASCII_ISALPHA(p[1]) || (p[1] == 'h'
+				&& (!ASCII_ISALPHA(p[2]) || (p[2] == 'a'
+					&& (STRNCMP(&p[3], "nge", 3) != 0
+					    || !ASCII_ISALPHA(p[6])))))))
+		    || (p[0] == 'i'
+			&& (!ASCII_ISALPHA(p[1]) || (p[1] == 'n'
+				&& (!ASCII_ISALPHA(p[2])
+				    || (p[2] == 's'
+					&& (!ASCII_ISALPHA(p[3])
+						|| p[3] == 'e'))))))))
+		skip_until = vim_strsave((char_u *)".");
+
+	    // Check for ":python <<EOF", ":tcl <<EOF", etc.
+	    arg = skipwhite(skiptowhite(p));
+	    if (arg[0] == '<' && arg[1] =='<'
+		    && ((p[0] == 'p' && p[1] == 'y'
+				    && (!ASCII_ISALNUM(p[2]) || p[2] == 't'
+					|| ((p[2] == '3' || p[2] == 'x')
+						   && !ASCII_ISALPHA(p[3]))))
+			|| (p[0] == 'p' && p[1] == 'e'
+				    && (!ASCII_ISALPHA(p[2]) || p[2] == 'r'))
+			|| (p[0] == 't' && p[1] == 'c'
+				    && (!ASCII_ISALPHA(p[2]) || p[2] == 'l'))
+			|| (p[0] == 'l' && p[1] == 'u' && p[2] == 'a'
+				    && !ASCII_ISALPHA(p[3]))
+			|| (p[0] == 'r' && p[1] == 'u' && p[2] == 'b'
+				    && (!ASCII_ISALPHA(p[3]) || p[3] == 'y'))
+			|| (p[0] == 'm' && p[1] == 'z'
+				    && (!ASCII_ISALPHA(p[2]) || p[2] == 's'))
+			))
+	    {
+		// ":python <<" continues until a dot, like ":append"
+		p = skipwhite(arg + 2);
+		if (STRNCMP(p, "trim", 4) == 0)
+		{
+		    // Ignore leading white space.
+		    p = skipwhite(p + 4);
+		    heredoc_trimmed = vim_strnsave(theline,
+						 skipwhite(theline) - theline);
+		}
+		if (*p == NUL)
+		    skip_until = vim_strsave((char_u *)".");
+		else
+		    skip_until = vim_strnsave(p, skiptowhite(p) - p);
+		getline_options = GETLINE_NONE;
+		is_heredoc = TRUE;
+	    }
+
+	    // Check for ":cmd v =<< [trim] EOF"
+	    //       and ":cmd [a, b] =<< [trim] EOF"
+	    //       and "lines =<< [trim] EOF" for Vim9
+	    // Where "cmd" can be "let", "var", "final" or "const".
+	    arg = skipwhite(skiptowhite(p));
+	    if (*arg == '[')
+		arg = vim_strchr(arg, ']');
+	    if (arg != NULL)
+	    {
+		int found = (eap->cmdidx == CMD_def && arg[0] == '='
+					     && arg[1] == '<' && arg[2] =='<');
+
+		if (!found)
+		    // skip over the argument after "cmd"
+		    arg = skipwhite(skiptowhite(arg));
+		if (found || (arg[0] == '=' && arg[1] == '<' && arg[2] =='<'
+			&& (checkforcmd(&p, "let", 2)
+			    || checkforcmd(&p, "var", 3)
+			    || checkforcmd(&p, "final", 5)
+			    || checkforcmd(&p, "const", 5))))
+		{
+		    p = skipwhite(arg + 3);
+		    if (STRNCMP(p, "trim", 4) == 0)
+		    {
+			// Ignore leading white space.
+			p = skipwhite(p + 4);
+			heredoc_trimmed = vim_strnsave(theline,
+						 skipwhite(theline) - theline);
+		    }
+		    skip_until = vim_strnsave(p, skiptowhite(p) - p);
+		    getline_options = GETLINE_NONE;
+		    is_heredoc = TRUE;
+		}
+	    }
+	}
+
+	// Add the line to the function.
+	if (ga_grow(newlines, 1 + sourcing_lnum_off) == FAIL)
+	    goto theend;
+
+	// Copy the line to newly allocated memory.  get_one_sourceline()
+	// allocates 250 bytes per line, this saves 80% on average.  The cost
+	// is an extra alloc/free.
+	p = vim_strsave(theline);
+	if (p == NULL)
+	    goto theend;
+	((char_u **)(newlines->ga_data))[newlines->ga_len++] = p;
+
+	// Add NULL lines for continuation lines, so that the line count is
+	// equal to the index in the growarray.
+	while (sourcing_lnum_off-- > 0)
+	    ((char_u **)(newlines->ga_data))[newlines->ga_len++] = NULL;
+
+	// Check for end of eap->arg.
+	if (line_arg != NULL && *line_arg == NUL)
+	    line_arg = NULL;
+    }
+
+    // Don't define the function when skipping commands or when an error was
+    // detected.
+    if (!eap->skip && !did_emsg)
+	ret = OK;
+
+theend:
+    vim_free(skip_until);
+    vim_free(heredoc_trimmed);
+    need_wait_return |= saved_wait_return;
+    return ret;
+}
+
+/*
+ * Handle the body of a lambda.  *arg points to the "{", process statements
+ * until the matching "}".
+ * When successful "rettv" is set to a funcref.
+ */
+    static int
+lambda_function_body(
+	char_u	    **arg,
+	typval_T    *rettv,
+	evalarg_T   *evalarg,
+	garray_T    *newargs,
+	garray_T    *argtypes,
+	int	    varargs,
+	garray_T    *default_args,
+	char_u	    *ret_type)
+{
+    int		evaluate = evalarg != NULL
+				      && (evalarg->eval_flags & EVAL_EVALUATE);
+    ufunc_T	*ufunc;
+    exarg_T	eap;
+    garray_T	newlines;
+    char_u	*cmdline = NULL;
+    int		ret = FAIL;
+    char_u	*line_to_free = NULL;
+    partial_T	*pt;
+    char_u	*name;
+    int		lnum_save = -1;
+    linenr_T	sourcing_lnum_top = SOURCING_LNUM;
+
+    CLEAR_FIELD(eap);
+    eap.cmdidx = CMD_block;
+    eap.forceit = FALSE;
+    eap.arg = *arg + 1;
+    eap.cmdlinep = &cmdline;
+    eap.skip = !evaluate;
+    if (evalarg->eval_cctx != NULL)
+	fill_exarg_from_cctx(&eap, evalarg->eval_cctx);
+    else
+    {
+	eap.getline = evalarg->eval_getline;
+	eap.cookie = evalarg->eval_cookie;
+    }
+
+    ga_init2(&newlines, (int)sizeof(char_u *), 10);
+    if (get_function_body(&eap, &newlines, NULL, &line_to_free) == FAIL)
+	goto erret;
+    if (cmdline != NULL)
+    {
+	// Something comes after the "}".
+	*arg = eap.nextcmd;
+	if (evalarg->eval_cctx == NULL)
+	{
+	    // Need to keep the line and free it/ later.
+	    vim_free(evalarg->eval_tofree_lambda);
+	    evalarg->eval_tofree_lambda = cmdline;
+	}
+    }
+    else
+	*arg = (char_u *)"";
+
+    name = get_lambda_name();
+    ufunc = alloc_clear(offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
+    if (ufunc == NULL)
+	goto erret;
+    set_ufunc_name(ufunc, name);
+    if (hash_add(&func_hashtab, UF2HIKEY(ufunc)) == FAIL)
+    {
+	vim_free(ufunc);
+	goto erret;
+    }
+    ufunc->uf_refcount = 1;
+    ufunc->uf_args = *newargs;
+    newargs->ga_data = NULL;
+    ufunc->uf_def_args = *default_args;
+    default_args->ga_data = NULL;
+    ufunc->uf_func_type = &t_func_any;
+
+    // error messages are for the first function line
+    lnum_save = SOURCING_LNUM;
+    SOURCING_LNUM = sourcing_lnum_top;
+
+    // parse argument types
+    if (parse_argument_types(ufunc, argtypes, varargs) == FAIL)
+    {
+	SOURCING_LNUM = lnum_save;
+	goto erret;
+    }
+
+    // parse the return type, if any
+    if (parse_return_type(ufunc, ret_type) == FAIL)
+	goto erret;
+
+    pt = ALLOC_CLEAR_ONE(partial_T);
+    if (pt == NULL)
+	goto erret;
+    pt->pt_func = ufunc;
+    pt->pt_refcount = 1;
+
+    ufunc->uf_lines = newlines;
+    newlines.ga_data = NULL;
+    if (sandbox)
+	ufunc->uf_flags |= FC_SANDBOX;
+    if (!ASCII_ISUPPER(*ufunc->uf_name))
+	ufunc->uf_flags |= FC_VIM9;
+    ufunc->uf_script_ctx = current_sctx;
+    ufunc->uf_script_ctx_version = current_sctx.sc_version;
+    ufunc->uf_script_ctx.sc_lnum += sourcing_lnum_top;
+    set_function_type(ufunc);
+
+    rettv->vval.v_partial = pt;
+    rettv->v_type = VAR_PARTIAL;
+    ret = OK;
+
+erret:
+    if (lnum_save >= 0)
+	SOURCING_LNUM = lnum_save;
+    vim_free(line_to_free);
+    ga_clear_strings(&newlines);
+    ga_clear_strings(newargs);
+    ga_clear_strings(default_args);
+    return ret;
+}
+
+/*
+ * Parse a lambda expression and get a Funcref from "*arg" into "rettv".
  * "arg" points to the { in "{arg -> expr}" or the ( in "(arg) => expr"
  * When "types_optional" is TRUE optionally take argument types.
  * Return OK or FAIL.  Returns NOTDONE for dict or {expr}.
@@ -554,6 +1067,7 @@ get_lambda_tv(
     garray_T	newlines;
     garray_T	*pnewargs;
     garray_T	argtypes;
+    garray_T	default_args;
     ufunc_T	*fp = NULL;
     partial_T   *pt = NULL;
     int		varargs;
@@ -596,7 +1110,8 @@ get_lambda_tv(
     *arg += 1;
     ret = get_function_args(arg, equal_arrow ? ')' : '-', pnewargs,
 	    types_optional ? &argtypes : NULL, types_optional, evalarg,
-					    &varargs, NULL, FALSE, NULL, NULL);
+					    &varargs, &default_args,
+					    FALSE, NULL, NULL);
     if (ret == FAIL
 		  || (s = skip_arrow(*arg, equal_arrow, &ret_type,
 		equal_arrow || in_vim9script() ? &white_error : NULL)) == NULL)
@@ -624,9 +1139,15 @@ get_lambda_tv(
     // Recognize "{" as the start of a function body.
     if (equal_arrow && **arg == '{')
     {
-	// TODO: process the function body upto the "}".
-	// Return type is required then.
-	emsg("Lambda function body not supported yet");
+	if (lambda_function_body(arg, rettv, evalarg, pnewargs,
+			   types_optional ? &argtypes : NULL, varargs,
+			   &default_args, ret_type) == FAIL)
+	    goto errret;
+	goto theend;
+    }
+    if (default_args.ga_len > 0)
+    {
+	emsg(_(e_cannot_use_default_values_in_lambda));
 	goto errret;
     }
 
@@ -732,6 +1253,7 @@ get_lambda_tv(
 	hash_add(&func_hashtab, UF2HIKEY(fp));
     }
 
+theend:
     eval_lavars_used = old_eval_lavars;
     if (evalarg != NULL && evalarg->eval_tofree == NULL)
 	evalarg->eval_tofree = tofree1;
@@ -745,6 +1267,7 @@ get_lambda_tv(
 errret:
     ga_clear_strings(&newargs);
     ga_clear_strings(&newlines);
+    ga_clear_strings(&default_args);
     if (types_optional)
 	ga_clear_strings(&argtypes);
     vim_free(fp);
@@ -2459,7 +2982,7 @@ call_func(
     {
 	// Check that the argument types are OK for the types of the funcref.
 	if (check_argument_types(funcexe->check_type, argvars, argcount,
-								 name) == FAIL)
+				     (name != NULL) ? name : funcname) == FAIL)
 	    error = FCERR_OTHER;
     }
 
@@ -3006,27 +3529,6 @@ list_functions(regmatch_T *regmatch)
 }
 
 /*
- * Check if "*cmd" points to a function command and if so advance "*cmd" and
- * return TRUE.
- * Otherwise return FALSE;
- * Do not consider "function(" to be a command.
- */
-    static int
-is_function_cmd(char_u **cmd)
-{
-    char_u *p = *cmd;
-
-    if (checkforcmd(&p, "function", 2))
-    {
-	if (*p == '(')
-	    return FALSE;
-	*cmd = p;
-	return TRUE;
-    }
-    return FALSE;
-}
-
-/*
  * ":function" also supporting nested ":def".
  * When "name_arg" is not NULL this is a nested function, using "name_arg" for
  * the function name.
@@ -3035,12 +3537,10 @@ is_function_cmd(char_u **cmd)
     ufunc_T *
 define_function(exarg_T *eap, char_u *name_arg)
 {
-    char_u	*theline;
     char_u	*line_to_free = NULL;
     int		j;
     int		c;
     int		saved_did_emsg;
-    int		saved_wait_return = need_wait_return;
     char_u	*name = name_arg;
     int		is_global = FALSE;
     char_u	*p;
@@ -3056,21 +3556,12 @@ define_function(exarg_T *eap, char_u *na
     char_u	*ret_type = NULL;
     ufunc_T	*fp = NULL;
     int		overwrite = FALSE;
-    int		indent;
-    int		nesting;
-#define MAX_FUNC_NESTING 50
-    char	nesting_def[MAX_FUNC_NESTING];
     dictitem_T	*v;
     funcdict_T	fudi;
     static int	func_nr = 0;	    // number for nameless function
     int		paren;
     hashitem_T	*hi;
-    getline_opt_T getline_options;
-    linenr_T	sourcing_lnum_off;
     linenr_T	sourcing_lnum_top;
-    int		is_heredoc = FALSE;
-    char_u	*skip_until = NULL;
-    char_u	*heredoc_trimmed = NULL;
     int		vim9script = in_vim9script();
     imported_T	*import = NULL;
 
@@ -3263,7 +3754,7 @@ define_function(exarg_T *eap, char_u *na
 	goto ret_free;
     }
 
-    ga_init2(&newlines, (int)sizeof(char_u *), 3);
+    ga_init2(&newlines, (int)sizeof(char_u *), 10);
 
     if (!eap->skip && name_arg == NULL)
     {
@@ -3399,309 +3890,7 @@ define_function(exarg_T *eap, char_u *na
     // Save the starting line number.
     sourcing_lnum_top = SOURCING_LNUM;
 
-    // Detect having skipped over comment lines to find the return
-    // type.  Add NULL lines to keep the line count correct.
-    sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie);
-    if (SOURCING_LNUM < sourcing_lnum_off)
-    {
-	sourcing_lnum_off -= SOURCING_LNUM;
-	if (ga_grow(&newlines, sourcing_lnum_off) == FAIL)
-	    goto erret;
-	while (sourcing_lnum_off-- > 0)
-	    ((char_u **)(newlines.ga_data))[newlines.ga_len++] = NULL;
-    }
-
-    indent = 2;
-    nesting = 0;
-    nesting_def[nesting] = (eap->cmdidx == CMD_def);
-    getline_options = eap->cmdidx == CMD_def
-				? GETLINE_CONCAT_CONTBAR : GETLINE_CONCAT_CONT;
-    for (;;)
-    {
-	if (KeyTyped)
-	{
-	    msg_scroll = TRUE;
-	    saved_wait_return = FALSE;
-	}
-	need_wait_return = FALSE;
-
-	if (line_arg != NULL)
-	{
-	    // Use eap->arg, split up in parts by line breaks.
-	    theline = line_arg;
-	    p = vim_strchr(theline, '\n');
-	    if (p == NULL)
-		line_arg += STRLEN(line_arg);
-	    else
-	    {
-		*p = NUL;
-		line_arg = p + 1;
-	    }
-	}
-	else
-	{
-	    vim_free(line_to_free);
-	    if (eap->getline == NULL)
-		theline = getcmdline(':', 0L, indent, getline_options);
-	    else
-		theline = eap->getline(':', eap->cookie, indent,
-							      getline_options);
-	    line_to_free = theline;
-	}
-	if (KeyTyped)
-	    lines_left = Rows - 1;
-	if (theline == NULL)
-	{
-	    // Use the start of the function for the line number.
-	    SOURCING_LNUM = sourcing_lnum_top;
-	    if (skip_until != NULL)
-		semsg(_(e_missing_heredoc_end_marker_str), skip_until);
-	    else if (eap->cmdidx == CMD_def)
-		emsg(_(e_missing_enddef));
-	    else
-		emsg(_("E126: Missing :endfunction"));
-	    goto erret;
-	}
-
-	// Detect line continuation: SOURCING_LNUM increased more than one.
-	sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie);
-	if (SOURCING_LNUM < sourcing_lnum_off)
-	    sourcing_lnum_off -= SOURCING_LNUM;
-	else
-	    sourcing_lnum_off = 0;
-
-	if (skip_until != NULL)
-	{
-	    // Don't check for ":endfunc"/":enddef" between
-	    // * ":append" and "."
-	    // * ":python <<EOF" and "EOF"
-	    // * ":let {var-name} =<< [trim] {marker}" and "{marker}"
-	    if (heredoc_trimmed == NULL
-		    || (is_heredoc && skipwhite(theline) == theline)
-		    || STRNCMP(theline, heredoc_trimmed,
-						 STRLEN(heredoc_trimmed)) == 0)
-	    {
-		if (heredoc_trimmed == NULL)
-		    p = theline;
-		else if (is_heredoc)
-		    p = skipwhite(theline) == theline
-				 ? theline : theline + STRLEN(heredoc_trimmed);
-		else
-		    p = theline + STRLEN(heredoc_trimmed);
-		if (STRCMP(p, skip_until) == 0)
-		{
-		    VIM_CLEAR(skip_until);
-		    VIM_CLEAR(heredoc_trimmed);
-		    getline_options = eap->cmdidx == CMD_def
-				? GETLINE_CONCAT_CONTBAR : GETLINE_CONCAT_CONT;
-		    is_heredoc = FALSE;
-		}
-	    }
-	}
-	else
-	{
-	    // skip ':' and blanks
-	    for (p = theline; VIM_ISWHITE(*p) || *p == ':'; ++p)
-		;
-
-	    // Check for "endfunction" or "enddef".
-	    // When a ":" follows it must be a dict key; "enddef: value,"
-	    if (checkforcmd(&p, nesting_def[nesting]
-						? "enddef" : "endfunction", 4)
-		    && *p != ':')
-	    {
-		if (nesting-- == 0)
-		{
-		    char_u *nextcmd = NULL;
-
-		    if (*p == '|')
-			nextcmd = p + 1;
-		    else if (line_arg != NULL && *skipwhite(line_arg) != NUL)
-			nextcmd = line_arg;
-		    else if (*p != NUL && *p != '"' && p_verbose > 0)
-			give_warning2(eap->cmdidx == CMD_def
-			    ? (char_u *)_("W1001: Text found after :enddef: %s")
-			    : (char_u *)_("W22: Text found after :endfunction: %s"),
-			     p, TRUE);
-		    if (nextcmd != NULL)
-		    {
-			// Another command follows. If the line came from "eap"
-			// we can simply point into it, otherwise we need to
-			// change "eap->cmdlinep".
-			eap->nextcmd = nextcmd;
-			if (line_to_free != NULL)
-			{
-			    vim_free(*eap->cmdlinep);
-			    *eap->cmdlinep = line_to_free;
-			    line_to_free = NULL;
-			}
-		    }
-		    break;
-		}
-	    }
-
-	    // Check for mismatched "endfunc" or "enddef".
-	    // We don't check for "def" inside "func" thus we also can't check
-	    // for "enddef".
-	    // We continue to find the end of the function, although we might
-	    // not find it.
-	    else if (nesting_def[nesting])
-	    {
-		if (checkforcmd(&p, "endfunction", 4) && *p != ':')
-		    emsg(_(e_mismatched_endfunction));
-	    }
-	    else if (eap->cmdidx == CMD_def && checkforcmd(&p, "enddef", 4))
-		emsg(_(e_mismatched_enddef));
-
-	    // Increase indent inside "if", "while", "for" and "try", decrease
-	    // at "end".
-	    if (indent > 2 && (*p == '}' || STRNCMP(p, "end", 3) == 0))
-		indent -= 2;
-	    else if (STRNCMP(p, "if", 2) == 0
-		    || STRNCMP(p, "wh", 2) == 0
-		    || STRNCMP(p, "for", 3) == 0
-		    || STRNCMP(p, "try", 3) == 0)
-		indent += 2;
-
-	    // Check for defining a function inside this function.
-	    // Only recognize "def" inside "def", not inside "function",
-	    // For backwards compatibility, see Test_function_python().
-	    c = *p;
-	    if (is_function_cmd(&p)
-		    || (eap->cmdidx == CMD_def && checkforcmd(&p, "def", 3)))
-	    {
-		if (*p == '!')
-		    p = skipwhite(p + 1);
-		p += eval_fname_script(p);
-		vim_free(trans_function_name(&p, NULL, TRUE, 0, NULL,
-								  NULL, NULL));
-		if (*skipwhite(p) == '(')
-		{
-		    if (nesting == MAX_FUNC_NESTING - 1)
-			emsg(_(e_function_nesting_too_deep));
-		    else
-		    {
-			++nesting;
-			nesting_def[nesting] = (c == 'd');
-			indent += 2;
-		    }
-		}
-	    }
-
-	    // Check for ":append", ":change", ":insert".  Not for :def.
-	    p = skip_range(p, FALSE, NULL);
-	    if (eap->cmdidx != CMD_def
-		&& ((p[0] == 'a' && (!ASCII_ISALPHA(p[1]) || p[1] == 'p'))
-		    || (p[0] == 'c'
-			&& (!ASCII_ISALPHA(p[1]) || (p[1] == 'h'
-				&& (!ASCII_ISALPHA(p[2]) || (p[2] == 'a'
-					&& (STRNCMP(&p[3], "nge", 3) != 0
-					    || !ASCII_ISALPHA(p[6])))))))
-		    || (p[0] == 'i'
-			&& (!ASCII_ISALPHA(p[1]) || (p[1] == 'n'
-				&& (!ASCII_ISALPHA(p[2])
-				    || (p[2] == 's'
-					&& (!ASCII_ISALPHA(p[3])
-						|| p[3] == 'e'))))))))
-		skip_until = vim_strsave((char_u *)".");
-
-	    // Check for ":python <<EOF", ":tcl <<EOF", etc.
-	    arg = skipwhite(skiptowhite(p));
-	    if (arg[0] == '<' && arg[1] =='<'
-		    && ((p[0] == 'p' && p[1] == 'y'
-				    && (!ASCII_ISALNUM(p[2]) || p[2] == 't'
-					|| ((p[2] == '3' || p[2] == 'x')
-						   && !ASCII_ISALPHA(p[3]))))
-			|| (p[0] == 'p' && p[1] == 'e'
-				    && (!ASCII_ISALPHA(p[2]) || p[2] == 'r'))
-			|| (p[0] == 't' && p[1] == 'c'
-				    && (!ASCII_ISALPHA(p[2]) || p[2] == 'l'))
-			|| (p[0] == 'l' && p[1] == 'u' && p[2] == 'a'
-				    && !ASCII_ISALPHA(p[3]))
-			|| (p[0] == 'r' && p[1] == 'u' && p[2] == 'b'
-				    && (!ASCII_ISALPHA(p[3]) || p[3] == 'y'))
-			|| (p[0] == 'm' && p[1] == 'z'
-				    && (!ASCII_ISALPHA(p[2]) || p[2] == 's'))
-			))
-	    {
-		// ":python <<" continues until a dot, like ":append"
-		p = skipwhite(arg + 2);
-		if (STRNCMP(p, "trim", 4) == 0)
-		{
-		    // Ignore leading white space.
-		    p = skipwhite(p + 4);
-		    heredoc_trimmed = vim_strnsave(theline,
-						 skipwhite(theline) - theline);
-		}
-		if (*p == NUL)
-		    skip_until = vim_strsave((char_u *)".");
-		else
-		    skip_until = vim_strnsave(p, skiptowhite(p) - p);
-		getline_options = GETLINE_NONE;
-		is_heredoc = TRUE;
-	    }
-
-	    // Check for ":cmd v =<< [trim] EOF"
-	    //       and ":cmd [a, b] =<< [trim] EOF"
-	    //       and "lines =<< [trim] EOF" for Vim9
-	    // Where "cmd" can be "let", "var", "final" or "const".
-	    arg = skipwhite(skiptowhite(p));
-	    if (*arg == '[')
-		arg = vim_strchr(arg, ']');
-	    if (arg != NULL)
-	    {
-		int found = (eap->cmdidx == CMD_def && arg[0] == '='
-					     && arg[1] == '<' && arg[2] =='<');
-
-		if (!found)
-		    // skip over the argument after "cmd"
-		    arg = skipwhite(skiptowhite(arg));
-		if (found || (arg[0] == '=' && arg[1] == '<' && arg[2] =='<'
-			&& (checkforcmd(&p, "let", 2)
-			    || checkforcmd(&p, "var", 3)
-			    || checkforcmd(&p, "final", 5)
-			    || checkforcmd(&p, "const", 5))))
-		{
-		    p = skipwhite(arg + 3);
-		    if (STRNCMP(p, "trim", 4) == 0)
-		    {
-			// Ignore leading white space.
-			p = skipwhite(p + 4);
-			heredoc_trimmed = vim_strnsave(theline,
-						 skipwhite(theline) - theline);
-		    }
-		    skip_until = vim_strnsave(p, skiptowhite(p) - p);
-		    getline_options = GETLINE_NONE;
-		    is_heredoc = TRUE;
-		}
-	    }
-	}
-
-	// Add the line to the function.
-	if (ga_grow(&newlines, 1 + sourcing_lnum_off) == FAIL)
-	    goto erret;
-
-	// Copy the line to newly allocated memory.  get_one_sourceline()
-	// allocates 250 bytes per line, this saves 80% on average.  The cost
-	// is an extra alloc/free.
-	p = vim_strsave(theline);
-	if (p == NULL)
-	    goto erret;
-	((char_u **)(newlines.ga_data))[newlines.ga_len++] = p;
-
-	// Add NULL lines for continuation lines, so that the line count is
-	// equal to the index in the growarray.
-	while (sourcing_lnum_off-- > 0)
-	    ((char_u **)(newlines.ga_data))[newlines.ga_len++] = NULL;
-
-	// Check for end of eap->arg.
-	if (line_arg != NULL && *line_arg == NUL)
-	    line_arg = NULL;
-    }
-
-    // Don't define the function when skipping commands or when an error was
-    // detected.
-    if (eap->skip || did_emsg)
+    if (get_function_body(eap, &newlines, line_arg, &line_to_free) == FAIL)
 	goto erret;
 
     /*
@@ -3933,18 +4122,10 @@ define_function(exarg_T *eap, char_u *na
 	varargs = FALSE;
 
 	// parse the return type, if any
-	if (ret_type == NULL)
-	    fp->uf_ret_type = &t_void;
-	else
+	if (parse_return_type(fp, ret_type) == FAIL)
 	{
-	    p = ret_type;
-	    fp->uf_ret_type = parse_type(&p, &fp->uf_type_list, TRUE);
-	    if (fp->uf_ret_type == NULL)
-	    {
-		fp->uf_ret_type = &t_void;
-		SOURCING_LNUM = lnum_save;
-		goto erret;
-	    }
+	    SOURCING_LNUM = lnum_save;
+	    goto erret;
 	}
 	SOURCING_LNUM = lnum_save;
     }
@@ -4004,15 +4185,12 @@ errret_2:
 	VIM_CLEAR(fp->uf_arg_types);
 ret_free:
     ga_clear_strings(&argtypes);
-    vim_free(skip_until);
-    vim_free(heredoc_trimmed);
     vim_free(line_to_free);
     vim_free(fudi.fd_newkey);
     if (name != name_arg)
 	vim_free(name);
     vim_free(ret_type);
     did_emsg |= saved_did_emsg;
-    need_wait_return |= saved_wait_return;
 
     return fp;
 }
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    2635,
+/**/
     2634,
 /**/
     2633,
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -3171,7 +3171,7 @@ compile_list(char_u **arg, cctx_T *cctx,
 
 /*
  * Parse a lambda: "(arg, arg) => expr"
- * "*arg" points to the '{'.
+ * "*arg" points to the '('.
  * Returns OK/FAIL when a lambda is recognized, NOTDONE if it's not a lambda.
  */
     static int
@@ -5126,6 +5126,13 @@ exarg_getline(
     }
 }
 
+    void
+fill_exarg_from_cctx(exarg_T *eap, cctx_T *cctx)
+{
+    eap->getline = exarg_getline;
+    eap->cookie = cctx;
+}
+
 /*
  * Compile a nested :def command.
  */
@@ -5176,9 +5183,8 @@ compile_nested_function(exarg_T *eap, cc
 	return NULL;
 
     eap->arg = name_end;
-    eap->getline = exarg_getline;
-    eap->cookie = cctx;
-    eap->skip = cctx->ctx_skip == SKIP_YES;
+    fill_exarg_from_cctx(eap, cctx);
+
     eap->forceit = FALSE;
     lambda_name = vim_strsave(get_lambda_name());
     if (lambda_name == NULL)