changeset 30291:61a688be1899 v9.0.0481

patch 9.0.0481: in :def function all closures in loop get the same variables Commit: https://github.com/vim/vim/commit/8fa745e7be3a791ac25f93ef0227bbc48ade8a37 Author: Bram Moolenaar <Bram@vim.org> Date: Fri Sep 16 19:04:24 2022 +0100 patch 9.0.0481: in :def function all closures in loop get the same variables Problem: In a :def function all closures in a loop get the same variables. Solution: Use a separate list of variables for LOADOUTER and STOREOUTER. Not copied at end of loop yet.
author Bram Moolenaar <Bram@vim.org>
date Fri, 16 Sep 2022 20:15:03 +0200
parents e5d24015e8c5
children eeb2c98953bf
files src/proto/userfunc.pro src/proto/vim9cmds.pro src/proto/vim9execute.pro src/proto/vim9instr.pro src/structs.h src/userfunc.c src/version.c src/vim9.h src/vim9cmds.c src/vim9compile.c src/vim9execute.c src/vim9expr.c src/vim9instr.c
diffstat 13 files changed, 288 insertions(+), 80 deletions(-) [+]
line wrap: on
line diff
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -16,7 +16,7 @@ int func_is_global(ufunc_T *ufunc);
 int func_requires_g_prefix(ufunc_T *ufunc);
 int func_name_refcount(char_u *name);
 void func_clear_free(ufunc_T *fp, int force);
-int copy_func(char_u *lambda, char_u *global, ectx_T *ectx);
+int copy_lambda_to_global_func(char_u *lambda, char_u *global, short loop_var_idx, short loop_var_count, ectx_T *ectx);
 int funcdepth_increment(void);
 void funcdepth_decrement(void);
 int funcdepth_get(void);
--- a/src/proto/vim9cmds.pro
+++ b/src/proto/vim9cmds.pro
@@ -11,6 +11,8 @@ char_u *compile_for(char_u *arg_start, c
 char_u *compile_endfor(char_u *arg, cctx_T *cctx);
 char_u *compile_while(char_u *arg, cctx_T *cctx);
 char_u *compile_endwhile(char_u *arg, cctx_T *cctx);
+short get_loop_var_info(cctx_T *cctx, short *loop_var_idx);
+int get_loop_var_idx(cctx_T *cctx);
 char_u *compile_continue(char_u *arg, cctx_T *cctx);
 char_u *compile_break(char_u *arg, cctx_T *cctx);
 char_u *compile_block(char_u *arg, cctx_T *cctx);
--- a/src/proto/vim9execute.pro
+++ b/src/proto/vim9execute.pro
@@ -9,7 +9,7 @@ void restore_current_ectx(ectx_T *ectx);
 int add_defer_function(char_u *name, int argcount, typval_T *argvars);
 char_u *char_from_string(char_u *str, varnumber_T index);
 char_u *string_slice(char_u *str, varnumber_T first, varnumber_T last, int exclusive);
-int fill_partial_and_closure(partial_T *pt, ufunc_T *ufunc, ectx_T *ectx);
+int fill_partial_and_closure(partial_T *pt, ufunc_T *ufunc, short loop_var_idx, short loop_var_count, ectx_T *ectx);
 int may_load_script(int sid, int *loaded);
 typval_T *lookup_debug_var(char_u *name);
 int may_break_in_function(ufunc_T *ufunc);
--- a/src/proto/vim9instr.pro
+++ b/src/proto/vim9instr.pro
@@ -31,7 +31,7 @@ int generate_CHECKLEN(cctx_T *cctx, int 
 int generate_STORE(cctx_T *cctx, isntype_T isn_type, int idx, char_u *name);
 int generate_STORENR(cctx_T *cctx, int idx, varnumber_T value);
 int generate_LOAD(cctx_T *cctx, isntype_T isn_type, int idx, char_u *name, type_T *type);
-int generate_LOADOUTER(cctx_T *cctx, int idx, int nesting, type_T *type);
+int generate_LOADOUTER(cctx_T *cctx, int idx, int nesting, int loop_idx, type_T *type);
 int generate_LOADV(cctx_T *cctx, char_u *name);
 int generate_UNLET(cctx_T *cctx, isntype_T isn_type, char_u *name, int forceit);
 int generate_LOCKCONST(cctx_T *cctx);
@@ -40,7 +40,7 @@ int generate_VIM9SCRIPT(cctx_T *cctx, is
 int generate_NEWLIST(cctx_T *cctx, int count, int use_null);
 int generate_NEWDICT(cctx_T *cctx, int count, int use_null);
 int generate_FUNCREF(cctx_T *cctx, ufunc_T *ufunc, isn_T **isnp);
-int generate_NEWFUNC(cctx_T *cctx, char_u *lambda_name, char_u *func_name);
+int generate_NEWFUNC(cctx_T *cctx, char_u *lambda_name, char_u *func_name, short loop_var_idx, short loop_var_count);
 int generate_DEF(cctx_T *cctx, char_u *name, size_t len);
 int generate_JUMP(cctx_T *cctx, jumpwhen_T when, int where);
 int generate_WHILE(cctx_T *cctx, int funcref_idx);
--- a/src/structs.h
+++ b/src/structs.h
@@ -1656,7 +1656,7 @@ typedef enum {
 
 /*
  * Structure to hold info for a user function.
- * When adding a field check copy_func().
+ * When adding a field check copy_lambda_to_global_func().
  */
 typedef struct
 {
@@ -1741,7 +1741,8 @@ typedef struct
 #define FC_NOARGS   0x200	// no a: variables in lambda
 #define FC_VIM9	    0x400	// defined in vim9 script file
 #define FC_CFUNC    0x800	// defined as Lua C func
-#define FC_COPY	    0x1000	// copy of another function by copy_func()
+#define FC_COPY	    0x1000	// copy of another function by
+				// copy_lambda_to_global_func()
 #define FC_LAMBDA   0x2000	// one line "return {expr}"
 
 #define MAX_FUNC_ARGS	20	// maximum number of function arguments
@@ -2096,10 +2097,17 @@ struct funcstack_S
 
 typedef struct outer_S outer_T;
 struct outer_S {
-    garray_T	*out_stack;	    // stack from outer scope
+    garray_T	*out_stack;	    // stack from outer scope, or a copy
+				    // containing only arguments and local vars
     int		out_frame_idx;	    // index of stack frame in out_stack
     outer_T	*out_up;	    // outer scope of outer scope or NULL
     partial_T	*out_up_partial;    // partial owning out_up or NULL
+
+    garray_T	*out_loop_stack;    // stack from outer scope, or a copy
+				    // containing only vars inside the loop
+    short	out_loop_var_idx;   // first variable defined in a loop
+				    // in out_loop_stack
+    short	out_loop_var_count; // number of variables defined in a loop
 };
 
 struct partial_S
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -2452,7 +2452,12 @@ func_clear_free(ufunc_T *fp, int force)
  * This is for when a compiled function defines a global function.
  */
     int
-copy_func(char_u *lambda, char_u *global, ectx_T *ectx)
+copy_lambda_to_global_func(
+	char_u	*lambda,
+	char_u	*global,
+	short	loop_var_idx,
+	short	loop_var_count,
+	ectx_T	*ectx)
 {
     ufunc_T *ufunc = find_func_even_dead(lambda, FFED_IS_GLOBAL);
     ufunc_T *fp = NULL;
@@ -2519,7 +2524,8 @@ copy_func(char_u *lambda, char_u *global
 
 	if (pt == NULL)
 	    goto failed;
-	if (fill_partial_and_closure(pt, ufunc, ectx) == FAIL)
+	if (fill_partial_and_closure(pt, ufunc, loop_var_idx, loop_var_count,
+								 ectx) == FAIL)
 	{
 	    vim_free(pt);
 	    goto failed;
--- a/src/version.c
+++ b/src/version.c
@@ -704,6 +704,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    481,
+/**/
     480,
 /**/
     479,
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -354,16 +354,29 @@ typedef struct {
     int		ul_forceit;	// forceit flag
 } unlet_T;
 
+// extra arguments for funcref_T
+typedef struct {
+    char_u	*fre_func_name;	    // function name for legacy function
+    short	fre_loop_var_idx;   // index of first variable inside loop
+    short	fre_loop_var_count; // number of variables inside loop
+} funcref_extra_T;
+
 // arguments to ISN_FUNCREF
 typedef struct {
-    int		fr_dfunc_idx;	// function index for :def function
-    char_u	*fr_func_name;	// function name for legacy function
+    int		    fr_dfunc_idx;   // function index for :def function
+    funcref_extra_T *fr_extra;	    // optional extra information
 } funcref_T;
 
 // arguments to ISN_NEWFUNC
 typedef struct {
-    char_u	*nf_lambda;	// name of the lambda already defined
-    char_u	*nf_global;	// name of the global function to be created
+    char_u	*nfa_lambda;	   // name of the lambda already defined
+    char_u	*nfa_global;	   // name of the global function to be created
+    short	nfa_loop_var_idx;    // index of first variable inside loop
+    short	nfa_loop_var_count;  // number of variables inside loop
+} newfuncarg_T;
+
+typedef struct {
+    newfuncarg_T *nf_arg;
 } newfunc_T;
 
 // arguments to ISN_CHECKLEN
@@ -401,6 +414,8 @@ typedef struct {
     int		outer_depth;	// nesting level, stack frames to go up
 } isn_outer_T;
 
+#define OUTER_LOOP_DEPTH -9	// used for outer_depth for loop variables
+
 // arguments to ISN_SUBSTITUTE
 typedef struct {
     char_u	*subs_cmd;	// :s command
@@ -677,6 +692,7 @@ typedef struct {
     char_u	*lv_name;
     type_T	*lv_type;
     int		lv_idx;		// index of the variable on the stack
+    int		lv_loop_idx;	// index of first variable inside a loop or -1
     int		lv_from_outer;	// nesting level, using ctx_outer scope
     int		lv_const;	// when TRUE cannot be assigned to
     int		lv_arg;		// when TRUE this is an argument
--- a/src/vim9cmds.c
+++ b/src/vim9cmds.c
@@ -1246,6 +1246,49 @@ 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.
+ */
+    short
+get_loop_var_info(cctx_T *cctx, short *loop_var_idx)
+{
+    scope_T	*scope = cctx->ctx_scope;
+    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;
+
+    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;
+	return (short)(cctx->ctx_locals.ga_len - start_local_count);
+    }
+    return 0;
+}
+
+/*
+ * Get the index of the first variable in a loop, if any.
+ * Returns -1 if none.
+ */
+    int
+get_loop_var_idx(cctx_T *cctx)
+{
+    short loop_var_idx;
+
+    if (get_loop_var_info(cctx, &loop_var_idx) > 0)
+	return loop_var_idx;
+    return -1;
+}
+
+/*
  * compile "continue"
  */
     char_u *
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -54,6 +54,7 @@ lookup_local(char_u *name, size_t len, l
 	    {
 		*lvar = *lvp;
 		lvar->lv_from_outer = 0;
+		lvar->lv_loop_idx = get_loop_var_idx(cctx);
 	    }
 	    return OK;
 	}
@@ -954,7 +955,8 @@ compile_nested_function(exarg_T *eap, cc
     // recursive call.
     if (is_global)
     {
-	r = generate_NEWFUNC(cctx, lambda_name, func_name);
+	// TODO: loop variable index and count
+	r = generate_NEWFUNC(cctx, lambda_name, func_name, 0, 0);
 	func_name = NULL;
 	lambda_name = NULL;
     }
@@ -1193,7 +1195,7 @@ generate_loadvar(
 	    {
 		if (lvar->lv_from_outer > 0)
 		    generate_LOADOUTER(cctx, lvar->lv_idx, lvar->lv_from_outer,
-									 type);
+						      lvar->lv_loop_idx, type);
 		else
 		    generate_LOAD(cctx, ISN_LOAD, lvar->lv_idx, NULL, type);
 	    }
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -673,6 +673,9 @@ handle_closure_in_use(ectx_T *ectx, int 
     if (closure_count == 0)
 	return OK;  // no funcrefs created
 
+    // Compute "top": the first entry in the stack used by the function.
+    // This is the first argument (after that comes the stack frame and then
+    // the local variables).
     argcount = ufunc_argcount(dfunc->df_ufunc);
     top = ectx->ec_frame_idx - argcount;
 
@@ -740,6 +743,7 @@ handle_closure_in_use(ectx_T *ectx, int 
 	    else
 		copy_tv(tv, stack + idx);
 	}
+	// Skip the stack frame.
 	// Move the local variables.
 	for (idx = 0; idx < dfunc->df_varcount; ++idx)
 	{
@@ -770,10 +774,17 @@ handle_closure_in_use(ectx_T *ectx, int 
 							- closure_count + idx];
 	    if (pt->pt_refcount > 1)
 	    {
+		int	prev_frame_idx = pt->pt_outer.out_frame_idx;
+
 		++funcstack->fs_refcount;
 		pt->pt_funcstack = funcstack;
 		pt->pt_outer.out_stack = &funcstack->fs_ga;
 		pt->pt_outer.out_frame_idx = ectx->ec_frame_idx - top;
+
+		// TODO: drop this, should be done at ISN_ENDLOOP
+		pt->pt_outer.out_loop_stack = &funcstack->fs_ga;
+		pt->pt_outer.out_loop_var_idx -=
+				   prev_frame_idx - pt->pt_outer.out_frame_idx;
 	    }
 	}
     }
@@ -1814,7 +1825,12 @@ call_eval_func(
  * needed, especially when it is used as a closure.
  */
     int
-fill_partial_and_closure(partial_T *pt, ufunc_T *ufunc, ectx_T *ectx)
+fill_partial_and_closure(
+	partial_T   *pt,
+	ufunc_T	    *ufunc,
+	short	    loop_var_idx,
+	short	    loop_var_count,
+	ectx_T	    *ectx)
 {
     pt->pt_func = ufunc;
     pt->pt_refcount = 1;
@@ -1839,6 +1855,14 @@ fill_partial_and_closure(partial_T *pt, 
 	    }
 	}
 
+	// The closure may need to find variables defined inside a loop.  A
+	// new reference is made every time, ISN_ENDLOOP will check if they
+	// are actually used.
+	pt->pt_outer.out_loop_stack = &ectx->ec_stack;
+	pt->pt_outer.out_loop_var_idx = ectx->ec_frame_idx + STACK_FRAME_SIZE
+								+ loop_var_idx;
+	pt->pt_outer.out_loop_var_count = loop_var_count;
+
 	// If the function currently executing returns and the closure is still
 	// being referenced, we need to make a copy of the context (arguments
 	// and local variables) so that the closure can use it later.
@@ -1853,8 +1877,8 @@ fill_partial_and_closure(partial_T *pt, 
 	++(((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_frame_idx
 		       + STACK_FRAME_SIZE + dfunc->df_varcount)->vval.v_number;
 
-	((partial_T **)ectx->ec_funcrefs.ga_data)
-			       [ectx->ec_funcrefs.ga_len] = pt;
+	((partial_T **)ectx->ec_funcrefs.ga_data)[ectx->ec_funcrefs.ga_len]
+									  = pt;
 	++pt->pt_refcount;
 	++ectx->ec_funcrefs.ga_len;
     }
@@ -3610,9 +3634,15 @@ exec_instructions(ectx_T *ectx)
 			    iemsg("LOADOUTER depth more than scope levels");
 			goto theend;
 		    }
-		    tv = ((typval_T *)outer->out_stack->ga_data)
-				    + outer->out_frame_idx + STACK_FRAME_SIZE
-				    + iptr->isn_arg.outer.outer_idx;
+		    if (depth == OUTER_LOOP_DEPTH)
+			// variable declared in loop
+			tv = ((typval_T *)outer->out_loop_stack->ga_data)
+					    + outer->out_loop_var_idx
+					    + iptr->isn_arg.outer.outer_idx;
+		    else
+			tv = ((typval_T *)outer->out_stack->ga_data)
+				      + outer->out_frame_idx + STACK_FRAME_SIZE
+				      + iptr->isn_arg.outer.outer_idx;
 		    if (iptr->isn_type == ISN_LOADOUTER)
 		    {
 			if (GA_GROW_FAILS(&ectx->ec_stack, 1))
@@ -3913,9 +3943,10 @@ exec_instructions(ectx_T *ectx)
 	    // push a partial, a reference to a compiled function
 	    case ISN_FUNCREF:
 		{
-		    partial_T   *pt = ALLOC_CLEAR_ONE(partial_T);
-		    ufunc_T	*ufunc;
-		    funcref_T	*funcref = &iptr->isn_arg.funcref;
+		    partial_T	    *pt = ALLOC_CLEAR_ONE(partial_T);
+		    ufunc_T	    *ufunc;
+		    funcref_T	    *funcref = &iptr->isn_arg.funcref;
+		    funcref_extra_T *extra = funcref->fr_extra;
 
 		    if (pt == NULL)
 			goto theend;
@@ -3924,7 +3955,7 @@ exec_instructions(ectx_T *ectx)
 			vim_free(pt);
 			goto theend;
 		    }
-		    if (funcref->fr_func_name == NULL)
+		    if (extra == NULL || extra->fre_func_name == NULL)
 		    {
 			dfunc_T	*pt_dfunc = ((dfunc_T *)def_functions.ga_data)
 						       + funcref->fr_dfunc_idx;
@@ -3932,16 +3963,17 @@ exec_instructions(ectx_T *ectx)
 			ufunc = pt_dfunc->df_ufunc;
 		    }
 		    else
-		    {
-			ufunc = find_func(funcref->fr_func_name, FALSE);
-		    }
+			ufunc = find_func(extra->fre_func_name, FALSE);
 		    if (ufunc == NULL)
 		    {
 			SOURCING_LNUM = iptr->isn_lnum;
 			iemsg("ufunc unexpectedly NULL for FUNCREF");
 			goto theend;
 		    }
-		    if (fill_partial_and_closure(pt, ufunc, ectx) == FAIL)
+		    if (fill_partial_and_closure(pt, ufunc,
+				extra == NULL ? 0 : extra->fre_loop_var_idx,
+				extra == NULL ? 0 : extra->fre_loop_var_count,
+								 ectx) == FAIL)
 			goto theend;
 		    tv = STACK_TV_BOT(0);
 		    ++ectx->ec_stack.ga_len;
@@ -3954,10 +3986,11 @@ exec_instructions(ectx_T *ectx)
 	    // Create a global function from a lambda.
 	    case ISN_NEWFUNC:
 		{
-		    newfunc_T	*newfunc = &iptr->isn_arg.newfunc;
-
-		    if (copy_func(newfunc->nf_lambda, newfunc->nf_global,
-								 ectx) == FAIL)
+		    newfuncarg_T    *arg = iptr->isn_arg.newfunc.nf_arg;
+
+		    if (copy_lambda_to_global_func(arg->nfa_lambda,
+					arg->nfa_global, arg->nfa_loop_var_idx,
+					arg->nfa_loop_var_count, ectx) == FAIL)
 			goto theend;
 		}
 		break;
@@ -5520,7 +5553,7 @@ call_def_function(
 	ufunc_T *base_ufunc = dfunc->df_ufunc;
 
 	// "uf_partial" is on the ufunc that "df_ufunc" points to, as is done
-	// by copy_func().
+	// by copy_lambda_to_global_func().
 	if (partial != NULL || base_ufunc->uf_partial != NULL)
 	{
 	    ectx.ec_outer_ref = ALLOC_CLEAR_ONE(outer_ref_T);
@@ -5880,15 +5913,20 @@ list_instructions(char *pfx, isn_T *inst
 		break;
 	    case ISN_LOADOUTER:
 		{
-		    if (iptr->isn_arg.outer.outer_idx < 0)
+		    isn_outer_T *outer = &iptr->isn_arg.outer;
+
+		    if (outer->outer_idx < 0)
 			smsg("%s%4d LOADOUTER level %d arg[%d]", pfx, current,
-				iptr->isn_arg.outer.outer_depth,
-				iptr->isn_arg.outer.outer_idx
+				outer->outer_depth,
+				outer->outer_idx
 							  + STACK_FRAME_SIZE);
+		    else if (outer->outer_depth == OUTER_LOOP_DEPTH)
+			smsg("%s%4d LOADOUTER level 1 $%d in loop",
+					       pfx, current, outer->outer_idx);
 		    else
 			smsg("%s%4d LOADOUTER level %d $%d", pfx, current,
-					      iptr->isn_arg.outer.outer_depth,
-					      iptr->isn_arg.outer.outer_idx);
+					      outer->outer_depth,
+					      outer->outer_idx);
 		}
 		break;
 	    case ISN_LOADV:
@@ -5971,9 +6009,16 @@ list_instructions(char *pfx, isn_T *inst
 							 iptr->isn_arg.number);
 		break;
 	    case ISN_STOREOUTER:
-		smsg("%s%4d STOREOUTER level %d $%d", pfx, current,
-			iptr->isn_arg.outer.outer_depth,
-			iptr->isn_arg.outer.outer_idx);
+		{
+		    isn_outer_T *outer = &iptr->isn_arg.outer;
+
+		    if (outer->outer_depth == OUTER_LOOP_DEPTH)
+			smsg("%s%4d STOREOUTER level 1 $%d in loop",
+				pfx, current, outer->outer_idx);
+		    else
+			smsg("%s%4d STOREOUTER level %d $%d", pfx, current,
+				outer->outer_depth, outer->outer_idx);
+		}
 		break;
 	    case ISN_STOREV:
 		smsg("%s%4d STOREV v:%s", pfx, current,
@@ -6190,27 +6235,41 @@ list_instructions(char *pfx, isn_T *inst
 		break;
 	    case ISN_FUNCREF:
 		{
-		    funcref_T	*funcref = &iptr->isn_arg.funcref;
-		    char_u	*name;
-
-		    if (funcref->fr_func_name == NULL)
+		    funcref_T		*funcref = &iptr->isn_arg.funcref;
+		    funcref_extra_T	*extra = funcref->fr_extra;
+		    char_u		*name;
+
+		    if (extra == NULL || extra->fre_func_name == NULL)
 		    {
 			dfunc_T	*df = ((dfunc_T *)def_functions.ga_data)
 						       + funcref->fr_dfunc_idx;
 			name = df->df_ufunc->uf_name;
 		    }
 		    else
-			name = funcref->fr_func_name;
-		    smsg("%s%4d FUNCREF %s", pfx, current, name);
+			name = extra->fre_func_name;
+		    if (extra == NULL || extra->fre_loop_var_count == 0)
+			smsg("%s%4d FUNCREF %s", pfx, current, name);
+		    else
+			smsg("%s%4d FUNCREF %s var $%d - $%d", pfx, current,
+				name,
+				extra->fre_loop_var_idx,
+				extra->fre_loop_var_idx
+					      + extra->fre_loop_var_count - 1);
 		}
 		break;
 
 	    case ISN_NEWFUNC:
 		{
-		    newfunc_T	*newfunc = &iptr->isn_arg.newfunc;
-
-		    smsg("%s%4d NEWFUNC %s %s", pfx, current,
-				       newfunc->nf_lambda, newfunc->nf_global);
+		    newfuncarg_T	*arg = iptr->isn_arg.newfunc.nf_arg;
+
+		    if (arg->nfa_loop_var_count == 0)
+			smsg("%s%4d NEWFUNC %s %s", pfx, current,
+					     arg->nfa_lambda, arg->nfa_global);
+		    else
+			smsg("%s%4d NEWFUNC %s %s var $%d - $%d", pfx, current,
+			  arg->nfa_lambda, arg->nfa_global,
+			  arg->nfa_loop_var_idx,
+			  arg->nfa_loop_var_idx + arg->nfa_loop_var_count - 1);
 		}
 		break;
 
--- a/src/vim9expr.c
+++ b/src/vim9expr.c
@@ -496,6 +496,7 @@ compile_load(
 	int	    idx;
 	int	    gen_load = FALSE;
 	int	    gen_load_outer = 0;
+	int	    outer_loop_idx = -1;
 
 	name = vim_strnsave(*arg, end - *arg);
 	if (name == NULL)
@@ -520,6 +521,7 @@ compile_load(
 	    {
 		type = lvar.lv_type;
 		idx = lvar.lv_idx;
+		outer_loop_idx = lvar.lv_loop_idx;
 		if (lvar.lv_from_outer != 0)
 		    gen_load_outer = lvar.lv_from_outer;
 		else
@@ -544,7 +546,8 @@ compile_load(
 	    res = generate_LOAD(cctx, ISN_LOAD, idx, NULL, type);
 	if (gen_load_outer > 0)
 	{
-	    res = generate_LOADOUTER(cctx, idx, gen_load_outer, type);
+	    res = generate_LOADOUTER(cctx, idx,
+					 gen_load_outer, outer_loop_idx, type);
 	    cctx->ctx_outer_used = TRUE;
 	}
     }
--- a/src/vim9instr.c
+++ b/src/vim9instr.c
@@ -916,15 +916,25 @@ generate_STORE(cctx_T *cctx, isntype_T i
  * Generate an ISN_STOREOUTER instruction.
  */
     static int
-generate_STOREOUTER(cctx_T *cctx, int idx, int level)
+generate_STOREOUTER(cctx_T *cctx, int idx, int level, int loop_idx)
 {
     isn_T	*isn;
 
     RETURN_OK_IF_SKIP(cctx);
     if ((isn = generate_instr_drop(cctx, ISN_STOREOUTER, 1)) == NULL)
 	return FAIL;
-    isn->isn_arg.outer.outer_idx = idx;
-    isn->isn_arg.outer.outer_depth = level;
+    if (level == 1 && loop_idx >= 0 && idx >= loop_idx)
+    {
+	// Store a variable defined in a loop.  A copy will be made at the end
+	// of the loop.  TODO: how about deeper nesting?
+	isn->isn_arg.outer.outer_idx = idx - loop_idx;
+	isn->isn_arg.outer.outer_depth = OUTER_LOOP_DEPTH;
+    }
+    else
+    {
+	isn->isn_arg.outer.outer_idx = idx;
+	isn->isn_arg.outer.outer_depth = level;
+    }
 
     return OK;
 }
@@ -999,6 +1009,7 @@ generate_LOADOUTER(
 	cctx_T	    *cctx,
 	int	    idx,
 	int	    nesting,
+	int	    loop_idx,
 	type_T	    *type)
 {
     isn_T	*isn;
@@ -1006,8 +1017,18 @@ generate_LOADOUTER(
     RETURN_OK_IF_SKIP(cctx);
     if ((isn = generate_instr_type2(cctx, ISN_LOADOUTER, type, type)) == NULL)
 	return FAIL;
-    isn->isn_arg.outer.outer_idx = idx;
-    isn->isn_arg.outer.outer_depth = nesting;
+    if (nesting == 1 && loop_idx >= 0 && idx >= loop_idx)
+    {
+	// Load a variable defined in a loop.  A copy will be made at the end
+	// of the loop.  TODO: how about deeper nesting?
+	isn->isn_arg.outer.outer_idx = idx - loop_idx;
+	isn->isn_arg.outer.outer_depth = OUTER_LOOP_DEPTH;
+    }
+    else
+    {
+	isn->isn_arg.outer.outer_idx = idx;
+	isn->isn_arg.outer.outer_depth = nesting;
+    }
 
     return OK;
 }
@@ -1186,20 +1207,39 @@ generate_NEWDICT(cctx_T *cctx, int count
 /*
  * Generate an ISN_FUNCREF instruction.
  * "isnp" is set to the instruction, so that fr_dfunc_idx can be set later.
+ * If variables were declared inside a loop "loop_var_idx" is the index of the
+ * first one and "loop_var_count" the number of variables declared.
  */
     int
-generate_FUNCREF(cctx_T *cctx, ufunc_T *ufunc, isn_T **isnp)
+generate_FUNCREF(
+	cctx_T	    *cctx,
+	ufunc_T	    *ufunc,
+	isn_T	    **isnp)
 {
-    isn_T	*isn;
-    type_T	*type;
+    isn_T	    *isn;
+    type_T	    *type;
+    funcref_extra_T *extra;
+    short	    loop_var_idx;
+    short	    loop_var_count;
 
     RETURN_OK_IF_SKIP(cctx);
     if ((isn = generate_instr(cctx, ISN_FUNCREF)) == NULL)
 	return FAIL;
     if (isnp != NULL)
 	*isnp = isn;
+
+    loop_var_count = get_loop_var_info(cctx, &loop_var_idx);
+    if (ufunc->uf_def_status == UF_NOT_COMPILED || loop_var_count > 0)
+    {
+	extra = ALLOC_CLEAR_ONE(funcref_extra_T);
+	if (extra == NULL)
+	    return FAIL;
+	isn->isn_arg.funcref.fr_extra = extra;
+	extra->fre_loop_var_idx = loop_var_idx;
+	extra->fre_loop_var_count = loop_var_count;
+    }
     if (ufunc->uf_def_status == UF_NOT_COMPILED)
-	isn->isn_arg.funcref.fr_func_name = vim_strsave(ufunc->uf_name);
+	extra->fre_func_name = vim_strsave(ufunc->uf_name);
     else
 	isn->isn_arg.funcref.fr_dfunc_idx = ufunc->uf_dfunc_idx;
     cctx->ctx_has_closure = 1;
@@ -1221,7 +1261,12 @@ generate_FUNCREF(cctx_T *cctx, ufunc_T *
  * consumed.
  */
     int
-generate_NEWFUNC(cctx_T *cctx, char_u *lambda_name, char_u *func_name)
+generate_NEWFUNC(
+	cctx_T	*cctx,
+	char_u	*lambda_name,
+	char_u	*func_name,
+	short	loop_var_idx,
+	short	loop_var_count)
 {
     isn_T	*isn;
     int		ret = OK;
@@ -1232,9 +1277,19 @@ generate_NEWFUNC(cctx_T *cctx, char_u *l
 	    ret = FAIL;
 	else
 	{
-	    isn->isn_arg.newfunc.nf_lambda = lambda_name;
-	    isn->isn_arg.newfunc.nf_global = func_name;
-	    return OK;
+	    newfuncarg_T *arg = ALLOC_CLEAR_ONE(newfuncarg_T);
+
+	    if (arg == NULL)
+		ret = FAIL;
+	    else
+	    {
+		isn->isn_arg.newfunc.nf_arg = arg;
+		arg->nfa_lambda = lambda_name;
+		arg->nfa_global = func_name;
+		arg->nfa_loop_var_idx = loop_var_idx;
+		arg->nfa_loop_var_count = loop_var_count;
+		return OK;
+	    }
 	}
     }
     vim_free(lambda_name);
@@ -2123,7 +2178,7 @@ generate_store_lhs(cctx_T *cctx, lhs_T *
 	}
 	else if (lhs->lhs_lvar->lv_from_outer > 0)
 	    generate_STOREOUTER(cctx, lhs->lhs_lvar->lv_idx,
-						 lhs->lhs_lvar->lv_from_outer);
+		     lhs->lhs_lvar->lv_from_outer, lhs->lhs_lvar->lv_loop_idx);
 	else
 	    generate_STORE(cctx, ISN_STORE, lhs->lhs_lvar->lv_idx, NULL);
     }
@@ -2226,22 +2281,28 @@ delete_instr(isn_T *isn)
 
 	case ISN_FUNCREF:
 	    {
-		if (isn->isn_arg.funcref.fr_func_name == NULL)
+		funcref_T	*funcref = &isn->isn_arg.funcref;
+		funcref_extra_T *extra = funcref->fr_extra;
+
+		if (extra == NULL || extra->fre_func_name == NULL)
 		{
 		    dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
-			+ isn->isn_arg.funcref.fr_dfunc_idx;
+						       + funcref->fr_dfunc_idx;
 		    ufunc_T *ufunc = dfunc->df_ufunc;
 
 		    if (ufunc != NULL && func_name_refcount(ufunc->uf_name))
 			func_ptr_unref(ufunc);
 		}
-		else
+		if (extra != NULL)
 		{
-		    char_u *name = isn->isn_arg.funcref.fr_func_name;
+		    char_u *name = extra->fre_func_name;
 
 		    if (name != NULL)
+		    {
 			func_unref(name);
-		    vim_free(isn->isn_arg.funcref.fr_func_name);
+			vim_free(name);
+		    }
+		    vim_free(extra);
 		}
 	    }
 	    break;
@@ -2259,17 +2320,23 @@ delete_instr(isn_T *isn)
 
 	case ISN_NEWFUNC:
 	    {
-		char_u  *lambda = isn->isn_arg.newfunc.nf_lambda;
-		ufunc_T *ufunc = find_func_even_dead(lambda, FFED_IS_GLOBAL);
+		newfuncarg_T *arg = isn->isn_arg.newfunc.nf_arg;
 
-		if (ufunc != NULL)
+		if (arg != NULL)
 		{
-		    unlink_def_function(ufunc);
-		    func_ptr_unref(ufunc);
+		    ufunc_T *ufunc = find_func_even_dead(
+					      arg->nfa_lambda, FFED_IS_GLOBAL);
+
+		    if (ufunc != NULL)
+		    {
+			unlink_def_function(ufunc);
+			func_ptr_unref(ufunc);
+		    }
+
+		    vim_free(arg->nfa_lambda);
+		    vim_free(arg->nfa_global);
+		    vim_free(arg);
 		}
-
-		vim_free(lambda);
-		vim_free(isn->isn_arg.newfunc.nf_global);
 	    }
 	    break;