# HG changeset patch # User Bram Moolenaar # Date 1613225703 -3600 # Node ID 5e5780e3f75ddc9f9ff3e3fb913e9c69e9a20a02 # Parent 1a41fde53816949832ec9ba20993a6228811af0a patch 8.2.2506: Vim9: :continue does not work correctly in a :try block Commit: https://github.com/vim/vim/commit/c150c09ec4f97636c6339f5687fdaa9f665095d2 Author: Bram Moolenaar Date: Sat Feb 13 15:02:46 2021 +0100 patch 8.2.2506: Vim9: :continue does not work correctly in a :try block Problem: Vim9: :continue does not work correctly in a :try block Solution: Add the TRYCLEANUP instruction. (closes https://github.com/vim/vim/issues/7827) 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 @@ -1111,6 +1111,63 @@ def Test_disassemble_for_loop_unpack() instr) enddef +def ForLoopContinue() + for nr in [1, 2] + try + echo "ok" + try + echo "deeper" + catch + continue + endtry + catch + echo "not ok" + endtry + endfor +enddef + +def Test_disassemble_for_loop_continue() + var instr = execute('disassemble ForLoopContinue') + assert_match('ForLoopContinue\_s*' .. + 'for nr in \[1, 2]\_s*' .. + '0 STORE -1 in $0\_s*' .. + '1 PUSHNR 1\_s*' .. + '2 PUSHNR 2\_s*' .. + '3 NEWLIST size 2\_s*' .. + '4 FOR $0 -> 22\_s*' .. + '5 STORE $1\_s*' .. + 'try\_s*' .. + '6 TRY catch -> 17, end -> 20\_s*' .. + 'echo "ok"\_s*' .. + '7 PUSHS "ok"\_s*' .. + '8 ECHO 1\_s*' .. + 'try\_s*' .. + '9 TRY catch -> 13, end -> 15\_s*' .. + 'echo "deeper"\_s*' .. + '10 PUSHS "deeper"\_s*' .. + '11 ECHO 1\_s*' .. + 'catch\_s*' .. + '12 JUMP -> 15\_s*' .. + '13 CATCH\_s*' .. + 'continue\_s*' .. + '14 TRY-CONTINUE 2 levels -> 4\_s*' .. + 'endtry\_s*' .. + '15 ENDTRY\_s*' .. + 'catch\_s*' .. + '16 JUMP -> 20\_s*' .. + '17 CATCH\_s*' .. + 'echo "not ok"\_s*' .. + '18 PUSHS "not ok"\_s*' .. + '19 ECHO 1\_s*' .. + 'endtry\_s*' .. + '20 ENDTRY\_s*' .. + 'endfor\_s*' .. + '21 JUMP -> 4\_s*' .. + '\d\+ DROP\_s*' .. + '\d\+ RETURN 0', + instr) +enddef + let g:number = 42 def TypeCast() diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim --- a/src/testdir/test_vim9_script.vim +++ b/src/testdir/test_vim9_script.vim @@ -2201,6 +2201,23 @@ def Test_for_loop_unpack() CheckDefExecFailure(lines, 'E1017:', 1) enddef +def Test_for_loop_with_try_continue() + var looped = 0 + var cleanup = 0 + for i in range(3) + looped += 1 + try + eval [][0] + catch + continue + finally + cleanup += 1 + endtry + endfor + assert_equal(3, looped) + assert_equal(3, cleanup) +enddef + def Test_while_loop() var result = '' var cnt = 0 diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -751,6 +751,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 2506, +/**/ 2505, /**/ 2504, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -100,6 +100,7 @@ typedef enum { ISN_PUSHEXC, // push v:exception ISN_CATCH, // drop v:exception ISN_ENDTRY, // take entry off from ec_trystack + ISN_TRYCONT, // handle :continue inside a :try statement // more expression operations ISN_ADDLIST, // add two lists @@ -209,9 +210,15 @@ typedef struct { // arguments to ISN_TRY typedef struct { int try_catch; // position to jump to on throw - int try_finally; // position to jump to for return + int try_finally; // :finally or :endtry position to jump to } try_T; +// arguments to ISN_TRYCONT +typedef struct { + int tct_levels; // number of nested try statements + int tct_where; // position to jump to, WHILE or FOR +} trycont_T; + // arguments to ISN_ECHO typedef struct { int echo_with_white; // :echo instead of :echon @@ -333,6 +340,7 @@ struct isn_S { jump_T jump; forloop_T forloop; try_T try; + trycont_T trycont; cbfunc_T bfunc; cdfunc_T dfunc; cpfunc_T pfunc; diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -1592,6 +1592,23 @@ generate_FOR(cctx_T *cctx, int loop_idx) return OK; } +/* + * Generate an ISN_TRYCONT instruction. + */ + static int +generate_TRYCONT(cctx_T *cctx, int levels, int where) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr(cctx, ISN_TRYCONT)) == NULL) + return FAIL; + isn->isn_arg.trycont.tct_levels = levels; + isn->isn_arg.trycont.tct_where = where; + + return OK; +} + /* * Generate an ISN_BCALL instruction. @@ -7314,6 +7331,8 @@ compile_endwhile(char_u *arg, cctx_T *cc compile_continue(char_u *arg, cctx_T *cctx) { scope_T *scope = cctx->ctx_scope; + int try_scopes = 0; + int loop_label; for (;;) { @@ -7322,15 +7341,29 @@ compile_continue(char_u *arg, cctx_T *cc emsg(_(e_continue)); return NULL; } - if (scope->se_type == FOR_SCOPE || scope->se_type == WHILE_SCOPE) + 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; } - // Jump back to the FOR or WHILE instruction. - generate_JUMP(cctx, JUMP_ALWAYS, - scope->se_type == FOR_SCOPE ? scope->se_u.se_for.fs_top_label - : scope->se_u.se_while.ws_top_label); + if (try_scopes > 0) + // Inside one or more try/catch blocks we first need to jump to the + // "finally" or "endtry" to cleanup. + generate_TRYCONT(cctx, try_scopes, loop_label); + else + // Jump back to the FOR or WHILE instruction. + generate_JUMP(cctx, JUMP_ALWAYS, loop_label); + return arg; } @@ -7625,7 +7658,7 @@ compile_endtry(char_u *arg, cctx_T *cctx { scope_T *scope = cctx->ctx_scope; garray_T *instr = &cctx->ctx_instr; - isn_T *isn; + isn_T *try_isn; // end block scope from :catch or :finally if (scope != NULL && scope->se_type == BLOCK_SCOPE) @@ -7646,11 +7679,11 @@ compile_endtry(char_u *arg, cctx_T *cctx return NULL; } + try_isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_try_label; if (cctx->ctx_skip != SKIP_YES) { - isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_try_label; - if (isn->isn_arg.try.try_catch == 0 - && isn->isn_arg.try.try_finally == 0) + if (try_isn->isn_arg.try.try_catch == 0 + && try_isn->isn_arg.try.try_finally == 0) { emsg(_(e_missing_catch_or_finally)); return NULL; @@ -7670,21 +7703,27 @@ compile_endtry(char_u *arg, cctx_T *cctx instr->ga_len, cctx); // End :catch or :finally scope: set value in ISN_TRY instruction - if (isn->isn_arg.try.try_catch == 0) - isn->isn_arg.try.try_catch = instr->ga_len; - if (isn->isn_arg.try.try_finally == 0) - isn->isn_arg.try.try_finally = instr->ga_len; + if (try_isn->isn_arg.try.try_catch == 0) + try_isn->isn_arg.try.try_catch = instr->ga_len; + if (try_isn->isn_arg.try.try_finally == 0) + try_isn->isn_arg.try.try_finally = instr->ga_len; if (scope->se_u.se_try.ts_catch_label != 0) { // Last catch without match jumps here - isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_catch_label; + isn_T *isn = ((isn_T *)instr->ga_data) + + scope->se_u.se_try.ts_catch_label; isn->isn_arg.jump.jump_where = instr->ga_len; } } compile_endblock(cctx); + if (try_isn->isn_arg.try.try_finally == 0) + // No :finally encountered, use the try_finaly field to point to + // ENDTRY, so that TRYCONT can jump there. + try_isn->isn_arg.try.try_finally = cctx->ctx_instr.ga_len; + if (cctx->ctx_skip != SKIP_YES && generate_instr(cctx, ISN_ENDTRY) == NULL) return NULL; #ifdef FEAT_PROFILE @@ -8850,6 +8889,7 @@ delete_instr(isn_T *isn) case ISN_STRSLICE: case ISN_THROW: case ISN_TRY: + case ISN_TRYCONT: case ISN_UNLETINDEX: case ISN_UNPACK: // nothing allocated diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -27,8 +27,9 @@ typedef struct { int tcd_frame_idx; // ec_frame_idx at ISN_TRY int tcd_stack_len; // size of ectx.ec_stack at ISN_TRY int tcd_catch_idx; // instruction of the first catch - int tcd_finally_idx; // instruction of the finally block + int tcd_finally_idx; // instruction of the finally block or :endtry int tcd_caught; // catch block entered + int tcd_cont; // :continue encountered, jump here int tcd_return; // when TRUE return from end of :finally } trycmd_T; @@ -2417,7 +2418,8 @@ call_def_function( + trystack->ga_len - 1; if (trycmd != NULL && trycmd->tcd_frame_idx == ectx.ec_frame_idx - && trycmd->tcd_finally_idx != 0) + && ectx.ec_instr[trycmd->tcd_finally_idx] + .isn_type != ISN_ENDTRY) { // jump to ":finally" ectx.ec_iidx = trycmd->tcd_finally_idx; @@ -2610,6 +2612,34 @@ call_def_function( } break; + case ISN_TRYCONT: + { + garray_T *trystack = &ectx.ec_trystack; + trycont_T *trycont = &iptr->isn_arg.trycont; + int i; + trycmd_T *trycmd; + int iidx = trycont->tct_where; + + if (trystack->ga_len < trycont->tct_levels) + { + siemsg("TRYCONT: expected %d levels, found %d", + trycont->tct_levels, trystack->ga_len); + goto failed; + } + // Make :endtry jump to any outer try block and the last + // :endtry inside the loop to the loop start. + for (i = trycont->tct_levels; i > 0; --i) + { + trycmd = ((trycmd_T *)trystack->ga_data) + + trystack->ga_len - i; + trycmd->tcd_cont = iidx; + iidx = trycmd->tcd_finally_idx; + } + // jump to :finally or :endtry of current try statement + ectx.ec_iidx = iidx; + } + break; + // end of ":try" block case ISN_ENDTRY: { @@ -2640,6 +2670,10 @@ call_def_function( --ectx.ec_stack.ga_len; clear_tv(STACK_TV_BOT(0)); } + if (trycmd->tcd_cont) + // handling :continue: jump to outer try block or + // start of the loop + ectx.ec_iidx = trycmd->tcd_cont; } } break; @@ -4213,14 +4247,27 @@ ex_disassemble(exarg_T *eap) { try_T *try = &iptr->isn_arg.try; - smsg("%4d TRY catch -> %d, finally -> %d", current, - try->try_catch, try->try_finally); + smsg("%4d TRY catch -> %d, %s -> %d", current, + try->try_catch, + instr[try->try_finally].isn_type == ISN_ENDTRY + ? "end" : "finally", + try->try_finally); } break; case ISN_CATCH: // TODO smsg("%4d CATCH", current); break; + case ISN_TRYCONT: + { + trycont_T *trycont = &iptr->isn_arg.trycont; + + smsg("%4d TRY-CONTINUE %d level%s -> %d", current, + trycont->tct_levels, + trycont->tct_levels == 1 ? "" : "s", + trycont->tct_where); + } + break; case ISN_ENDTRY: smsg("%4d ENDTRY", current); break;