changeset 35032:eeab9c4c801b v9.1.0367

patch 9.1.0367: compile_def_function is too long Commit: https://github.com/vim/vim/commit/a16f251333e324c94ca8e3e92d1fcf3193dfa382 Author: Yegappan Lakshmanan <yegappan@yahoo.com> Date: Tue Apr 23 20:14:46 2024 +0200 patch 9.1.0367: compile_def_function is too long Problem: compile_def_function is too long Solution: Move out the code to compile the body of a function (Yegappan Lakshmanan) closes: #14622 Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
author Christian Brabandt <cb@256bit.org>
date Tue, 23 Apr 2024 20:30:09 +0200
parents 70d96966e62a
children cd559abb232a
files src/testdir/test_vim9_builtin.vim src/version.c src/vim9compile.c src/vim9type.c
diffstat 4 files changed, 575 insertions(+), 557 deletions(-) [+]
line wrap: on
line diff
--- a/src/testdir/test_vim9_builtin.vim
+++ b/src/testdir/test_vim9_builtin.vim
@@ -4785,6 +4785,8 @@ def Test_typename()
   endif
   assert_equal('class<Unknown>', typename(null_class))
   assert_equal('object<Unknown>', typename(null_object))
+  var l: list<func(list<number>): number> = [function('min')]
+  assert_equal('list<func(list<number>): number>', typename(l))
 enddef
 
 def Test_undofile()
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    367,
+/**/
     366,
 /**/
     365,
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -3271,6 +3271,564 @@ add_def_function(ufunc_T *ufunc)
 }
 
 /*
+ * Compile def function body.  Loop over all the lines in the function and
+ * generate instructions.
+ */
+    static int
+compile_def_function_body(
+    cctx_T	*cctx,
+    int		last_func_lnum,
+    int		check_return_type,
+    garray_T	*lines_to_free,
+    char	**errormsg)
+{
+    char_u	*line = NULL;
+    char_u	*p;
+    int		did_emsg_before = did_emsg;
+#ifdef FEAT_PROFILE
+    int		prof_lnum = -1;
+#endif
+    int		debug_lnum = -1;
+
+    for (;;)
+    {
+	exarg_T	    ea;
+	int	    starts_with_colon = FALSE;
+	char_u	    *cmd;
+	cmdmod_T    local_cmdmod;
+
+	// Bail out on the first error to avoid a flood of errors and report
+	// the right line number when inside try/catch.
+	if (did_emsg_before != did_emsg)
+	    return FAIL;
+
+	if (line != NULL && *line == '|')
+	    // the line continues after a '|'
+	    ++line;
+	else if (line != NULL && *skipwhite(line) != NUL
+		&& !(*line == '#' && (line == cctx->ctx_line_start
+						    || VIM_ISWHITE(line[-1]))))
+	{
+	    semsg(_(e_trailing_characters_str), line);
+	    return FAIL;
+	}
+	else if (line != NULL && vim9_bad_comment(skipwhite(line)))
+	    return FAIL;
+	else
+	{
+	    line = next_line_from_context(cctx, FALSE);
+	    if (cctx->ctx_lnum >= last_func_lnum)
+	    {
+		// beyond the last line
+#ifdef FEAT_PROFILE
+		if (cctx->ctx_skip != SKIP_YES)
+		    may_generate_prof_end(cctx, prof_lnum);
+#endif
+		break;
+	    }
+	    // Make a copy, splitting off nextcmd and removing trailing spaces
+	    // may change it.
+	    if (line != NULL)
+	    {
+		line = vim_strsave(line);
+		if (ga_add_string(lines_to_free, line) == FAIL)
+		    return FAIL;
+	    }
+	}
+
+	CLEAR_FIELD(ea);
+	ea.cmdlinep = &line;
+	ea.cmd = skipwhite(line);
+	ea.skip = cctx->ctx_skip == SKIP_YES;
+
+	if (*ea.cmd == '#')
+	{
+	    // "#" starts a comment, but "#{" is an error
+	    if (vim9_bad_comment(ea.cmd))
+		return FAIL;
+	    line = (char_u *)"";
+	    continue;
+	}
+
+#ifdef FEAT_PROFILE
+	if (cctx->ctx_compile_type == CT_PROFILE && cctx->ctx_lnum != prof_lnum
+						  && cctx->ctx_skip != SKIP_YES)
+	{
+	    may_generate_prof_end(cctx, prof_lnum);
+
+	    prof_lnum = cctx->ctx_lnum;
+	    generate_instr(cctx, ISN_PROF_START);
+	}
+#endif
+	if (cctx->ctx_compile_type == CT_DEBUG && cctx->ctx_lnum != debug_lnum
+						  && cctx->ctx_skip != SKIP_YES)
+	{
+	    debug_lnum = cctx->ctx_lnum;
+	    generate_instr_debug(cctx);
+	}
+	cctx->ctx_prev_lnum = cctx->ctx_lnum + 1;
+
+	// Some things can be recognized by the first character.
+	switch (*ea.cmd)
+	{
+	    case '}':
+		{
+		    // "}" ends a block scope
+		    scopetype_T stype = cctx->ctx_scope == NULL
+					  ? NO_SCOPE : cctx->ctx_scope->se_type;
+
+		    if (stype == BLOCK_SCOPE)
+		    {
+			compile_endblock(cctx);
+			line = ea.cmd;
+		    }
+		    else
+		    {
+			emsg(_(e_using_rcurly_outside_if_block_scope));
+			return FAIL;
+		    }
+		    if (line != NULL)
+			line = skipwhite(ea.cmd + 1);
+		    continue;
+		}
+
+	    case '{':
+		// "{" starts a block scope
+		// "{'a': 1}->func() is something else
+		if (ends_excmd(*skipwhite(ea.cmd + 1)))
+		{
+		    line = compile_block(ea.cmd, cctx);
+		    continue;
+		}
+		break;
+	}
+
+	/*
+	 * COMMAND MODIFIERS
+	 */
+	cctx->ctx_has_cmdmod = FALSE;
+	if (parse_command_modifiers(&ea, errormsg, &local_cmdmod, FALSE)
+								       == FAIL)
+	    return FAIL;
+	generate_cmdmods(cctx, &local_cmdmod);
+	undo_cmdmod(&local_cmdmod);
+
+	// Check if there was a colon after the last command modifier or before
+	// the current position.
+	for (p = ea.cmd; p >= line; --p)
+	{
+	    if (*p == ':')
+		starts_with_colon = TRUE;
+	    if (p < ea.cmd && !VIM_ISWHITE(*p))
+		break;
+	}
+
+	// Skip ":call" to get to the function name, unless using :legacy
+	p = ea.cmd;
+	if (!(local_cmdmod.cmod_flags & CMOD_LEGACY))
+	{
+	    if (checkforcmd(&ea.cmd, "call", 3))
+	    {
+		if (*ea.cmd == '(')
+		    // not for "call()"
+		    ea.cmd = p;
+		else
+		    ea.cmd = skipwhite(ea.cmd);
+	    }
+
+	    if (!starts_with_colon)
+	    {
+		int	    assign;
+
+		// Check for assignment after command modifiers.
+		assign = may_compile_assignment(&ea, &line, cctx);
+		if (assign == OK)
+		    goto nextline;
+		if (assign == FAIL)
+		    return FAIL;
+	    }
+	}
+
+	/*
+	 * COMMAND after range
+	 * 'text'->func() should not be confused with 'a mark
+	 * 0z1234->func() should not be confused with a zero line number
+	 * "++nr" and "--nr" are eval commands
+	 * in "$ENV->func()" the "$" is not a range
+	 * "123->func()" is a method call
+	 */
+	cmd = ea.cmd;
+	if ((*cmd != '$' || starts_with_colon)
+		&& (starts_with_colon
+		    || !(*cmd == '\''
+			|| (cmd[0] == '0' && cmd[1] == 'z')
+			|| (cmd[0] != NUL && cmd[0] == cmd[1]
+					    && (*cmd == '+' || *cmd == '-'))
+			|| number_method(cmd))))
+	{
+	    ea.cmd = skip_range(ea.cmd, TRUE, NULL);
+	    if (ea.cmd > cmd)
+	    {
+		if (!starts_with_colon
+				   && !(local_cmdmod.cmod_flags & CMOD_LEGACY))
+		{
+		    semsg(_(e_colon_required_before_range_str), cmd);
+		    return FAIL;
+		}
+		ea.addr_count = 1;
+		if (ends_excmd2(line, ea.cmd))
+		{
+		    // A range without a command: jump to the line.
+		    generate_EXEC(cctx, ISN_EXECRANGE,
+					      vim_strnsave(cmd, ea.cmd - cmd));
+		    line = ea.cmd;
+		    goto nextline;
+		}
+	    }
+	}
+	p = find_ex_command(&ea, NULL,
+		starts_with_colon || (local_cmdmod.cmod_flags & CMOD_LEGACY)
+						  ? NULL : item_exists, cctx);
+
+	if (p == NULL)
+	{
+	    if (cctx->ctx_skip != SKIP_YES)
+		semsg(_(e_ambiguous_use_of_user_defined_command_str), ea.cmd);
+	    return FAIL;
+	}
+
+	// When using ":legacy cmd" always use compile_exec().
+	if (local_cmdmod.cmod_flags & CMOD_LEGACY)
+	{
+	    char_u *start = ea.cmd;
+
+	    switch (ea.cmdidx)
+	    {
+		case CMD_if:
+		case CMD_elseif:
+		case CMD_else:
+		case CMD_endif:
+		case CMD_for:
+		case CMD_endfor:
+		case CMD_continue:
+		case CMD_break:
+		case CMD_while:
+		case CMD_endwhile:
+		case CMD_try:
+		case CMD_catch:
+		case CMD_finally:
+		case CMD_endtry:
+			semsg(_(e_cannot_use_legacy_with_command_str), ea.cmd);
+			return FAIL;
+		default: break;
+	    }
+
+	    // ":legacy return expr" needs to be handled differently.
+	    if (checkforcmd(&start, "return", 4))
+		ea.cmdidx = CMD_return;
+	    else
+		ea.cmdidx = CMD_legacy;
+	}
+
+	if (p == ea.cmd && ea.cmdidx != CMD_SIZE)
+	{
+	    // "eval" is used for "val->func()" and "var" for "var = val", then
+	    // "p" is equal to "ea.cmd" for a valid command.
+	    if (ea.cmdidx == CMD_eval || ea.cmdidx == CMD_var)
+		;
+	    else if (cctx->ctx_skip == SKIP_YES)
+	    {
+		line += STRLEN(line);
+		goto nextline;
+	    }
+	    else
+	    {
+		semsg(_(e_command_not_recognized_str), ea.cmd);
+		return FAIL;
+	    }
+	}
+
+	if ((cctx->ctx_had_return || cctx->ctx_had_throw)
+		&& ea.cmdidx != CMD_elseif
+		&& ea.cmdidx != CMD_else
+		&& ea.cmdidx != CMD_endif
+		&& ea.cmdidx != CMD_endfor
+		&& ea.cmdidx != CMD_endwhile
+		&& ea.cmdidx != CMD_catch
+		&& ea.cmdidx != CMD_finally
+		&& ea.cmdidx != CMD_endtry
+		&& !ignore_unreachable_code_for_testing)
+	{
+	    semsg(_(e_unreachable_code_after_str),
+				     cctx->ctx_had_return ? "return" : "throw");
+	    return FAIL;
+	}
+	cctx->ctx_had_throw = FALSE;
+
+	p = skipwhite(p);
+	if (ea.cmdidx != CMD_SIZE
+			    && ea.cmdidx != CMD_write && ea.cmdidx != CMD_read)
+	{
+	    if (ea.cmdidx >= 0)
+		ea.argt = excmd_get_argt(ea.cmdidx);
+	    if ((ea.argt & EX_BANG) && *p == '!')
+	    {
+		ea.forceit = TRUE;
+		p = skipwhite(p + 1);
+	    }
+	    if ((ea.argt & EX_RANGE) == 0 && ea.addr_count > 0)
+	    {
+		emsg(_(e_no_range_allowed));
+		return FAIL;
+	    }
+	}
+
+	switch (ea.cmdidx)
+	{
+	    case CMD_def:
+	    case CMD_function:
+		    ea.arg = p;
+		    line = compile_nested_function(&ea, cctx, lines_to_free);
+		    break;
+
+	    case CMD_return:
+		    line = compile_return(p, check_return_type,
+				 local_cmdmod.cmod_flags & CMOD_LEGACY, cctx);
+		    cctx->ctx_had_return = TRUE;
+		    break;
+
+	    case CMD_let:
+		    emsg(_(e_cannot_use_let_in_vim9_script));
+		    break;
+	    case CMD_var:
+	    case CMD_final:
+	    case CMD_const:
+	    case CMD_increment:
+	    case CMD_decrement:
+		    line = compile_assignment(p, &ea, ea.cmdidx, cctx);
+		    if (line == p)
+		    {
+			emsg(_(e_invalid_assignment));
+			line = NULL;
+		    }
+		    break;
+
+	    case CMD_unlet:
+	    case CMD_unlockvar:
+	    case CMD_lockvar:
+		    line = compile_unletlock(p, &ea, cctx);
+		    break;
+
+	    case CMD_import:
+		    emsg(_(e_import_can_only_be_used_in_script));
+		    line = NULL;
+		    break;
+
+	    case CMD_if:
+		    line = compile_if(p, cctx);
+		    break;
+	    case CMD_elseif:
+		    line = compile_elseif(p, cctx);
+		    cctx->ctx_had_return = FALSE;
+		    break;
+	    case CMD_else:
+		    line = compile_else(p, cctx);
+		    cctx->ctx_had_return = FALSE;
+		    break;
+	    case CMD_endif:
+		    line = compile_endif(p, cctx);
+		    break;
+
+	    case CMD_while:
+		    line = compile_while(p, cctx);
+		    break;
+	    case CMD_endwhile:
+		    line = compile_endwhile(p, cctx);
+		    cctx->ctx_had_return = FALSE;
+		    break;
+
+	    case CMD_for:
+		    line = compile_for(p, cctx);
+		    break;
+	    case CMD_endfor:
+		    line = compile_endfor(p, cctx);
+		    cctx->ctx_had_return = FALSE;
+		    break;
+	    case CMD_continue:
+		    line = compile_continue(p, cctx);
+		    break;
+	    case CMD_break:
+		    line = compile_break(p, cctx);
+		    break;
+
+	    case CMD_try:
+		    line = compile_try(p, cctx);
+		    break;
+	    case CMD_catch:
+		    line = compile_catch(p, cctx);
+		    cctx->ctx_had_return = FALSE;
+		    break;
+	    case CMD_finally:
+		    line = compile_finally(p, cctx);
+		    cctx->ctx_had_return = FALSE;
+		    break;
+	    case CMD_endtry:
+		    line = compile_endtry(p, cctx);
+		    break;
+	    case CMD_throw:
+		    line = compile_throw(p, cctx);
+		    cctx->ctx_had_throw = TRUE;
+		    break;
+
+	    case CMD_eval:
+		    line = compile_eval(p, cctx);
+		    break;
+
+	    case CMD_defer:
+		    line = compile_defer(p, cctx);
+		    break;
+
+#ifdef HAS_MESSAGE_WINDOW
+	    case CMD_echowindow:
+		    {
+			long cmd_count = get_cmd_count(line, &ea);
+			if (cmd_count < 0)
+			    line = NULL;
+			else
+			    line = compile_mult_expr(p, ea.cmdidx,
+							     cmd_count, cctx);
+		    }
+		    break;
+#endif
+	    case CMD_echo:
+	    case CMD_echon:
+	    case CMD_echoconsole:
+	    case CMD_echoerr:
+	    case CMD_echomsg:
+	    case CMD_execute:
+		    line = compile_mult_expr(p, ea.cmdidx, 0, cctx);
+		    break;
+
+	    case CMD_put:
+		    ea.cmd = cmd;
+		    line = compile_put(p, &ea, cctx);
+		    break;
+
+	    case CMD_substitute:
+		    if (check_global_and_subst(ea.cmd, p) == FAIL)
+			return FAIL;
+		    if (cctx->ctx_skip == SKIP_YES)
+			line = (char_u *)"";
+		    else
+		    {
+			ea.arg = p;
+			line = compile_substitute(line, &ea, cctx);
+		    }
+		    break;
+
+	    case CMD_redir:
+		    ea.arg = p;
+		    line = compile_redir(line, &ea, cctx);
+		    break;
+
+	    case CMD_cexpr:
+	    case CMD_lexpr:
+	    case CMD_caddexpr:
+	    case CMD_laddexpr:
+	    case CMD_cgetexpr:
+	    case CMD_lgetexpr:
+#ifdef FEAT_QUICKFIX
+		    ea.arg = p;
+		    line = compile_cexpr(line, &ea, cctx);
+#else
+		    ex_ni(&ea);
+		    line = NULL;
+#endif
+		    break;
+
+	    case CMD_append:
+	    case CMD_change:
+	    case CMD_insert:
+	    case CMD_k:
+	    case CMD_t:
+	    case CMD_xit:
+		    not_in_vim9(&ea);
+		    return FAIL;
+
+	    case CMD_SIZE:
+		    if (cctx->ctx_skip != SKIP_YES)
+		    {
+			semsg(_(e_invalid_command_str), ea.cmd);
+			return FAIL;
+		    }
+		    // We don't check for a next command here.
+		    line = (char_u *)"";
+		    break;
+
+	    case CMD_lua:
+	    case CMD_mzscheme:
+	    case CMD_perl:
+	    case CMD_py3:
+	    case CMD_python3:
+	    case CMD_python:
+	    case CMD_pythonx:
+	    case CMD_ruby:
+	    case CMD_tcl:
+		    ea.arg = p;
+		    if (vim_strchr(line, '\n') == NULL)
+			line = compile_exec(line, &ea, cctx);
+		    else
+			// heredoc lines have been concatenated with NL
+			// characters in get_function_body()
+			line = compile_script(line, cctx);
+		    break;
+
+	    case CMD_vim9script:
+		    if (cctx->ctx_skip != SKIP_YES)
+		    {
+			emsg(_(e_vim9script_can_only_be_used_in_script));
+			return FAIL;
+		    }
+		    line = (char_u *)"";
+		    break;
+
+	    case CMD_class:
+		    emsg(_(e_class_can_only_be_used_in_script));
+		    return FAIL;
+
+	    case CMD_type:
+		    emsg(_(e_type_can_only_be_used_in_script));
+		    return FAIL;
+
+	    case CMD_global:
+		    if (check_global_and_subst(ea.cmd, p) == FAIL)
+			return FAIL;
+		    // FALLTHROUGH
+	    default:
+		    // Not recognized, execute with do_cmdline_cmd().
+		    ea.arg = p;
+		    line = compile_exec(line, &ea, cctx);
+		    break;
+	}
+nextline:
+	if (line == NULL)
+	    return FAIL;
+	line = skipwhite(line);
+
+	// Undo any command modifiers.
+	generate_undo_cmdmods(cctx);
+
+	if (cctx->ctx_type_stack.ga_len < 0)
+	{
+	    iemsg("Type stack underflow");
+	    return FAIL;
+	}
+    } // END of the loop over all the function body lines.
+
+    return OK;
+}
+
+/*
  * After ex_function() has collected all the function lines: parse and compile
  * the lines into instructions.
  * Adds the function to "def_functions".
@@ -3290,9 +3848,7 @@ compile_def_function(
 	compiletype_T   compile_type,
 	cctx_T		*outer_cctx)
 {
-    char_u	*line = NULL;
     garray_T	lines_to_free;
-    char_u	*p;
     char	*errormsg = NULL;	// error message
     cctx_T	cctx;
     garray_T	*instr;
@@ -3304,10 +3860,6 @@ compile_def_function(
     int		save_cmod_flags = cmdmod.cmod_flags;
     int		do_estack_push;
     int		new_def_function = FALSE;
-#ifdef FEAT_PROFILE
-    int		prof_lnum = -1;
-#endif
-    int		debug_lnum = -1;
 
     // allocated lines are freed at the end
     ga_init2(&lines_to_free, sizeof(char_u *), 50);
@@ -3523,543 +4075,9 @@ compile_def_function(
 	goto erret;
     }
 
-    /*
-     * Loop over all the lines of the function and generate instructions.
-     */
-    for (;;)
-    {
-	exarg_T	    ea;
-	int	    starts_with_colon = FALSE;
-	char_u	    *cmd;
-	cmdmod_T    local_cmdmod;
-
-	// Bail out on the first error to avoid a flood of errors and report
-	// the right line number when inside try/catch.
-	if (did_emsg_before != did_emsg)
-	    goto erret;
-
-	if (line != NULL && *line == '|')
-	    // the line continues after a '|'
-	    ++line;
-	else if (line != NULL && *skipwhite(line) != NUL
-		&& !(*line == '#' && (line == cctx.ctx_line_start
-						    || VIM_ISWHITE(line[-1]))))
-	{
-	    semsg(_(e_trailing_characters_str), line);
-	    goto erret;
-	}
-	else if (line != NULL && vim9_bad_comment(skipwhite(line)))
-	    goto erret;
-	else
-	{
-	    line = next_line_from_context(&cctx, FALSE);
-	    if (cctx.ctx_lnum >= ufunc->uf_lines.ga_len)
-	    {
-		// beyond the last line
-#ifdef FEAT_PROFILE
-		if (cctx.ctx_skip != SKIP_YES)
-		    may_generate_prof_end(&cctx, prof_lnum);
-#endif
-		break;
-	    }
-	    // Make a copy, splitting off nextcmd and removing trailing spaces
-	    // may change it.
-	    if (line != NULL)
-	    {
-		line = vim_strsave(line);
-		if (ga_add_string(&lines_to_free, line) == FAIL)
-		    goto erret;
-	    }
-	}
-
-	CLEAR_FIELD(ea);
-	ea.cmdlinep = &line;
-	ea.cmd = skipwhite(line);
-	ea.skip = cctx.ctx_skip == SKIP_YES;
-
-	if (*ea.cmd == '#')
-	{
-	    // "#" starts a comment, but "#{" is an error
-	    if (vim9_bad_comment(ea.cmd))
-		goto erret;
-	    line = (char_u *)"";
-	    continue;
-	}
-
-#ifdef FEAT_PROFILE
-	if (cctx.ctx_compile_type == CT_PROFILE && cctx.ctx_lnum != prof_lnum
-						  && cctx.ctx_skip != SKIP_YES)
-	{
-	    may_generate_prof_end(&cctx, prof_lnum);
-
-	    prof_lnum = cctx.ctx_lnum;
-	    generate_instr(&cctx, ISN_PROF_START);
-	}
-#endif
-	if (cctx.ctx_compile_type == CT_DEBUG && cctx.ctx_lnum != debug_lnum
-						  && cctx.ctx_skip != SKIP_YES)
-	{
-	    debug_lnum = cctx.ctx_lnum;
-	    generate_instr_debug(&cctx);
-	}
-	cctx.ctx_prev_lnum = cctx.ctx_lnum + 1;
-
-	// Some things can be recognized by the first character.
-	switch (*ea.cmd)
-	{
-	    case '}':
-		{
-		    // "}" ends a block scope
-		    scopetype_T stype = cctx.ctx_scope == NULL
-					  ? NO_SCOPE : cctx.ctx_scope->se_type;
-
-		    if (stype == BLOCK_SCOPE)
-		    {
-			compile_endblock(&cctx);
-			line = ea.cmd;
-		    }
-		    else
-		    {
-			emsg(_(e_using_rcurly_outside_if_block_scope));
-			goto erret;
-		    }
-		    if (line != NULL)
-			line = skipwhite(ea.cmd + 1);
-		    continue;
-		}
-
-	    case '{':
-		// "{" starts a block scope
-		// "{'a': 1}->func() is something else
-		if (ends_excmd(*skipwhite(ea.cmd + 1)))
-		{
-		    line = compile_block(ea.cmd, &cctx);
-		    continue;
-		}
-		break;
-	}
-
-	/*
-	 * COMMAND MODIFIERS
-	 */
-	cctx.ctx_has_cmdmod = FALSE;
-	if (parse_command_modifiers(&ea, &errormsg, &local_cmdmod, FALSE)
-								       == FAIL)
-	    goto erret;
-	generate_cmdmods(&cctx, &local_cmdmod);
-	undo_cmdmod(&local_cmdmod);
-
-	// Check if there was a colon after the last command modifier or before
-	// the current position.
-	for (p = ea.cmd; p >= line; --p)
-	{
-	    if (*p == ':')
-		starts_with_colon = TRUE;
-	    if (p < ea.cmd && !VIM_ISWHITE(*p))
-		break;
-	}
-
-	// Skip ":call" to get to the function name, unless using :legacy
-	p = ea.cmd;
-	if (!(local_cmdmod.cmod_flags & CMOD_LEGACY))
-	{
-	    if (checkforcmd(&ea.cmd, "call", 3))
-	    {
-		if (*ea.cmd == '(')
-		    // not for "call()"
-		    ea.cmd = p;
-		else
-		    ea.cmd = skipwhite(ea.cmd);
-	    }
-
-	    if (!starts_with_colon)
-	    {
-		int	    assign;
-
-		// Check for assignment after command modifiers.
-		assign = may_compile_assignment(&ea, &line, &cctx);
-		if (assign == OK)
-		    goto nextline;
-		if (assign == FAIL)
-		    goto erret;
-	    }
-	}
-
-	/*
-	 * COMMAND after range
-	 * 'text'->func() should not be confused with 'a mark
-	 * 0z1234->func() should not be confused with a zero line number
-	 * "++nr" and "--nr" are eval commands
-	 * in "$ENV->func()" the "$" is not a range
-	 * "123->func()" is a method call
-	 */
-	cmd = ea.cmd;
-	if ((*cmd != '$' || starts_with_colon)
-		&& (starts_with_colon
-		    || !(*cmd == '\''
-			|| (cmd[0] == '0' && cmd[1] == 'z')
-			|| (cmd[0] != NUL && cmd[0] == cmd[1]
-					    && (*cmd == '+' || *cmd == '-'))
-			|| number_method(cmd))))
-	{
-	    ea.cmd = skip_range(ea.cmd, TRUE, NULL);
-	    if (ea.cmd > cmd)
-	    {
-		if (!starts_with_colon
-				   && !(local_cmdmod.cmod_flags & CMOD_LEGACY))
-		{
-		    semsg(_(e_colon_required_before_range_str), cmd);
-		    goto erret;
-		}
-		ea.addr_count = 1;
-		if (ends_excmd2(line, ea.cmd))
-		{
-		    // A range without a command: jump to the line.
-		    generate_EXEC(&cctx, ISN_EXECRANGE,
-					      vim_strnsave(cmd, ea.cmd - cmd));
-		    line = ea.cmd;
-		    goto nextline;
-		}
-	    }
-	}
-	p = find_ex_command(&ea, NULL,
-		starts_with_colon || (local_cmdmod.cmod_flags & CMOD_LEGACY)
-						  ? NULL : item_exists, &cctx);
-
-	if (p == NULL)
-	{
-	    if (cctx.ctx_skip != SKIP_YES)
-		semsg(_(e_ambiguous_use_of_user_defined_command_str), ea.cmd);
-	    goto erret;
-	}
-
-	// When using ":legacy cmd" always use compile_exec().
-	if (local_cmdmod.cmod_flags & CMOD_LEGACY)
-	{
-	    char_u *start = ea.cmd;
-
-	    switch (ea.cmdidx)
-	    {
-		case CMD_if:
-		case CMD_elseif:
-		case CMD_else:
-		case CMD_endif:
-		case CMD_for:
-		case CMD_endfor:
-		case CMD_continue:
-		case CMD_break:
-		case CMD_while:
-		case CMD_endwhile:
-		case CMD_try:
-		case CMD_catch:
-		case CMD_finally:
-		case CMD_endtry:
-			semsg(_(e_cannot_use_legacy_with_command_str), ea.cmd);
-			goto erret;
-		default: break;
-	    }
-
-	    // ":legacy return expr" needs to be handled differently.
-	    if (checkforcmd(&start, "return", 4))
-		ea.cmdidx = CMD_return;
-	    else
-		ea.cmdidx = CMD_legacy;
-	}
-
-	if (p == ea.cmd && ea.cmdidx != CMD_SIZE)
-	{
-	    // "eval" is used for "val->func()" and "var" for "var = val", then
-	    // "p" is equal to "ea.cmd" for a valid command.
-	    if (ea.cmdidx == CMD_eval || ea.cmdidx == CMD_var)
-		;
-	    else if (cctx.ctx_skip == SKIP_YES)
-	    {
-		line += STRLEN(line);
-		goto nextline;
-	    }
-	    else
-	    {
-		semsg(_(e_command_not_recognized_str), ea.cmd);
-		goto erret;
-	    }
-	}
-
-	if ((cctx.ctx_had_return || cctx.ctx_had_throw)
-		&& ea.cmdidx != CMD_elseif
-		&& ea.cmdidx != CMD_else
-		&& ea.cmdidx != CMD_endif
-		&& ea.cmdidx != CMD_endfor
-		&& ea.cmdidx != CMD_endwhile
-		&& ea.cmdidx != CMD_catch
-		&& ea.cmdidx != CMD_finally
-		&& ea.cmdidx != CMD_endtry
-		&& !ignore_unreachable_code_for_testing)
-	{
-	    semsg(_(e_unreachable_code_after_str),
-				     cctx.ctx_had_return ? "return" : "throw");
-	    goto erret;
-	}
-	cctx.ctx_had_throw = FALSE;
-
-	p = skipwhite(p);
-	if (ea.cmdidx != CMD_SIZE
-			    && ea.cmdidx != CMD_write && ea.cmdidx != CMD_read)
-	{
-	    if (ea.cmdidx >= 0)
-		ea.argt = excmd_get_argt(ea.cmdidx);
-	    if ((ea.argt & EX_BANG) && *p == '!')
-	    {
-		ea.forceit = TRUE;
-		p = skipwhite(p + 1);
-	    }
-	    if ((ea.argt & EX_RANGE) == 0 && ea.addr_count > 0)
-	    {
-		emsg(_(e_no_range_allowed));
-		goto erret;
-	    }
-	}
-
-	switch (ea.cmdidx)
-	{
-	    case CMD_def:
-	    case CMD_function:
-		    ea.arg = p;
-		    line = compile_nested_function(&ea, &cctx, &lines_to_free);
-		    break;
-
-	    case CMD_return:
-		    line = compile_return(p, check_return_type,
-				 local_cmdmod.cmod_flags & CMOD_LEGACY, &cctx);
-		    cctx.ctx_had_return = TRUE;
-		    break;
-
-	    case CMD_let:
-		    emsg(_(e_cannot_use_let_in_vim9_script));
-		    break;
-	    case CMD_var:
-	    case CMD_final:
-	    case CMD_const:
-	    case CMD_increment:
-	    case CMD_decrement:
-		    line = compile_assignment(p, &ea, ea.cmdidx, &cctx);
-		    if (line == p)
-		    {
-			emsg(_(e_invalid_assignment));
-			line = NULL;
-		    }
-		    break;
-
-	    case CMD_unlet:
-	    case CMD_unlockvar:
-	    case CMD_lockvar:
-		    line = compile_unletlock(p, &ea, &cctx);
-		    break;
-
-	    case CMD_import:
-		    emsg(_(e_import_can_only_be_used_in_script));
-		    line = NULL;
-		    break;
-
-	    case CMD_if:
-		    line = compile_if(p, &cctx);
-		    break;
-	    case CMD_elseif:
-		    line = compile_elseif(p, &cctx);
-		    cctx.ctx_had_return = FALSE;
-		    break;
-	    case CMD_else:
-		    line = compile_else(p, &cctx);
-		    cctx.ctx_had_return = FALSE;
-		    break;
-	    case CMD_endif:
-		    line = compile_endif(p, &cctx);
-		    break;
-
-	    case CMD_while:
-		    line = compile_while(p, &cctx);
-		    break;
-	    case CMD_endwhile:
-		    line = compile_endwhile(p, &cctx);
-		    cctx.ctx_had_return = FALSE;
-		    break;
-
-	    case CMD_for:
-		    line = compile_for(p, &cctx);
-		    break;
-	    case CMD_endfor:
-		    line = compile_endfor(p, &cctx);
-		    cctx.ctx_had_return = FALSE;
-		    break;
-	    case CMD_continue:
-		    line = compile_continue(p, &cctx);
-		    break;
-	    case CMD_break:
-		    line = compile_break(p, &cctx);
-		    break;
-
-	    case CMD_try:
-		    line = compile_try(p, &cctx);
-		    break;
-	    case CMD_catch:
-		    line = compile_catch(p, &cctx);
-		    cctx.ctx_had_return = FALSE;
-		    break;
-	    case CMD_finally:
-		    line = compile_finally(p, &cctx);
-		    cctx.ctx_had_return = FALSE;
-		    break;
-	    case CMD_endtry:
-		    line = compile_endtry(p, &cctx);
-		    break;
-	    case CMD_throw:
-		    line = compile_throw(p, &cctx);
-		    cctx.ctx_had_throw = TRUE;
-		    break;
-
-	    case CMD_eval:
-		    line = compile_eval(p, &cctx);
-		    break;
-
-	    case CMD_defer:
-		    line = compile_defer(p, &cctx);
-		    break;
-
-#ifdef HAS_MESSAGE_WINDOW
-	    case CMD_echowindow:
-		    {
-			long cmd_count = get_cmd_count(line, &ea);
-			if (cmd_count < 0)
-			    line = NULL;
-			else
-			    line = compile_mult_expr(p, ea.cmdidx,
-							     cmd_count, &cctx);
-		    }
-		    break;
-#endif
-	    case CMD_echo:
-	    case CMD_echon:
-	    case CMD_echoconsole:
-	    case CMD_echoerr:
-	    case CMD_echomsg:
-	    case CMD_execute:
-		    line = compile_mult_expr(p, ea.cmdidx, 0, &cctx);
-		    break;
-
-	    case CMD_put:
-		    ea.cmd = cmd;
-		    line = compile_put(p, &ea, &cctx);
-		    break;
-
-	    case CMD_substitute:
-		    if (check_global_and_subst(ea.cmd, p) == FAIL)
-			goto erret;
-		    if (cctx.ctx_skip == SKIP_YES)
-			line = (char_u *)"";
-		    else
-		    {
-			ea.arg = p;
-			line = compile_substitute(line, &ea, &cctx);
-		    }
-		    break;
-
-	    case CMD_redir:
-		    ea.arg = p;
-		    line = compile_redir(line, &ea, &cctx);
-		    break;
-
-	    case CMD_cexpr:
-	    case CMD_lexpr:
-	    case CMD_caddexpr:
-	    case CMD_laddexpr:
-	    case CMD_cgetexpr:
-	    case CMD_lgetexpr:
-#ifdef FEAT_QUICKFIX
-		    ea.arg = p;
-		    line = compile_cexpr(line, &ea, &cctx);
-#else
-		    ex_ni(&ea);
-		    line = NULL;
-#endif
-		    break;
-
-	    case CMD_append:
-	    case CMD_change:
-	    case CMD_insert:
-	    case CMD_k:
-	    case CMD_t:
-	    case CMD_xit:
-		    not_in_vim9(&ea);
-		    goto erret;
-
-	    case CMD_SIZE:
-		    if (cctx.ctx_skip != SKIP_YES)
-		    {
-			semsg(_(e_invalid_command_str), ea.cmd);
-			goto erret;
-		    }
-		    // We don't check for a next command here.
-		    line = (char_u *)"";
-		    break;
-
-	    case CMD_lua:
-	    case CMD_mzscheme:
-	    case CMD_perl:
-	    case CMD_py3:
-	    case CMD_python3:
-	    case CMD_python:
-	    case CMD_pythonx:
-	    case CMD_ruby:
-	    case CMD_tcl:
-		    ea.arg = p;
-		    if (vim_strchr(line, '\n') == NULL)
-			line = compile_exec(line, &ea, &cctx);
-		    else
-			// heredoc lines have been concatenated with NL
-			// characters in get_function_body()
-			line = compile_script(line, &cctx);
-		    break;
-
-	    case CMD_vim9script:
-		    if (cctx.ctx_skip != SKIP_YES)
-		    {
-			emsg(_(e_vim9script_can_only_be_used_in_script));
-			goto erret;
-		    }
-		    line = (char_u *)"";
-		    break;
-
-	    case CMD_class:
-		    emsg(_(e_class_can_only_be_used_in_script));
-		    goto erret;
-
-	    case CMD_type:
-		    emsg(_(e_type_can_only_be_used_in_script));
-		    goto erret;
-
-	    case CMD_global:
-		    if (check_global_and_subst(ea.cmd, p) == FAIL)
-			goto erret;
-		    // FALLTHROUGH
-	    default:
-		    // Not recognized, execute with do_cmdline_cmd().
-		    ea.arg = p;
-		    line = compile_exec(line, &ea, &cctx);
-		    break;
-	}
-nextline:
-	if (line == NULL)
-	    goto erret;
-	line = skipwhite(line);
-
-	// Undo any command modifiers.
-	generate_undo_cmdmods(&cctx);
-
-	if (cctx.ctx_type_stack.ga_len < 0)
-	{
-	    iemsg("Type stack underflow");
-	    goto erret;
-	}
-    } // END of the loop over all the function body lines.
+    if (compile_def_function_body(&cctx, ufunc->uf_lines.ga_len,
+		check_return_type, &lines_to_free, &errormsg) == FAIL)
+	goto erret;
 
     if (cctx.ctx_scope != NULL)
     {
--- a/src/vim9type.c
+++ b/src/vim9type.c
@@ -1894,14 +1894,12 @@ type_name_list_or_dict(char *name, type_
 
     size_t len = STRLEN(name) + STRLEN(member_name) + 3;
     *tofree = alloc(len);
-    if (*tofree != NULL)
-    {
-	vim_snprintf(*tofree, len, "%s<%s>", name, member_name);
-	vim_free(member_free);
-	return *tofree;
-    }
+    if (*tofree == NULL)
+	return name;
 
-    return name;
+    vim_snprintf(*tofree, len, "%s<%s>", name, member_name);
+    vim_free(member_free);
+    return *tofree;
 }
 
 /*
@@ -1924,17 +1922,15 @@ type_name_class_or_obj(char *name, type_
 
     size_t len = STRLEN(name) + STRLEN(class_name) + 3;
     *tofree = alloc(len);
-    if (*tofree != NULL)
-    {
-	vim_snprintf(*tofree, len, "%s<%s>", name, class_name);
-	return *tofree;
-    }
+    if (*tofree == NULL)
+	return name;
 
-    return name;
+    vim_snprintf(*tofree, len, "%s<%s>", name, class_name);
+    return *tofree;
 }
 
 /*
- * Return the type name of a functio.
+ * Return the type name of a function.
  * The result may be in allocated memory, in which case "tofree" is set.
  */
     static char *