To: vim_dev@googlegroups.com Subject: Patch 9.0.0484 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ 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('\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 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 -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///