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 #7827)
This commit is contained in:
		| @ -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() | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -750,6 +750,8 @@ static char *(features[]) = | ||||
|  | ||||
| static int included_patches[] = | ||||
| {   /* Add new patch number below this line */ | ||||
| /**/ | ||||
|     2506, | ||||
| /**/ | ||||
|     2505, | ||||
| /**/ | ||||
|  | ||||
							
								
								
									
										10
									
								
								src/vim9.h
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								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; | ||||
|  | ||||
| @ -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 *cctx) | ||||
| 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 *cctx) | ||||
| 	    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 | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
		Reference in New Issue
	
	Block a user