changeset 20339:7587d892c00c v8.2.0725

patch 8.2.0725: Vim9: cannot call a function declared later in Vim9 script Commit: https://github.com/vim/vim/commit/09689a02840be40fa7bb10b1921fb5bc5b2908f1 Author: Bram Moolenaar <Bram@vim.org> Date: Sat May 9 22:50:08 2020 +0200 patch 8.2.0725: Vim9: cannot call a function declared later in Vim9 script Problem: Vim9: cannot call a function declared later in Vim9 script. Solution: Make two passes through the script file.
author Bram Moolenaar <Bram@vim.org>
date Sat, 09 May 2020 23:00:04 +0200
parents ff4ae3f09307
children f36a7f769ba4
files src/evalvars.c src/proto/evalvars.pro src/proto/scriptfile.pro src/proto/userfunc.pro src/proto/vim9compile.pro src/scriptfile.c src/testdir/test_vim9_disassemble.vim src/userfunc.c src/version.c src/vim.h src/vim9compile.c src/vim9execute.c src/vim9script.c
diffstat 13 files changed, 231 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- a/src/evalvars.c
+++ b/src/evalvars.c
@@ -164,7 +164,6 @@ static dict_T		vimvardict;		// Dictionar
 // for VIM_VERSION_ defines
 #include "version.h"
 
-static void ex_let_const(exarg_T *eap, int is_const);
 static char_u *skip_var_one(char_u *arg, int include_type);
 static void list_glob_vars(int *first);
 static void list_buf_vars(int *first);
@@ -698,11 +697,15 @@ ex_let(exarg_T *eap)
     void
 ex_const(exarg_T *eap)
 {
-    ex_let_const(eap, TRUE);
+    ex_let_const(eap, FALSE);
 }
 
-    static void
-ex_let_const(exarg_T *eap, int is_const)
+/*
+ * When "redefine" is TRUE the command will be executed again, redefining the
+ * variable is OK then.
+ */
+    void
+ex_let_const(exarg_T *eap, int redefine)
 {
     char_u	*arg = eap->arg;
     char_u	*expr = NULL;
@@ -714,11 +717,13 @@ ex_let_const(exarg_T *eap, int is_const)
     char_u	*argend;
     int		first = TRUE;
     int		concat;
-    int		flags = is_const ? LET_IS_CONST : 0;
+    int		flags = eap->cmdidx == CMD_const ? LET_IS_CONST : 0;
 
     // detect Vim9 assignment without ":let" or ":const"
     if (eap->arg == eap->cmd)
 	flags |= LET_NO_COMMAND;
+    if (redefine)
+	flags |= LET_REDEFINE;
 
     argend = skip_var_list(arg, TRUE, &var_count, &semicolon);
     if (argend == NULL)
@@ -2976,6 +2981,8 @@ set_var_const(
 
     if (flags & LET_IS_CONST)
 	di->di_tv.v_lock |= VAR_LOCKED;
+    if (flags & LET_REDEFINE)
+	di->di_flags |= DI_FLAGS_RELOAD;
 }
 
 /*
--- a/src/proto/evalvars.pro
+++ b/src/proto/evalvars.pro
@@ -16,6 +16,7 @@ void restore_vimvar(int idx, typval_T *s
 list_T *heredoc_get(exarg_T *eap, char_u *cmd, int script_get);
 void ex_let(exarg_T *eap);
 void ex_const(exarg_T *eap);
+void ex_let_const(exarg_T *eap, int redefine);
 int ex_let_vars(char_u *arg_start, typval_T *tv, int copy, int semicolon, int var_count, int flags, char_u *op);
 char_u *skip_var_list(char_u *arg, int include_type, int *var_count, int *semicolon);
 void list_hashtable_vars(hashtab_T *ht, char *prefix, int empty, int *first);
--- a/src/proto/scriptfile.pro
+++ b/src/proto/scriptfile.pro
@@ -19,6 +19,8 @@ int ExpandPackAddDir(char_u *pat, int *n
 void ex_source(exarg_T *eap);
 void ex_options(exarg_T *eap);
 linenr_T *source_breakpoint(void *cookie);
+garray_T *source_get_line_ga(void *cookie);
+void source_use_line_ga(void *cookie);
 int *source_dbg_tick(void *cookie);
 int source_level(void *cookie);
 int do_source(char_u *fname, int check_other, int is_vimrc, int *ret_sid);
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -23,7 +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);
+ufunc_T *def_function(exarg_T *eap, char_u *name_arg, void *context, int compile);
 void ex_function(exarg_T *eap);
 int eval_fname_script(char_u *p);
 int translated_function_exists(char_u *name, int is_global);
--- a/src/proto/vim9compile.pro
+++ b/src/proto/vim9compile.pro
@@ -9,6 +9,7 @@ imported_T *find_imported(char_u *name, 
 char_u *to_name_const_end(char_u *arg);
 int assignment_len(char_u *p, int *heredoc);
 int check_vim9_unlet(char_u *name);
+int add_def_function(ufunc_T *ufunc);
 void compile_def_function(ufunc_T *ufunc, int set_return_type, cctx_T *outer_cctx);
 void delete_instr(isn_T *isn);
 void delete_def_function(ufunc_T *ufunc);
--- a/src/scriptfile.c
+++ b/src/scriptfile.c
@@ -998,6 +998,8 @@ struct source_cookie
     int		error;		// TRUE if LF found after CR-LF
 #endif
 #ifdef FEAT_EVAL
+    garray_T	lines_ga;	// lines read in previous pass
+    int		use_lines_ga;	// next line to get from "lines_ga"
     linenr_T	breakpoint;	// next line with breakpoint or zero
     char_u	*fname;		// name of sourced file
     int		dbg_tick;	// debug_tick when breakpoint was set
@@ -1017,6 +1019,24 @@ source_breakpoint(void *cookie)
 }
 
 /*
+ * Get the grow array to store script lines in.
+ */
+    garray_T *
+source_get_line_ga(void *cookie)
+{
+    return &((struct source_cookie *)cookie)->lines_ga;
+}
+
+/*
+ * Set the index to start reading from the grow array with script lines.
+ */
+    void
+source_use_line_ga(void *cookie)
+{
+    ((struct source_cookie *)cookie)->use_lines_ga = 0;
+}
+
+/*
  * Return the address holding the debug tick for a source cookie.
  */
     int *
@@ -1235,6 +1255,9 @@ do_source(
     cookie.finished = FALSE;
 
 #ifdef FEAT_EVAL
+    ga_init2(&cookie.lines_ga, sizeof(char_u *), 200);
+    cookie.use_lines_ga = -1;
+
     // Check if this script has a breakpoint.
     cookie.breakpoint = dbg_find_breakpoint(TRUE, fname_exp, (linenr_T)0);
     cookie.fname = fname_exp;
@@ -1447,6 +1470,9 @@ almosttheend:
     vim_free(cookie.nextline);
     vim_free(firstline);
     convert_setup(&cookie.conv, NULL, NULL);
+#ifdef FEAT_EVAL
+    ga_clear_strings(&cookie.lines_ga);
+#endif
 
     if (trigger_source_post)
 	apply_autocmds(EVENT_SOURCEPOST, fname_exp, fname_exp, FALSE, curbuf);
@@ -1702,6 +1728,31 @@ getsourceline(int c UNUSED, void *cookie
     // one now.
     if (sp->finished)
 	line = NULL;
+#ifdef FEAT_EVAL
+    else if (sp->use_lines_ga >= 0)
+    {
+	// Get a line that was read in ex_vim9script().
+	for (;;)
+	{
+	    if (sp->use_lines_ga >= sp->lines_ga.ga_len)
+	    {
+		line = NULL;
+		break;
+	    }
+	    else
+	    {
+		line = ((char_u **)(sp->lines_ga.ga_data))[sp->use_lines_ga];
+		((char_u **)(sp->lines_ga.ga_data))[sp->use_lines_ga] = NULL;
+		++sp->use_lines_ga;
+		if (line != NULL)
+		    break;
+		// Skip NULL lines, they are equivalent to blank lines.
+		++sp->sourcing_lnum;
+	    }
+	}
+	SOURCING_LNUM = sp->sourcing_lnum + 1;
+    }
+#endif
     else if (sp->nextline == NULL)
 	line = get_one_sourceline(sp);
     else
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -1045,6 +1045,31 @@ def Test_display_func()
         res3)
 enddef
 
+def Test_vim9script_forward_func()
+  let lines =<< trim END
+    vim9script
+    def FuncOne(): string
+      return FuncTwo()
+    enddef
+    def FuncTwo(): string
+      return 'two'
+    enddef
+    let g:res_FuncOne = execute('disass FuncOne')
+  END
+  writefile(lines, 'Xdisassemble')
+  source Xdisassemble
+
+  " check that the first function calls the second with DCALL
+  assert_match('\<SNR>\d*_FuncOne.*' ..
+        'return FuncTwo().*' ..
+        '\d DCALL <SNR>\d\+_FuncTwo(argc 0).*' ..
+        '\d RETURN',
+        g:res_FuncOne)
+
+  delete('Xdisassemble')
+  unlet g:res_FuncOne
+enddef
+
 def s:ConcatStrings(): string
   return 'one' .. 'two' .. 'three'
 enddef
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -2378,7 +2378,7 @@ untrans_function_name(char_u *name)
  * Returns a pointer to the function or NULL if no function defined.
  */
     ufunc_T *
-def_function(exarg_T *eap, char_u *name_arg, void *context)
+def_function(exarg_T *eap, char_u *name_arg, void *context, int compile)
 {
     char_u	*theline;
     char_u	*line_to_free = NULL;
@@ -3241,6 +3241,7 @@ def_function(exarg_T *eap, char_u *name_
 	    p = ret_type;
 	    fp->uf_ret_type = parse_type(&p, &fp->uf_type_list);
 	}
+	SOURCING_LNUM = lnum_save;
     }
 
     fp->uf_lines = newlines;
@@ -3273,8 +3274,8 @@ def_function(exarg_T *eap, char_u *name_
 	is_export = FALSE;
     }
 
-    // ":def Func()" needs to be compiled
-    if (eap->cmdidx == CMD_def)
+    // ":def Func()" may need to be compiled
+    if (eap->cmdidx == CMD_def && compile)
 	compile_def_function(fp, FALSE, context);
 
     goto ret_free;
@@ -3304,7 +3305,7 @@ ret_free:
     void
 ex_function(exarg_T *eap)
 {
-    def_function(eap, NULL, NULL);
+    (void)def_function(eap, NULL, NULL, TRUE);
 }
 
 /*
--- 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 */
 /**/
+    725,
+/**/
     724,
 /**/
     723,
--- a/src/vim.h
+++ b/src/vim.h
@@ -2133,6 +2133,7 @@ typedef enum {
 // Flags for assignment functions.
 #define LET_IS_CONST	1   // ":const"
 #define LET_NO_COMMAND	2   // "var = expr" without ":let" or ":const"
+#define LET_REDEFINE	4   // variable can be redefined later
 
 #include "ex_cmds.h"	    // Ex command defines
 #include "spell.h"	    // spell checking stuff
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -4441,7 +4441,7 @@ compile_nested_function(exarg_T *eap, cc
     eap->cookie = cctx;
     eap->skip = cctx->ctx_skip == TRUE;
     eap->forceit = FALSE;
-    ufunc = def_function(eap, name, cctx);
+    ufunc = def_function(eap, name, cctx, TRUE);
 
     if (ufunc == NULL || ufunc->uf_dfunc_idx < 0)
 	return NULL;
@@ -6131,6 +6131,27 @@ theend:
 }
 
 /*
+ * Add a function to the list of :def functions.
+ * This "sets ufunc->uf_dfunc_idx" but the function isn't compiled yet.
+ */
+    int
+add_def_function(ufunc_T *ufunc)
+{
+    dfunc_T *dfunc;
+
+    // Add the function to "def_functions".
+    if (ga_grow(&def_functions, 1) == FAIL)
+	return FAIL;
+    dfunc = ((dfunc_T *)def_functions.ga_data) + def_functions.ga_len;
+    CLEAR_POINTER(dfunc);
+    dfunc->df_idx = def_functions.ga_len;
+    ufunc->uf_dfunc_idx = dfunc->df_idx;
+    dfunc->df_ufunc = ufunc;
+    ++def_functions.ga_len;
+    return OK;
+}
+
+/*
  * After ex_function() has collected all the function lines: parse and compile
  * the lines into instructions.
  * Adds the function to "def_functions".
@@ -6154,30 +6175,16 @@ compile_def_function(ufunc_T *ufunc, int
     sctx_T	save_current_sctx = current_sctx;
     int		emsg_before = called_emsg;
 
-    {
-	dfunc_T	*dfunc;  // may be invalidated by compile_lambda()
-
-	if (ufunc->uf_dfunc_idx >= 0)
-	{
-	    // Redefining a function that was compiled before.
-	    dfunc = ((dfunc_T *)def_functions.ga_data) + ufunc->uf_dfunc_idx;
-
-	    // Free old instructions.
-	    delete_def_function_contents(dfunc);
-	}
-	else
-	{
-	    // Add the function to "def_functions".
-	    if (ga_grow(&def_functions, 1) == FAIL)
-		return;
-	    dfunc = ((dfunc_T *)def_functions.ga_data) + def_functions.ga_len;
-	    CLEAR_POINTER(dfunc);
-	    dfunc->df_idx = def_functions.ga_len;
-	    ufunc->uf_dfunc_idx = dfunc->df_idx;
-	    dfunc->df_ufunc = ufunc;
-	    ++def_functions.ga_len;
-	}
-    }
+    if (ufunc->uf_dfunc_idx >= 0)
+    {
+	// Redefining a function that was compiled before.
+	dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
+							 + ufunc->uf_dfunc_idx;
+	// Free old instructions.
+	delete_def_function_contents(dfunc);
+    }
+    else if (add_def_function(ufunc) == FAIL)
+	return;
 
     CLEAR_FIELD(cctx);
     cctx.ctx_ufunc = ufunc;
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -669,6 +669,13 @@ call_def_function(
     ga_init2(&ectx.ec_stack, sizeof(typval_T), 500);
     if (ga_grow(&ectx.ec_stack, 20) == FAIL)
 	return FAIL;
+    {
+	// Check the function was compiled, it is postponed in ex_vim9script().
+	dfunc_T	*dfunc = ((dfunc_T *)def_functions.ga_data)
+							 + ufunc->uf_dfunc_idx;
+	if (dfunc->df_instr == NULL)
+	    return FAIL;
+    }
     ectx.ec_dfunc_idx = ufunc->uf_dfunc_idx;
 
     ga_init2(&ectx.ec_trystack, sizeof(trycmd_T), 10);
--- a/src/vim9script.c
+++ b/src/vim9script.c
@@ -32,7 +32,11 @@ in_vim9script(void)
     void
 ex_vim9script(exarg_T *eap)
 {
-    scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid);
+    scriptitem_T    *si = SCRIPT_ITEM(current_sctx.sc_sid);
+    garray_T	    *gap;
+    garray_T	    func_ga;
+    int		    idx;
+    ufunc_T	    *ufunc;
 
     if (!getline_equal(eap->getline, eap->cookie, getsourceline))
     {
@@ -47,12 +51,97 @@ ex_vim9script(exarg_T *eap)
     current_sctx.sc_version = SCRIPT_VERSION_VIM9;
     si->sn_version = SCRIPT_VERSION_VIM9;
     si->sn_had_command = TRUE;
+    ga_init2(&func_ga, sizeof(ufunc_T *), 20);
 
     if (STRCMP(p_cpo, CPO_VIM) != 0)
     {
 	si->sn_save_cpo = p_cpo;
 	p_cpo = vim_strsave((char_u *)CPO_VIM);
     }
+
+    // Make a pass through the script to find:
+    // - function declarations
+    // - variable and constant declarations
+    // - imports
+    // The types are recognized, so that they can be used when compiling a
+    // function.
+    gap = source_get_line_ga(eap->cookie);
+    for (;;)
+    {
+	char_u	    *line;
+	char_u	    *p;
+
+	if (ga_grow(gap, 1) == FAIL)
+	    return;
+	line = eap->getline(':', eap->cookie, 0, TRUE);
+	if (line == NULL)
+	    break;
+	((char_u **)(gap->ga_data))[gap->ga_len++] = line;
+	line = skipwhite(line);
+	p = line;
+	if (checkforcmd(&p, "function", 2) || checkforcmd(&p, "def", 3))
+	{
+	    int		    lnum_start = SOURCING_LNUM - 1;
+
+	    // Handle :function and :def by calling def_function().
+	    // It will read upto the matching :endded or :endfunction.
+	    eap->cmdidx = *line == 'f' ? CMD_function : CMD_def;
+	    eap->cmd = line;
+	    eap->arg = p;
+	    eap->forceit = FALSE;
+	    ufunc = def_function(eap, NULL, NULL, FALSE);
+
+	    if (ufunc != NULL && *line == 'd' && ga_grow(&func_ga, 1) == OK)
+	    {
+		// Add the function to the list of :def functions, so that it
+		// can be referenced by index.  It's compiled below.
+		add_def_function(ufunc);
+		((ufunc_T **)(func_ga.ga_data))[func_ga.ga_len++] = ufunc;
+	    }
+
+	    // Store empty lines in place of the function, we don't need to
+	    // process it again.
+	    vim_free(((char_u **)(gap->ga_data))[--gap->ga_len]);
+	    if (ga_grow(gap, SOURCING_LNUM - lnum_start) == OK)
+		while (lnum_start < SOURCING_LNUM)
+		{
+		    // getsourceline() will skip over NULL lines.
+		    ((char_u **)(gap->ga_data))[gap->ga_len++] = NULL;
+		    ++lnum_start;
+		}
+	}
+	else if (checkforcmd(&p, "let", 3) || checkforcmd(&p, "const", 4))
+	{
+	    eap->cmd = line;
+	    eap->arg = p;
+	    eap->forceit = FALSE;
+	    eap->cmdidx = *line == 'l' ? CMD_let: CMD_const;
+
+	    // The command will be executed again, it's OK to redefine the
+	    // variable then.
+	    ex_let_const(eap, TRUE);
+	}
+	else if (checkforcmd(&p, "import", 3))
+	{
+	    eap->arg = p;
+	    ex_import(eap);
+
+	    // Store empty line, we don't need to process the command again.
+	    vim_free(((char_u **)(gap->ga_data))[--gap->ga_len]);
+	    ((char_u **)(gap->ga_data))[gap->ga_len++] = NULL;
+	}
+    }
+
+    // Compile the :def functions.
+    for (idx = 0; idx < func_ga.ga_len; ++idx)
+    {
+	ufunc = ((ufunc_T **)(func_ga.ga_data))[idx];
+	compile_def_function(ufunc, FALSE, NULL);
+    }
+    ga_clear(&func_ga);
+
+    // Return to process the commands at the script level.
+    source_use_line_ga(eap->cookie);
 }
 
 /*
@@ -64,7 +153,7 @@ ex_vim9script(exarg_T *eap)
  * ":export {Name, ...}"
  */
     void
-ex_export(exarg_T *eap UNUSED)
+ex_export(exarg_T *eap)
 {
     if (current_sctx.sc_version != SCRIPT_VERSION_VIM9)
     {