diff src/vim9cmds.c @ 30333:fc0830246f49 v9.0.0502

patch 9.0.0502: a closure in a nested loop in a :def function does not work Commit: https://github.com/vim/vim/commit/cc34181f9994d64f8c8fa2f5845eaf0cc963067f Author: Bram Moolenaar <Bram@vim.org> Date: Mon Sep 19 15:54:34 2022 +0100 patch 9.0.0502: a closure in a nested loop in a :def function does not work Problem: A closure in a nested loop in a :def function does not work. Solution: Use an array of loopvars, one per loop level.
author Bram Moolenaar <Bram@vim.org>
date Mon, 19 Sep 2022 17:00:07 +0200
parents bee38b1d323c
children ee039a6049ff
line wrap: on
line diff
--- a/src/vim9cmds.c
+++ b/src/vim9cmds.c
@@ -347,6 +347,8 @@ new_scope(cctx_T *cctx, scopetype_T type
     cctx->ctx_scope = scope;
     scope->se_type = type;
     scope->se_local_count = cctx->ctx_locals.ga_len;
+    if (scope->se_outer != NULL)
+	scope->se_loop_depth = scope->se_outer->se_loop_depth;
     return scope;
 }
 
@@ -823,7 +825,9 @@ compile_for(char_u *arg_start, cctx_T *c
     scope_T	*scope;
     forscope_T	*forscope;
     lvar_T	*loop_lvar;	// loop iteration variable
+    int		loop_lvar_idx;
     lvar_T	*funcref_lvar;
+    int		funcref_lvar_idx;
     lvar_T	*var_lvar;	// variable for "var"
     type_T	*vartype;
     type_T	*item_type = &t_any;
@@ -867,6 +871,12 @@ compile_for(char_u *arg_start, cctx_T *c
     scope = new_scope(cctx, FOR_SCOPE);
     if (scope == NULL)
 	return NULL;
+    if (scope->se_loop_depth == MAX_LOOP_DEPTH)
+    {
+	emsg(_(e_loop_nesting_too_deep));
+	return NULL;
+    }
+    ++scope->se_loop_depth;
     forscope = &scope->se_u.se_for;
 
     // Reserve a variable to store the loop iteration counter and initialize it
@@ -877,7 +887,9 @@ compile_for(char_u *arg_start, cctx_T *c
 	drop_scope(cctx);
 	return NULL;  // out of memory
     }
-    generate_STORENR(cctx, loop_lvar->lv_idx, -1);
+    // get the index before a following reserve_local() makes the lval invalid
+    loop_lvar_idx = loop_lvar->lv_idx;
+    generate_STORENR(cctx, loop_lvar_idx, -1);
 
     // Reserve a variable to store ec_funcrefs.ga_len, used in ISN_ENDLOOP.
     // The variable index is always the loop var index plus one.
@@ -888,6 +900,8 @@ compile_for(char_u *arg_start, cctx_T *c
 	drop_scope(cctx);
 	return NULL;  // out of memory
     }
+    // get the index before a following reserve_local() makes the lval invalid
+    funcref_lvar_idx = funcref_lvar->lv_idx;
 
     // compile "expr", it remains on the stack until "endfor"
     arg = p;
@@ -951,7 +965,7 @@ compile_for(char_u *arg_start, cctx_T *c
 	    cctx->ctx_prev_lnum = save_prev_lnum;
 	}
 
-	generate_FOR(cctx, loop_lvar->lv_idx);
+	generate_FOR(cctx, loop_lvar_idx);
 
 	arg = arg_start;
 	if (var_list)
@@ -1053,8 +1067,8 @@ compile_for(char_u *arg_start, cctx_T *c
 	}
 
 	// remember the number of variables and closures, used for ENDLOOP
-	compile_fill_loop_info(&forscope->fs_loop_info,
-						   funcref_lvar->lv_idx, cctx);
+	compile_fill_loop_info(&forscope->fs_loop_info, funcref_lvar_idx, cctx);
+	forscope->fs_loop_info.li_depth = scope->se_loop_depth - 1;
     }
 
     return arg_end;
@@ -1075,8 +1089,7 @@ compile_loop_end(loop_info_T *loop_info,
 {
     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 generate_ENDLOOP(cctx, loop_info);
     return OK;
 }
 
@@ -1151,10 +1164,17 @@ compile_while(char_u *arg, cctx_T *cctx)
     scope_T	    *scope;
     whilescope_T    *whilescope;
     lvar_T	    *funcref_lvar;
+    int		    funcref_lvar_idx;
 
     scope = new_scope(cctx, WHILE_SCOPE);
     if (scope == NULL)
 	return NULL;
+    if (scope->se_loop_depth == MAX_LOOP_DEPTH)
+    {
+	emsg(_(e_loop_nesting_too_deep));
+	return NULL;
+    }
+    ++scope->se_loop_depth;
     whilescope = &scope->se_u.se_while;
 
     // "endwhile" jumps back here, one before when profiling or using cmdmods
@@ -1168,10 +1188,12 @@ compile_while(char_u *arg, cctx_T *cctx)
 	drop_scope(cctx);
 	return NULL;  // out of memory
     }
+    // get the index before a following reserve_local() makes the lval invalid
+    funcref_lvar_idx = funcref_lvar->lv_idx;
 
     // remember the number of variables and closures, used for ENDLOOP
-    compile_fill_loop_info(&whilescope->ws_loop_info,
-						   funcref_lvar->lv_idx, cctx);
+    compile_fill_loop_info(&whilescope->ws_loop_info, funcref_lvar_idx, cctx);
+    whilescope->ws_loop_info.li_depth = scope->se_loop_depth - 1;
 
     // compile "expr"
     if (compile_expr0(&p, cctx) == FAIL)
@@ -1193,7 +1215,7 @@ compile_while(char_u *arg, cctx_T *cctx)
 
 	// "while_end" is set when ":endwhile" is found
 	if (compile_jump_to_end(&whilescope->ws_end_label,
-			 JUMP_WHILE_FALSE, funcref_lvar->lv_idx, cctx) == FAIL)
+			     JUMP_WHILE_FALSE, funcref_lvar_idx, cctx) == FAIL)
 	    return FAIL;
     }
 
@@ -1249,45 +1271,83 @@ compile_endwhile(char_u *arg, cctx_T *cc
 
 /*
  * Get the current information about variables declared inside a loop.
- * Returns zero if there are none, otherwise the count.
- * "loop_var_idx" is then set to the index of the first variable.
+ * Returns TRUE if there are any and fills "lvi".
  */
-    short
-get_loop_var_info(cctx_T *cctx, short *loop_var_idx)
+    int
+get_loop_var_info(cctx_T *cctx, loopvarinfo_T *lvi)
 {
     scope_T	*scope = cctx->ctx_scope;
-    int		start_local_count;
+    int		prev_local_count = 0;
 
-    while (scope != NULL && scope->se_type != WHILE_SCOPE
+    CLEAR_POINTER(lvi);
+    for (;;)
+    {
+	loop_info_T	*loopinfo;
+	int		cur_local_last;
+	int		start_local_count;
+
+	while (scope != NULL && scope->se_type != WHILE_SCOPE
 						&& scope->se_type != FOR_SCOPE)
-	scope = scope->se_outer;
-    if (scope == NULL)
-	return 0;
+	    scope = scope->se_outer;
+	if (scope == NULL)
+	    break;
 
-    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;
-	return (short)(cctx->ctx_locals.ga_len - start_local_count);
+	if (scope->se_type == WHILE_SCOPE)
+	{
+	    loopinfo = &scope->se_u.se_while.ws_loop_info;
+	    // :while reserves one variable for funcref count
+	    cur_local_last = loopinfo->li_local_count - 1;
+	}
+	else
+	{
+	    loopinfo = &scope->se_u.se_for.fs_loop_info;
+	    // :for reserves three variable: loop count, funcref count and loop
+	    // var
+	    cur_local_last = loopinfo->li_local_count - 3;
+	}
+
+	start_local_count = loopinfo->li_local_count;
+	if (cctx->ctx_locals.ga_len > start_local_count)
+	{
+	    lvi->lvi_loop[loopinfo->li_depth].var_idx =
+						      (short)start_local_count;
+	    lvi->lvi_loop[loopinfo->li_depth].var_count =
+			  (short)(cctx->ctx_locals.ga_len - start_local_count
+							   - prev_local_count);
+	    if (lvi->lvi_depth == 0)
+		lvi->lvi_depth = loopinfo->li_depth + 1;
+	}
+
+	scope = scope->se_outer;
+	prev_local_count = cctx->ctx_locals.ga_len - cur_local_last;
     }
-    return 0;
+    return lvi->lvi_depth > 0;
 }
 
 /*
- * Get the index of the first variable in a loop, if any.
- * Returns -1 if none.
+ * Get the index of the variable "idx" in a loop, if any.
  */
-    int
-get_loop_var_idx(cctx_T *cctx)
+    void
+get_loop_var_idx(cctx_T *cctx, int idx, lvar_T *lvar)
 {
-    short loop_var_idx;
+    loopvarinfo_T lvi;
+
+    lvar->lv_loop_depth = -1;
+    lvar->lv_loop_idx = -1;
+    if (get_loop_var_info(cctx, &lvi))
+    {
+	int depth;
 
-    if (get_loop_var_info(cctx, &loop_var_idx) > 0)
-	return loop_var_idx;
-    return -1;
+	for (depth = lvi.lvi_depth - 1; depth >= 0; --depth)
+	    if (idx >= lvi.lvi_loop[depth].var_idx
+		    && idx < lvi.lvi_loop[depth].var_idx
+					       + lvi.lvi_loop[depth].var_count)
+	    {
+		lvar->lv_loop_depth = depth;
+		lvar->lv_loop_idx = lvi.lvi_loop[depth].var_idx;
+		return;
+	    }
+    }
 }
 
 /*