0
0
mirror of https://github.com/vim/vim.git synced 2025-09-24 03:44:06 -04:00

patch 9.0.0470: 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:   When in a loop and a closure refers to a variable declared in the
            loop, prepare for making a copy of variables for each closure.
This commit is contained in:
Bram Moolenaar
2022-09-15 17:19:37 +01:00
parent 3735f11050
commit b46c083a5e
8 changed files with 315 additions and 58 deletions

View File

@@ -278,10 +278,15 @@ compile_unletlock(char_u *arg, exarg_T *eap, cctx_T *cctx)
}
/*
* generate a jump to the ":endif"/":endfor"/":endwhile"/":finally"/":endtry".
* Generate a jump to the ":endif"/":endfor"/":endwhile"/":finally"/":endtry".
* "funcref_idx" is used for JUMP_WHILE_FALSE
*/
static int
compile_jump_to_end(endlabel_T **el, jumpwhen_T when, cctx_T *cctx)
compile_jump_to_end(
endlabel_T **el,
jumpwhen_T when,
int funcref_idx,
cctx_T *cctx)
{
garray_T *instr = &cctx->ctx_instr;
endlabel_T *endlabel = ALLOC_CLEAR_ONE(endlabel_T);
@@ -292,7 +297,10 @@ compile_jump_to_end(endlabel_T **el, jumpwhen_T when, cctx_T *cctx)
*el = endlabel;
endlabel->el_end_label = instr->ga_len;
generate_JUMP(cctx, when, 0);
if (when == JUMP_WHILE_FALSE)
generate_WHILE(cctx, funcref_idx);
else
generate_JUMP(cctx, when, 0);
return OK;
}
@@ -564,7 +572,7 @@ compile_elseif(char_u *arg, cctx_T *cctx)
}
if (compile_jump_to_end(&scope->se_u.se_if.is_end_label,
JUMP_ALWAYS, cctx) == FAIL)
JUMP_ALWAYS, 0, cctx) == FAIL)
return NULL;
// previous "if" or "elseif" jumps here
isn = ((isn_T *)instr->ga_data) + scope->se_u.se_if.is_if_label;
@@ -695,7 +703,7 @@ compile_else(char_u *arg, cctx_T *cctx)
{
if (!cctx->ctx_had_return
&& compile_jump_to_end(&scope->se_u.se_if.is_end_label,
JUMP_ALWAYS, cctx) == FAIL)
JUMP_ALWAYS, 0, cctx) == FAIL)
return NULL;
}
@@ -771,16 +779,17 @@ compile_endif(char_u *arg, cctx_T *cctx)
* Compile "for var in expr":
*
* Produces instructions:
* PUSHNR -1
* STORE loop-idx Set index to -1
* EVAL expr result of "expr" on top of stack
* STORE -1 in loop-idx Set index to -1
* EVAL expr Result of "expr" on top of stack
* top: FOR loop-idx, end Increment index, use list on bottom of stack
* - if beyond end, jump to "end"
* - otherwise get item from list and push it
* - store ec_funcrefs in var "loop-idx" + 1
* STORE var Store item in "var"
* ... body ...
* JUMP top Jump back to repeat
* end: DROP Drop the result of "expr"
* ENDLOOP funcref-idx off count Only if closure uses local var
* JUMP top Jump back to repeat
* end: DROP Drop the result of "expr"
*
* Compile "for [var1, var2] in expr" - as above, but instead of "STORE var":
* UNPACK 2 Split item in 2
@@ -801,7 +810,9 @@ compile_for(char_u *arg_start, cctx_T *cctx)
size_t varlen;
garray_T *instr = &cctx->ctx_instr;
scope_T *scope;
forscope_T *forscope;
lvar_T *loop_lvar; // loop iteration variable
lvar_T *funcref_lvar;
lvar_T *var_lvar; // variable for "var"
type_T *vartype;
type_T *item_type = &t_any;
@@ -845,18 +856,28 @@ compile_for(char_u *arg_start, cctx_T *cctx)
scope = new_scope(cctx, FOR_SCOPE);
if (scope == NULL)
return NULL;
forscope = &scope->se_u.se_for;
// Reserve a variable to store the loop iteration counter and initialize it
// to -1.
loop_lvar = reserve_local(cctx, (char_u *)"", 0, FALSE, &t_number);
if (loop_lvar == NULL)
{
// out of memory
drop_scope(cctx);
return NULL;
return NULL; // out of memory
}
generate_STORENR(cctx, loop_lvar->lv_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.
// It is not used when no closures are encountered, we don't know yet.
funcref_lvar = reserve_local(cctx, (char_u *)"", 0, FALSE, &t_number);
if (funcref_lvar == NULL)
{
drop_scope(cctx);
return NULL; // out of memory
}
// compile "expr", it remains on the stack until "endfor"
arg = p;
if (compile_expr0(&arg, cctx) == FAIL)
@@ -901,7 +922,7 @@ compile_for(char_u *arg_start, cctx_T *cctx)
generate_undo_cmdmods(cctx);
// "for_end" is set when ":endfor" is found
scope->se_u.se_for.fs_top_label = current_instr_idx(cctx);
forscope->fs_top_label = current_instr_idx(cctx);
if (cctx->ctx_compile_type == CT_DEBUG)
{
@@ -1019,6 +1040,11 @@ compile_for(char_u *arg_start, cctx_T *cctx)
arg = skipwhite(p);
vim_free(name);
}
forscope->fs_funcref_idx = funcref_lvar->lv_idx;
// remember the number of variables and closures, used in :endfor
forscope->fs_local_count = cctx->ctx_locals.ga_len;
forscope->fs_closure_count = cctx->ctx_closure_count;
}
return arg_end;
@@ -1029,6 +1055,23 @@ failed:
return NULL;
}
/*
* At :endfor and :endwhile: Generate an ISN_ENDLOOP instruction if any
* variable was declared that could be used by a new closure.
*/
static int
compile_loop_end(
int prev_local_count,
int prev_closure_count,
int funcref_idx,
cctx_T *cctx)
{
if (cctx->ctx_locals.ga_len > prev_local_count
&& cctx->ctx_closure_count > prev_closure_count)
return generate_ENDLOOP(cctx, funcref_idx, prev_local_count);
return OK;
}
/*
* compile "endfor"
*/
@@ -1052,6 +1095,14 @@ compile_endfor(char_u *arg, cctx_T *cctx)
cctx->ctx_scope = scope->se_outer;
if (cctx->ctx_skip != SKIP_YES)
{
// Handle the case that any local variables were declared that might be
// used in a closure.
if (compile_loop_end(forscope->fs_local_count,
forscope->fs_closure_count,
forscope->fs_funcref_idx,
cctx) == FAIL)
return NULL;
unwind_locals(cctx, scope->se_local_count);
// At end of ":for" scope jump back to the FOR instruction.
@@ -1080,25 +1131,42 @@ compile_endfor(char_u *arg, cctx_T *cctx)
* compile "while expr"
*
* Produces instructions:
* top: EVAL expr Push result of "expr"
* JUMP_IF_FALSE end jump if false
* ... body ...
* JUMP top Jump back to repeat
* top: EVAL expr Push result of "expr"
* WHILE funcref-idx end Jump if false
* ... body ...
* ENDLOOP funcref-idx off count only if closure uses local var
* JUMP top Jump back to repeat
* end:
*
*/
char_u *
compile_while(char_u *arg, cctx_T *cctx)
{
char_u *p = arg;
scope_T *scope;
char_u *p = arg;
scope_T *scope;
whilescope_T *whilescope;
lvar_T *funcref_lvar;
scope = new_scope(cctx, WHILE_SCOPE);
if (scope == NULL)
return NULL;
whilescope = &scope->se_u.se_while;
// "endwhile" jumps back here, one before when profiling or using cmdmods
scope->se_u.se_while.ws_top_label = current_instr_idx(cctx);
whilescope->ws_top_label = current_instr_idx(cctx);
// Reserve a variable to store ec_funcrefs.ga_len, used in ISN_ENDLOOP.
// It is not used when no closures are encountered, we don't know yet.
funcref_lvar = reserve_local(cctx, (char_u *)"", 0, FALSE, &t_number);
if (funcref_lvar == NULL)
{
drop_scope(cctx);
return NULL; // out of memory
}
whilescope->ws_funcref_idx = funcref_lvar->lv_idx;
// remember the number of variables and closures, used in :endwhile
whilescope->ws_local_count = cctx->ctx_locals.ga_len;
whilescope->ws_closure_count = cctx->ctx_closure_count;
// compile "expr"
if (compile_expr0(&p, cctx) == FAIL)
@@ -1119,8 +1187,8 @@ compile_while(char_u *arg, cctx_T *cctx)
generate_undo_cmdmods(cctx);
// "while_end" is set when ":endwhile" is found
if (compile_jump_to_end(&scope->se_u.se_while.ws_end_label,
JUMP_IF_FALSE, cctx) == FAIL)
if (compile_jump_to_end(&whilescope->ws_end_label,
JUMP_WHILE_FALSE, funcref_lvar->lv_idx, cctx) == FAIL)
return FAIL;
}
@@ -1146,6 +1214,16 @@ compile_endwhile(char_u *arg, cctx_T *cctx)
cctx->ctx_scope = scope->se_outer;
if (cctx->ctx_skip != SKIP_YES)
{
whilescope_T *whilescope = &scope->se_u.se_while;
// Handle the case that any local variables were declared that might be
// used in a closure.
if (compile_loop_end(whilescope->ws_local_count,
whilescope->ws_closure_count,
whilescope->ws_funcref_idx,
cctx) == FAIL)
return NULL;
unwind_locals(cctx, scope->se_local_count);
#ifdef FEAT_PROFILE
@@ -1250,7 +1328,7 @@ compile_break(char_u *arg, cctx_T *cctx)
// Jump to the end of the FOR or WHILE loop. The instruction index will be
// filled in later.
if (compile_jump_to_end(el, JUMP_ALWAYS, cctx) == FAIL)
if (compile_jump_to_end(el, JUMP_ALWAYS, 0, cctx) == FAIL)
return FAIL;
return arg;
@@ -1397,7 +1475,7 @@ compile_catch(char_u *arg, cctx_T *cctx UNUSED)
#endif
// Jump from end of previous block to :finally or :endtry
if (compile_jump_to_end(&scope->se_u.se_try.ts_end_label,
JUMP_ALWAYS, cctx) == FAIL)
JUMP_ALWAYS, 0, cctx) == FAIL)
return NULL;
// End :try or :catch scope: set value in ISN_TRY instruction