# HG changeset patch # User Bram Moolenaar # Date 1589058004 -7200 # Node ID 7587d892c00c084568bdc8992a744ff7a544747b # Parent ff4ae3f09307be392434bd9804ecd3fb61695761 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 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. diff --git a/src/evalvars.c b/src/evalvars.c --- 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; } /* diff --git a/src/proto/evalvars.pro b/src/proto/evalvars.pro --- 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); diff --git a/src/proto/scriptfile.pro b/src/proto/scriptfile.pro --- 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); diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro --- 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); diff --git a/src/proto/vim9compile.pro b/src/proto/vim9compile.pro --- 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); diff --git a/src/scriptfile.c b/src/scriptfile.c --- 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 diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim --- 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('\\d*_FuncOne.*' .. + 'return FuncTwo().*' .. + '\d DCALL \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 diff --git a/src/userfunc.c b/src/userfunc.c --- 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); } /* 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 */ /**/ + 725, +/**/ 724, /**/ 723, diff --git a/src/vim.h b/src/vim.h --- 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 diff --git a/src/vim9compile.c b/src/vim9compile.c --- 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; diff --git a/src/vim9execute.c b/src/vim9execute.c --- 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); diff --git a/src/vim9script.c b/src/vim9script.c --- 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) {