Patch 8.2.0725
Problem:    Vim9: cannot call a function declared later in Vim9 script.
Solution:   Make two passes through the script file.
Files:      src/scriptfile.c, src/proto/scriptfile.pro, src/vim9script.c,
            src/vim9compile.c, src/vim9execute.c, src/proto/vim9compile.pro,
            src/userfunc.c, src/proto/userfunc.pro, src/evalvars.c,
            src/proto/evalvars.pro, src/vim.h,
            src/testdir/test_vim9_disassemble.vim


*** ../vim-8.2.0724/src/scriptfile.c    2020-05-03 17:01:19.842024266 +0200
--- src/scriptfile.c    2020-05-09 21:52:21.128164355 +0200
***************
*** 998,1003 ****
--- 998,1005 ----
      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,1022 ****
--- 1019,1042 ----
  }
  
  /*
+  * 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,1240 ****
--- 1255,1263 ----
      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,1452 ****
--- 1470,1478 ----
      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,1707 ****
--- 1728,1758 ----
      // 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
*** ../vim-8.2.0724/src/proto/scriptfile.pro    2020-01-26 15:52:33.023833239 
+0100
--- src/proto/scriptfile.pro    2020-05-09 19:28:49.405736342 +0200
***************
*** 19,24 ****
--- 19,26 ----
  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);
*** ../vim-8.2.0724/src/vim9script.c    2020-04-27 22:47:45.186176148 +0200
--- src/vim9script.c    2020-05-09 22:48:51.139530983 +0200
***************
*** 32,38 ****
      void
  ex_vim9script(exarg_T *eap)
  {
!     scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid);
  
      if (!getline_equal(eap->getline, eap->cookie, getsourceline))
      {
--- 32,42 ----
      void
  ex_vim9script(exarg_T *eap)
  {
!     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,58 ****
--- 51,147 ----
      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,70 ****
   * ":export {Name, ...}"
   */
      void
! ex_export(exarg_T *eap UNUSED)
  {
      if (current_sctx.sc_version != SCRIPT_VERSION_VIM9)
      {
--- 153,159 ----
   * ":export {Name, ...}"
   */
      void
! ex_export(exarg_T *eap)
  {
      if (current_sctx.sc_version != SCRIPT_VERSION_VIM9)
      {
*** ../vim-8.2.0724/src/vim9compile.c   2020-05-09 18:28:30.397618068 +0200
--- src/vim9compile.c   2020-05-09 21:45:12.681643443 +0200
***************
*** 4441,4447 ****
      eap->cookie = cctx;
      eap->skip = cctx->ctx_skip == TRUE;
      eap->forceit = FALSE;
!     ufunc = def_function(eap, name, cctx);
  
      if (ufunc == NULL || ufunc->uf_dfunc_idx < 0)
        return NULL;
--- 4441,4447 ----
      eap->cookie = cctx;
      eap->skip = cctx->ctx_skip == TRUE;
      eap->forceit = FALSE;
!     ufunc = def_function(eap, name, cctx, TRUE);
  
      if (ufunc == NULL || ufunc->uf_dfunc_idx < 0)
        return NULL;
***************
*** 6131,6136 ****
--- 6131,6157 ----
  }
  
  /*
+  * 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,6183 ****
      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;
!       }
      }
  
      CLEAR_FIELD(cctx);
      cctx.ctx_ufunc = ufunc;
--- 6175,6190 ----
      sctx_T    save_current_sctx = current_sctx;
      int               emsg_before = called_emsg;
  
+     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;
*** ../vim-8.2.0724/src/vim9execute.c   2020-05-07 14:07:19.948220408 +0200
--- src/vim9execute.c   2020-05-09 22:20:48.377777547 +0200
***************
*** 669,674 ****
--- 669,681 ----
      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);
*** ../vim-8.2.0724/src/proto/vim9compile.pro   2020-05-01 19:29:05.002157723 
+0200
--- src/proto/vim9compile.pro   2020-05-09 21:44:59.909687746 +0200
***************
*** 9,14 ****
--- 9,15 ----
  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);
*** ../vim-8.2.0724/src/userfunc.c      2020-05-04 23:24:41.118072992 +0200
--- src/userfunc.c      2020-05-09 21:33:27.048081467 +0200
***************
*** 2378,2384 ****
   * 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)
  {
      char_u    *theline;
      char_u    *line_to_free = NULL;
--- 2378,2384 ----
   * 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, int compile)
  {
      char_u    *theline;
      char_u    *line_to_free = NULL;
***************
*** 3241,3246 ****
--- 3241,3247 ----
            p = ret_type;
            fp->uf_ret_type = parse_type(&p, &fp->uf_type_list);
        }
+       SOURCING_LNUM = lnum_save;
      }
  
      fp->uf_lines = newlines;
***************
*** 3273,3280 ****
        is_export = FALSE;
      }
  
!     // ":def Func()" needs to be compiled
!     if (eap->cmdidx == CMD_def)
        compile_def_function(fp, FALSE, context);
  
      goto ret_free;
--- 3274,3281 ----
        is_export = FALSE;
      }
  
!     // ":def Func()" may need to be compiled
!     if (eap->cmdidx == CMD_def && compile)
        compile_def_function(fp, FALSE, context);
  
      goto ret_free;
***************
*** 3304,3310 ****
      void
  ex_function(exarg_T *eap)
  {
!     def_function(eap, NULL, NULL);
  }
  
  /*
--- 3305,3311 ----
      void
  ex_function(exarg_T *eap)
  {
!     (void)def_function(eap, NULL, NULL, TRUE);
  }
  
  /*
*** ../vim-8.2.0724/src/proto/userfunc.pro      2020-05-04 23:24:41.118072992 
+0200
--- src/proto/userfunc.pro      2020-05-09 20:01:57.834140801 +0200
***************
*** 23,29 ****
  int call_func(char_u *funcname, int len, typval_T *rettv, int argcount_in, 
typval_T *argvars_in, funcexe_T *funcexe);
  char_u *trans_function_name(char_u **pp, int *is_global, int skip, int flags, 
funcdict_T *fdp, partial_T **partial);
  char_u *untrans_function_name(char_u *name);
! ufunc_T *def_function(exarg_T *eap, char_u *name_arg, void *context);
  void ex_function(exarg_T *eap);
  int eval_fname_script(char_u *p);
  int translated_function_exists(char_u *name, int is_global);
--- 23,29 ----
  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, 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);
*** ../vim-8.2.0724/src/evalvars.c      2020-05-09 13:06:20.224712254 +0200
--- src/evalvars.c      2020-05-09 22:32:32.855017381 +0200
***************
*** 164,170 ****
  // 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);
--- 164,169 ----
***************
*** 698,708 ****
      void
  ex_const(exarg_T *eap)
  {
!     ex_let_const(eap, TRUE);
  }
  
!     static void
! ex_let_const(exarg_T *eap, int is_const)
  {
      char_u    *arg = eap->arg;
      char_u    *expr = NULL;
--- 697,711 ----
      void
  ex_const(exarg_T *eap)
  {
!     ex_let_const(eap, FALSE);
  }
  
! /*
!  * 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,724 ****
      char_u    *argend;
      int               first = TRUE;
      int               concat;
!     int               flags = is_const ? LET_IS_CONST : 0;
  
      // detect Vim9 assignment without ":let" or ":const"
      if (eap->arg == eap->cmd)
        flags |= LET_NO_COMMAND;
  
      argend = skip_var_list(arg, TRUE, &var_count, &semicolon);
      if (argend == NULL)
--- 717,729 ----
      char_u    *argend;
      int               first = TRUE;
      int               concat;
!     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,2981 ****
--- 2981,2988 ----
  
      if (flags & LET_IS_CONST)
        di->di_tv.v_lock |= VAR_LOCKED;
+     if (flags & LET_REDEFINE)
+       di->di_flags |= DI_FLAGS_RELOAD;
  }
  
  /*
*** ../vim-8.2.0724/src/proto/evalvars.pro      2020-05-01 15:44:24.539895251 
+0200
--- src/proto/evalvars.pro      2020-05-09 22:32:36.947002041 +0200
***************
*** 16,21 ****
--- 16,22 ----
  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);
*** ../vim-8.2.0724/src/vim.h   2020-04-12 19:37:13.506297291 +0200
--- src/vim.h   2020-05-09 22:31:12.319320325 +0200
***************
*** 2133,2138 ****
--- 2133,2139 ----
  // 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
*** ../vim-8.2.0724/src/testdir/test_vim9_disassemble.vim       2020-05-09 
18:28:30.401618052 +0200
--- src/testdir/test_vim9_disassemble.vim       2020-05-09 22:00:25.318501912 
+0200
***************
*** 1045,1050 ****
--- 1045,1075 ----
          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
*** ../vim-8.2.0724/src/version.c       2020-05-09 18:44:52.637841293 +0200
--- src/version.c       2020-05-09 21:53:04.104016551 +0200
***************
*** 748,749 ****
--- 748,751 ----
  {   /* Add new patch number below this line */
+ /**/
+     725,
  /**/

-- 
hundred-and-one symptoms of being an internet addict:
83. Batteries in the TV remote now last for months.

 /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
///        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
\\\  an exciting new programming language -- http://www.Zimbu.org        ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/202005092050.049KobIH020330%40masaka.moolenaar.net.

Raspunde prin e-mail lui