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.