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.
diff --git a/src/vim9execute.c b/src/vim9execute.c
index a1c2f8b..2448b6d 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -673,6 +673,9 @@
     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 @@
 	    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 @@
 							- 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 @@
  * 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 @@
 	    }
 	}
 
+	// 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 @@
 	++(((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 @@
 			    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 @@
 	    // 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 @@
 			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 @@
 			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 @@
 	    // Create a global function from a lambda.
 	    case ISN_NEWFUNC:
 		{
-		    newfunc_T	*newfunc = &iptr->isn_arg.newfunc;
+		    newfuncarg_T    *arg = iptr->isn_arg.newfunc.nf_arg;
 
-		    if (copy_func(newfunc->nf_lambda, newfunc->nf_global,
-								 ectx) == FAIL)
+		    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 @@
 	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 @@
 		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 @@
 							 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 @@
 		break;
 	    case ISN_FUNCREF:
 		{
-		    funcref_T	*funcref = &iptr->isn_arg.funcref;
-		    char_u	*name;
+		    funcref_T		*funcref = &iptr->isn_arg.funcref;
+		    funcref_extra_T	*extra = funcref->fr_extra;
+		    char_u		*name;
 
-		    if (funcref->fr_func_name == NULL)
+		    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;
+		    newfuncarg_T	*arg = iptr->isn_arg.newfunc.nf_arg;
 
-		    smsg("%s%4d NEWFUNC %s %s", pfx, current,
-				       newfunc->nf_lambda, newfunc->nf_global);
+		    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;