Patch 9.0.0484
Problem:    In a :def function all closures in a loop get the same variables.
Solution:   Add ENDLOOP at break, continue and return if needed.
Files:      src/vim9.h, src/vim9cmds.c, src/testdir/test_vim9_disassemble.vim


*** ../vim-9.0.0483/src/vim9.h  2022-09-16 19:04:19.957886512 +0100
--- src/vim9.h  2022-09-17 11:43:49.060020424 +0100
***************
*** 625,639 ****
      endlabel_T        *is_end_label;      // instructions to set end label
  } ifscope_T;
  
  /*
   * info specific for the scope of :while
   */
  typedef struct {
      int               ws_top_label;       // instruction idx at WHILE
      endlabel_T        *ws_end_label;      // instructions to set end
!     int               ws_funcref_idx;     // index of var that holds funcref 
count
!     int               ws_local_count;     // ctx_locals.ga_len at :while
!     int               ws_closure_count;   // ctx_closure_count at :while
  } whilescope_T;
  
  /*
--- 625,644 ----
      endlabel_T        *is_end_label;      // instructions to set end label
  } ifscope_T;
  
+ // info used by :for and :while needed for ENDLOOP
+ typedef struct {
+     int           li_local_count;         // ctx_locals.ga_len at loop start
+     int           li_closure_count;       // ctx_closure_count at loop start
+     int           li_funcref_idx;         // index of var that holds funcref 
count
+ } loop_info_T;
+ 
  /*
   * info specific for the scope of :while
   */
  typedef struct {
      int               ws_top_label;       // instruction idx at WHILE
      endlabel_T        *ws_end_label;      // instructions to set end
!     loop_info_T ws_loop_info;     // info for LOOPEND
  } whilescope_T;
  
  /*
***************
*** 642,650 ****
  typedef struct {
      int               fs_top_label;       // instruction idx at FOR
      endlabel_T        *fs_end_label;      // break instructions
!     int               fs_funcref_idx;     // index of var that holds funcref 
count
!     int               fs_local_count;     // ctx_locals.ga_len at :for
!     int               fs_closure_count;   // ctx_closure_count at :for
  } forscope_T;
  
  /*
--- 647,653 ----
  typedef struct {
      int               fs_top_label;       // instruction idx at FOR
      endlabel_T        *fs_end_label;      // break instructions
!     loop_info_T       fs_loop_info;       // info for LOOPEND
  } forscope_T;
  
  /*
*** ../vim-9.0.0483/src/vim9cmds.c      2022-09-16 19:04:19.957886512 +0100
--- src/vim9cmds.c      2022-09-17 12:13:32.301289923 +0100
***************
*** 776,781 ****
--- 776,792 ----
  }
  
  /*
+  * Save the info needed for ENDLOOP.  Used by :for and :while.
+  */
+     static void
+ compile_fill_loop_info(loop_info_T *loop_info, int funcref_idx, cctx_T *cctx)
+ {
+     loop_info->li_funcref_idx = funcref_idx;
+     loop_info->li_local_count = cctx->ctx_locals.ga_len;
+     loop_info->li_closure_count = cctx->ctx_closure_count;
+ }
+ 
+ /*
   * Compile "for var in expr":
   *
   * Produces instructions:
***************
*** 1041,1050 ****
            vim_free(name);
        }
  
!       forscope->fs_funcref_idx = funcref_lvar->lv_idx;
!       // remember the number of variables and closures, used in :endfor
!       forscope->fs_local_count = cctx->ctx_locals.ga_len;
!       forscope->fs_closure_count = cctx->ctx_closure_count;
      }
  
      return arg_end;
--- 1052,1060 ----
            vim_free(name);
        }
  
!       // remember the number of variables and closures, used for ENDLOOP
!       compile_fill_loop_info(&forscope->fs_loop_info,
!                                                  funcref_lvar->lv_idx, cctx);
      }
  
      return arg_end;
***************
*** 1056,1074 ****
  }
  
  /*
!  * At :endfor and :endwhile: Generate an ISN_ENDLOOP instruction if any
!  * variable was declared that could be used by a new closure.
   */
      static int
! compile_loop_end(
!       int     prev_local_count,
!       int     prev_closure_count,
!       int     funcref_idx,
!       cctx_T  *cctx)
! {
!     if (cctx->ctx_locals.ga_len > prev_local_count
!           && cctx->ctx_closure_count > prev_closure_count)
!       return generate_ENDLOOP(cctx, funcref_idx, prev_local_count);
      return OK;
  }
  
--- 1066,1082 ----
  }
  
  /*
!  * Used when ending a loop of :for and :while: Generate an ISN_ENDLOOP
!  * instruction if any variable was declared that could be used by a new
!  * closure.
   */
      static int
! compile_loop_end(loop_info_T *loop_info, cctx_T *cctx)
! {
!     if (cctx->ctx_locals.ga_len > loop_info->li_local_count
!           && cctx->ctx_closure_count > loop_info->li_closure_count)
!       return generate_ENDLOOP(cctx, loop_info->li_funcref_idx,
!                                                   loop_info->li_local_count);
      return OK;
  }
  
***************
*** 1097,1106 ****
      {
        // Handle the case that any local variables were declared that might be
        // used in a closure.
!       if (compile_loop_end(forscope->fs_local_count,
!                               forscope->fs_closure_count,
!                               forscope->fs_funcref_idx,
!                               cctx) == FAIL)
            return NULL;
  
        unwind_locals(cctx, scope->se_local_count);
--- 1105,1111 ----
      {
        // Handle the case that any local variables were declared that might be
        // used in a closure.
!       if (compile_loop_end(&forscope->fs_loop_info, cctx) == FAIL)
            return NULL;
  
        unwind_locals(cctx, scope->se_local_count);
***************
*** 1163,1172 ****
        drop_scope(cctx);
        return NULL;  // out of memory
      }
!     whilescope->ws_funcref_idx = funcref_lvar->lv_idx;
!     // remember the number of variables and closures, used in :endwhile
!     whilescope->ws_local_count = cctx->ctx_locals.ga_len;
!     whilescope->ws_closure_count = cctx->ctx_closure_count;
  
      // compile "expr"
      if (compile_expr0(&p, cctx) == FAIL)
--- 1168,1177 ----
        drop_scope(cctx);
        return NULL;  // out of memory
      }
! 
!     // remember the number of variables and closures, used for ENDLOOP
!     compile_fill_loop_info(&whilescope->ws_loop_info,
!                                                  funcref_lvar->lv_idx, cctx);
  
      // compile "expr"
      if (compile_expr0(&p, cctx) == FAIL)
***************
*** 1218,1227 ****
  
        // Handle the case that any local variables were declared that might be
        // used in a closure.
!       if (compile_loop_end(whilescope->ws_local_count,
!                               whilescope->ws_closure_count,
!                               whilescope->ws_funcref_idx,
!                               cctx) == FAIL)
            return NULL;
  
        unwind_locals(cctx, scope->se_local_count);
--- 1223,1229 ----
  
        // Handle the case that any local variables were declared that might be
        // used in a closure.
!       if (compile_loop_end(&whilescope->ws_loop_info, cctx) == FAIL)
            return NULL;
  
        unwind_locals(cctx, scope->se_local_count);
***************
*** 1263,1271 ****
        return 0;
  
      if (scope->se_type == WHILE_SCOPE)
!       start_local_count = scope->se_u.se_while.ws_local_count;
      else
!       start_local_count = scope->se_u.se_for.fs_local_count;
      if (cctx->ctx_locals.ga_len > start_local_count)
      {
        *loop_var_idx = (short)start_local_count;
--- 1265,1273 ----
        return 0;
  
      if (scope->se_type == WHILE_SCOPE)
!       start_local_count = scope->se_u.se_while.ws_loop_info.li_local_count;
      else
!       start_local_count = scope->se_u.se_for.fs_loop_info.li_local_count;
      if (cctx->ctx_locals.ga_len > start_local_count)
      {
        *loop_var_idx = (short)start_local_count;
***************
*** 1289,1325 ****
  }
  
  /*
!  * compile "continue"
   */
!     char_u *
! compile_continue(char_u *arg, cctx_T *cctx)
  {
      scope_T   *scope = cctx->ctx_scope;
-     int               try_scopes = 0;
-     int               loop_label;
  
      for (;;)
      {
        if (scope == NULL)
        {
!           emsg(_(e_continue_without_while_or_for));
!           return NULL;
        }
        if (scope->se_type == FOR_SCOPE)
        {
!           loop_label = scope->se_u.se_for.fs_top_label;
            break;
        }
        if (scope->se_type == WHILE_SCOPE)
        {
!           loop_label = scope->se_u.se_while.ws_top_label;
            break;
        }
!       if (scope->se_type == TRY_SCOPE)
!           ++try_scopes;
        scope = scope->se_outer;
      }
  
      if (try_scopes > 0)
        // Inside one or more try/catch blocks we first need to jump to the
        // "finally" or "endtry" to cleanup.
--- 1291,1357 ----
  }
  
  /*
!  * Common for :break, :continue and :return
   */
!     static int
! compile_find_scope(
!       int         *loop_label,    // where to jump to or NULL
!       endlabel_T  ***el,          // end label or NULL
!       int         *try_scopes,    // :try scopes encountered or NULL
!       char        *error,         // error to use when no scope found
!       cctx_T      *cctx)
  {
      scope_T   *scope = cctx->ctx_scope;
  
      for (;;)
      {
        if (scope == NULL)
        {
!           if (error != NULL)
!               emsg(_(error));
!           return FAIL;
        }
        if (scope->se_type == FOR_SCOPE)
        {
!           if (compile_loop_end(&scope->se_u.se_for.fs_loop_info, cctx)
!                                                                      == FAIL)
!               return FAIL;
!           if (loop_label != NULL)
!               *loop_label = scope->se_u.se_for.fs_top_label;
!           if (el != NULL)
!               *el = &scope->se_u.se_for.fs_end_label;
            break;
        }
        if (scope->se_type == WHILE_SCOPE)
        {
!           if (compile_loop_end(&scope->se_u.se_while.ws_loop_info, cctx)
!                                                                      == FAIL)
!               return FAIL;
!           if (loop_label != NULL)
!               *loop_label = scope->se_u.se_while.ws_top_label;
!           if (el != NULL)
!               *el = &scope->se_u.se_while.ws_end_label;
            break;
        }
!       if (try_scopes != NULL && scope->se_type == TRY_SCOPE)
!           ++*try_scopes;
        scope = scope->se_outer;
      }
+     return OK;
+ }
+ 
+ /*
+  * compile "continue"
+  */
+     char_u *
+ compile_continue(char_u *arg, cctx_T *cctx)
+ {
+     int               try_scopes = 0;
+     int               loop_label;
  
+     if (compile_find_scope(&loop_label, NULL, &try_scopes,
+                               e_continue_without_while_or_for, cctx) == FAIL)
+       return NULL;
      if (try_scopes > 0)
        // Inside one or more try/catch blocks we first need to jump to the
        // "finally" or "endtry" to cleanup.
***************
*** 1337,1367 ****
      char_u *
  compile_break(char_u *arg, cctx_T *cctx)
  {
-     scope_T   *scope = cctx->ctx_scope;
      int               try_scopes = 0;
      endlabel_T        **el;
  
!     for (;;)
!     {
!       if (scope == NULL)
!       {
!           emsg(_(e_break_without_while_or_for));
!           return NULL;
!       }
!       if (scope->se_type == FOR_SCOPE)
!       {
!           el = &scope->se_u.se_for.fs_end_label;
!           break;
!       }
!       if (scope->se_type == WHILE_SCOPE)
!       {
!           el = &scope->se_u.se_while.ws_end_label;
!           break;
!       }
!       if (scope->se_type == TRY_SCOPE)
!           ++try_scopes;
!       scope = scope->se_outer;
!     }
  
      if (try_scopes > 0)
        // Inside one or more try/catch blocks we first need to jump to the
--- 1369,1380 ----
      char_u *
  compile_break(char_u *arg, cctx_T *cctx)
  {
      int               try_scopes = 0;
      endlabel_T        **el;
  
!     if (compile_find_scope(NULL, &el, &try_scopes,
!                                  e_break_without_while_or_for, cctx) == FAIL)
!       return NULL;
  
      if (try_scopes > 0)
        // Inside one or more try/catch blocks we first need to jump to the
***************
*** 2512,2517 ****
--- 2525,2533 ----
        generate_PUSHNR(cctx, 0);
      }
  
+     // may need ENDLOOP when inside a :for or :while loop
+     if (compile_find_scope(NULL, NULL, NULL, NULL, cctx) == FAIL)
+ 
      // Undo any command modifiers.
      generate_undo_cmdmods(cctx);
  
*** ../vim-9.0.0483/src/testdir/test_vim9_disassemble.vim       2022-09-15 
17:19:30.026390537 +0100
--- src/testdir/test_vim9_disassemble.vim       2022-09-17 12:36:35.280789576 
+0100
***************
*** 976,981 ****
--- 976,1060 ----
           lres)
  enddef
  
+ def s:ClosureInLoop()
+   for i in range(5)
+     var ii = i
+     continue
+     break
+     if g:val
+       return
+     endif
+     g:Ref = () => ii
+     continue
+     break
+     if g:val
+       return
+     endif
+   endfor
+ enddef
+ 
+ " Mainly check that ENDLOOP is only produced after a closure was created.
+ def Test_disassemble_closure_in_loop()
+   var res = execute('disass s:ClosureInLoop')
+   assert_match('<SNR>\d\+_ClosureInLoop\_s*' ..
+         'for i in range(5)\_s*' ..
+         '\d\+ STORE -1 in $0\_s*' ..
+         '\d\+ PUSHNR 5\_s*' ..
+         '\d\+ BCALL range(argc 1)\_s*' ..
+         '\d\+ FOR $0 -> \d\+\_s*' ..
+         '\d\+ STORE $2\_s*' ..
+ 
+         'var ii = i\_s*' ..
+         '\d\+ LOAD $2\_s*' ..
+         '\d\+ STORE $3\_s*' ..
+ 
+         'continue\_s*' ..
+         '\d\+ JUMP -> \d\+\_s*' ..
+ 
+         'break\_s*' ..
+         '\d\+ JUMP -> \d\+\_s*' ..
+ 
+         'if g:val\_s*' ..
+         '\d\+ LOADG g:val\_s*' ..
+         '\d\+ COND2BOOL\_s*' ..
+         '\d\+ JUMP_IF_FALSE -> \d\+\_s*' ..
+ 
+         '  return\_s*' ..
+         '\d\+ PUSHNR 0\_s*' ..
+         '\d\+ RETURN\_s*' ..
+ 
+         'endif\_s*' ..
+         'g:Ref = () => ii\_s*' ..
+         '\d\+ FUNCREF <lambda>4 var $3 - $3\_s*' ..
+         '\d\+ STOREG g:Ref\_s*' ..
+ 
+         'continue\_s*' ..
+         '\d\+ ENDLOOP $1 save $3 - $3\_s*' ..
+         '\d\+ JUMP -> \d\+\_s*' ..
+ 
+         'break\_s*' ..
+         '\d\+ ENDLOOP $1 save $3 - $3\_s*' ..
+         '\d\+ JUMP -> \d\+\_s*' ..
+ 
+          'if g:val\_s*' ..
+         '\d\+ LOADG g:val\_s*' ..
+         '\d\+ COND2BOOL\_s*' ..
+         '\d\+ JUMP_IF_FALSE -> \d\+\_s*' ..
+ 
+         '  return\_s*' ..
+         '\d\+ PUSHNR 0\_s*' ..
+         '\d\+ ENDLOOP $1 save $3 - $3\_s*' ..
+         '\d\+ RETURN\_s*' ..
+ 
+         'endif\_s*' ..
+         'endfor\_s*' ..
+         '\d\+ ENDLOOP $1 save $3 - $3\_s*' ..
+         '\d\+ JUMP -> \d\+\_s*' ..
+         '\d\+ DROP\_s*' ..
+         '\d\+ RETURN void',
+         res)
+ enddef
+ 
  def EchoArg(arg: string): string
    return arg
  enddef
*** ../vim-9.0.0483/src/version.c       2022-09-16 22:16:54.404074904 +0100
--- src/version.c       2022-09-17 12:39:02.632552672 +0100
***************
*** 705,706 ****
--- 705,708 ----
  {   /* Add new patch number below this line */
+ /**/
+     484,
  /**/

-- 
>From "know your smileys":
 :-{}   Too much lipstick

 /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
///                                                                      \\\
\\\        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
 \\\            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/20220917114037.B3F3C1C0846%40moolenaar.net.

Raspunde prin e-mail lui