# HG changeset patch # User Bram Moolenaar # Date 1663599607 -7200 # Node ID fc0830246f49fda03876f76794a32fedc22d825b # Parent 462d122636b3ae3efa18158231a05b79721e75ad 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 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. diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -3329,3 +3329,7 @@ EXTERN char e_cannot_use_type_with_this_ EXTERN char e_cannot_use_length_endcol_and_endlnum_with_text[] INIT(= N_("E1305: Cannot use \"length\", \"end_col\" and \"end_lnum\" with \"text\"")); #endif +#ifdef FEAT_EVAL +EXTERN char e_loop_nesting_too_deep[] + INIT(= N_("E1306: Loop nesting too deep")); +#endif diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -4807,11 +4807,12 @@ partial_free(partial_T *pt) funcstack_check_refcount(pt->pt_funcstack); } // Similarly for loop variables. - if (pt->pt_loopvars != NULL) - { - --pt->pt_loopvars->lvs_refcount; - loopvars_check_refcount(pt->pt_loopvars); - } + for (i = 0; i < MAX_LOOP_DEPTH; ++i) + if (pt->pt_loopvars[i] != NULL) + { + --pt->pt_loopvars[i]->lvs_refcount; + loopvars_check_refcount(pt->pt_loopvars[i]); + } vim_free(pt); } @@ -4839,8 +4840,15 @@ partial_unref(partial_T *pt) if (pt->pt_funcstack != NULL) done = funcstack_check_refcount(pt->pt_funcstack); - if (!done && pt->pt_loopvars != NULL) - loopvars_check_refcount(pt->pt_loopvars); + if (!done) + { + int depth; + + for (depth = 0; depth < MAX_LOOP_DEPTH; ++depth) + if (pt->pt_loopvars[depth] != NULL + && loopvars_check_refcount(pt->pt_loopvars[depth])) + break; + } } } } diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro --- 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_lambda_to_global_func(char_u *lambda, char_u *global, short loop_var_idx, short loop_var_count, ectx_T *ectx); +int copy_lambda_to_global_func(char_u *lambda, char_u *global, loopvarinfo_T *loopvarinfo, ectx_T *ectx); int funcdepth_increment(void); void funcdepth_decrement(void); int funcdepth_get(void); diff --git a/src/proto/vim9cmds.pro b/src/proto/vim9cmds.pro --- a/src/proto/vim9cmds.pro +++ b/src/proto/vim9cmds.pro @@ -11,8 +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); +int get_loop_var_info(cctx_T *cctx, loopvarinfo_T *lvi); +void get_loop_var_idx(cctx_T *cctx, int idx, lvar_T *lvar); 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); diff --git a/src/proto/vim9execute.pro b/src/proto/vim9execute.pro --- a/src/proto/vim9execute.pro +++ b/src/proto/vim9execute.pro @@ -9,11 +9,11 @@ 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, short loop_var_idx, short loop_var_count, ectx_T *ectx); +int fill_partial_and_closure(partial_T *pt, ufunc_T *ufunc, loopvarinfo_T *loopvarinfo, 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); -void loopvars_check_refcount(loopvars_T *loopvars); +int loopvars_check_refcount(loopvars_T *loopvars); int set_ref_in_loopvars(int copyID); int exe_typval_instr(typval_T *tv, typval_T *rettv); char_u *exe_substitute_instr(void); diff --git a/src/proto/vim9instr.pro b/src/proto/vim9instr.pro --- 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, int loop_idx, type_T *type); +int generate_LOADOUTER(cctx_T *cctx, int idx, int nesting, int loop_depth, 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,13 +40,13 @@ 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, short loop_var_idx, short loop_var_count); +int generate_NEWFUNC(cctx_T *cctx, char_u *lambda_name, char_u *func_name); 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); int generate_JUMP_IF_ARG_SET(cctx_T *cctx, int arg_off); int generate_FOR(cctx_T *cctx, int loop_idx); -int generate_ENDLOOP(cctx_T *cctx, int funcref_idx, int prev_local_count); +int generate_ENDLOOP(cctx_T *cctx, loop_info_T *loop_info); int generate_TRYCONT(cctx_T *cctx, int levels, int where); int check_internal_func_args(cctx_T *cctx, int func_idx, int argcount, int method_call, type2_T **argtypes, type2_T *shuffled_argtypes); int generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -2108,6 +2108,9 @@ struct loopvars_S int lvs_copyID; // for garbage collection }; +// maximum nesting of :while and :for loops in a :def function +#define MAX_LOOP_DEPTH 10 + typedef struct outer_S outer_T; struct outer_S { garray_T *out_stack; // stack from outer scope, or a copy @@ -2116,11 +2119,13 @@ struct outer_S { 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 + struct { + garray_T *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 + short var_idx; // first variable defined in a loop in + // out_loop_stack + short var_count; // number of variables defined in a loop + } out_loop[MAX_LOOP_DEPTH]; }; struct partial_S @@ -2141,7 +2146,8 @@ struct partial_S funcstack_T *pt_funcstack; // copy of stack, used after context // function returns - loopvars_T *pt_loopvars; // copy of loop variables, used after loop + loopvars_T *(pt_loopvars[MAX_LOOP_DEPTH]); + // copy of loop variables, used after loop // block ends typval_T *pt_argv; // arguments in allocated array @@ -2151,6 +2157,14 @@ struct partial_S dict_T *pt_dict; // dict for "self" }; +typedef struct { + short lvi_depth; // current nested loop depth + struct { + short var_idx; // index of first variable inside loop + short var_count; // number of variables inside loop + } lvi_loop[MAX_LOOP_DEPTH]; +} loopvarinfo_T; + typedef struct AutoPatCmd_S AutoPatCmd_T; /* diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim --- a/src/testdir/test_vim9_disassemble.vim +++ b/src/testdir/test_vim9_disassemble.vim @@ -1023,15 +1023,15 @@ def Test_disassemble_closure_in_loop() 'endif\_s*' .. 'g:Ref = () => ii\_s*' .. - '\d\+ FUNCREF 4 var $3 - $3\_s*' .. + '\d\+ FUNCREF 4 vars $3-$3\_s*' .. '\d\+ STOREG g:Ref\_s*' .. 'continue\_s*' .. - '\d\+ ENDLOOP $1 save $3 - $3\_s*' .. + '\d\+ ENDLOOP ref $1 save $3-$3 depth 0\_s*' .. '\d\+ JUMP -> \d\+\_s*' .. 'break\_s*' .. - '\d\+ ENDLOOP $1 save $3 - $3\_s*' .. + '\d\+ ENDLOOP ref $1 save $3-$3 depth 0\_s*' .. '\d\+ JUMP -> \d\+\_s*' .. 'if g:val\_s*' .. @@ -1041,12 +1041,12 @@ def Test_disassemble_closure_in_loop() ' return\_s*' .. '\d\+ PUSHNR 0\_s*' .. - '\d\+ ENDLOOP $1 save $3 - $3\_s*' .. + '\d\+ ENDLOOP ref $1 save $3-$3 depth 0\_s*' .. '\d\+ RETURN\_s*' .. 'endif\_s*' .. 'endfor\_s*' .. - '\d\+ ENDLOOP $1 save $3 - $3\_s*' .. + '\d\+ ENDLOOP ref $1 save $3-$3 depth 0\_s*' .. '\d\+ JUMP -> \d\+\_s*' .. '\d\+ DROP\_s*' .. '\d\+ RETURN void', diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim --- a/src/testdir/test_vim9_script.vim +++ b/src/testdir/test_vim9_script.vim @@ -2322,10 +2322,46 @@ def Test_for_loop_with_closure() endfor endfor END - v9.CheckScriptSuccess(['vim9script'] + lines) - # FIXME: not yet right for :def - lines[14] = 'assert_equal(2 .. a, flist[n]())' - v9.CheckDefSuccess(lines) + v9.CheckDefAndScriptSuccess(lines) +enddef + +def Test_define_global_closure_in_loops() + var lines =<< trim END + vim9script + + def Func() + for i in range(3) + var ii = i + for a in ['a', 'b', 'c'] + var aa = a + if ii == 0 && aa == 'a' + def g:Global_0a(): string + return ii .. aa + enddef + endif + if ii == 1 && aa == 'b' + def g:Global_1b(): string + return ii .. aa + enddef + endif + if ii == 2 && aa == 'c' + def g:Global_2c(): string + return ii .. aa + enddef + endif + endfor + endfor + enddef + Func() + END + v9.CheckScriptSuccess(lines) + assert_equal("0a", g:Global_0a()) + assert_equal("1b", g:Global_1b()) + assert_equal("2c", g:Global_2c()) + + delfunc g:Global_0a + delfunc g:Global_1b + delfunc g:Global_2c enddef def Test_for_loop_fails() @@ -2418,6 +2454,32 @@ def Test_for_loop_fails() endfor END v9.CheckDefExecAndScriptFailure(lines, 'E1013: Argument 2: type mismatch, expected dict but got dict') + + lines =<< trim END + for a in range(3) + while a > 3 + for b in range(2) + while b < 0 + for c in range(5) + while c > 6 + while c < 0 + for d in range(1) + for e in range(3) + while e > 3 + endwhile + endfor + endfor + endwhile + endwhile + endfor + endwhile + endfor + endwhile + endfor + END + v9.CheckDefSuccess(lines) + + v9.CheckDefFailure(['for x in range(3)'] + lines + ['endfor'], 'E1306:') enddef def Test_for_loop_script_var() diff --git a/src/userfunc.c b/src/userfunc.c --- a/src/userfunc.c +++ b/src/userfunc.c @@ -2453,11 +2453,10 @@ func_clear_free(ufunc_T *fp, int force) */ int copy_lambda_to_global_func( - char_u *lambda, - char_u *global, - short loop_var_idx, - short loop_var_count, - ectx_T *ectx) + char_u *lambda, + char_u *global, + loopvarinfo_T *loopvarinfo, + ectx_T *ectx) { ufunc_T *ufunc = find_func_even_dead(lambda, FFED_IS_GLOBAL); ufunc_T *fp = NULL; @@ -2524,14 +2523,12 @@ copy_lambda_to_global_func( if (pt == NULL) goto failed; - if (fill_partial_and_closure(pt, ufunc, loop_var_idx, loop_var_count, - ectx) == FAIL) + if (fill_partial_and_closure(pt, ufunc, loopvarinfo, ectx) == FAIL) { vim_free(pt); goto failed; } ufunc->uf_partial = pt; - --pt->pt_refcount; // not actually referenced here } return OK; diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -700,6 +700,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 502, +/**/ 501, /**/ 500, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -252,30 +252,31 @@ typedef enum { // arguments to ISN_JUMP typedef struct { jumpwhen_T jump_when; - int jump_where; // position to jump to + int jump_where; // position to jump to } jump_T; // arguments to ISN_JUMP_IF_ARG_SET typedef struct { - int jump_arg_off; // argument index, negative - int jump_where; // position to jump to + int jump_arg_off; // argument index, negative + int jump_where; // position to jump to } jumparg_T; // arguments to ISN_FOR typedef struct { - int for_idx; // loop variable index - int for_end; // position to jump to after done + short for_loop_idx; // loop variable index + int for_end; // position to jump to after done } forloop_T; // arguments to ISN_WHILE typedef struct { - int while_funcref_idx; // variable index for funcref count - int while_end; // position to jump to after done + short while_funcref_idx; // variable index for funcref count + int while_end; // position to jump to after done } whileloop_T; // arguments to ISN_ENDLOOP typedef struct { short end_funcref_idx; // variable index of funcrefs.ga_len + short end_depth; // nested loop depth short end_var_idx; // first variable declared in the loop short end_var_count; // number of variables declared in the loop } endloop_T; @@ -356,9 +357,8 @@ typedef struct { // 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 + char_u *fre_func_name; // function name for legacy function + loopvarinfo_T fre_loopvar_info; // info about variables inside loops } funcref_extra_T; // arguments to ISN_FUNCREF @@ -369,10 +369,9 @@ typedef struct { // arguments to ISN_NEWFUNC typedef struct { - 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 + char_u *nfa_lambda; // name of the lambda already defined + char_u *nfa_global; // name of the global function to be created + loopvarinfo_T nfa_loopvar_info; // ifno about variables inside loops } newfuncarg_T; typedef struct { @@ -628,6 +627,7 @@ typedef struct { int li_local_count; // ctx_locals.ga_len at loop start int li_closure_count; // ctx_closure_count at loop start int li_funcref_idx; // index of var that holds funcref count + int li_depth; // nested loop depth } loop_info_T; /* @@ -678,6 +678,7 @@ struct scope_S { scopetype_T se_type; int se_local_count; // ctx_locals.ga_len before scope skip_T se_skip_save; // ctx_skip before the block + int se_loop_depth; // number of loop scopes, including this union { ifscope_T se_if; whilescope_T se_while; @@ -693,6 +694,7 @@ typedef struct { char_u *lv_name; type_T *lv_type; int lv_idx; // index of the variable on the stack + int lv_loop_depth; // depth for variable inside a loop or -1 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 diff --git a/src/vim9cmds.c b/src/vim9cmds.c --- 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; + } + } } /* diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -1825,11 +1825,10 @@ call_eval_func( */ int fill_partial_and_closure( - partial_T *pt, - ufunc_T *ufunc, - short loop_var_idx, - short loop_var_count, - ectx_T *ectx) + partial_T *pt, + ufunc_T *ufunc, + loopvarinfo_T *lvi, + ectx_T *ectx) { pt->pt_func = ufunc; pt->pt_refcount = 1; @@ -1854,13 +1853,22 @@ fill_partial_and_closure( } } - // 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 (lvi != NULL) + { + int depth; + + // The closure may need to find variables defined inside a loop, + // for every nested loop. A new reference is made every time, + // ISN_ENDLOOP will check if they are actually used. + for (depth = 0; depth < lvi->lvi_depth; ++depth) + { + pt->pt_outer.out_loop[depth].stack = &ectx->ec_stack; + pt->pt_outer.out_loop[depth].var_idx = ectx->ec_frame_idx + + STACK_FRAME_SIZE + lvi->lvi_loop[depth].var_idx; + pt->pt_outer.out_loop[depth].var_count = + lvi->lvi_loop[depth].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 @@ -2507,7 +2515,7 @@ execute_for(isn_T *iptr, ectx_T *ectx) int jump = FALSE; typval_T *ltv = STACK_TV_BOT(-1); typval_T *idxtv = - STACK_TV_VAR(iptr->isn_arg.forloop.for_idx); + STACK_TV_VAR(iptr->isn_arg.forloop.for_loop_idx); if (GA_GROW_FAILS(&ectx->ec_stack, 1)) return FAIL; @@ -2613,7 +2621,7 @@ execute_for(isn_T *iptr, ectx_T *ectx) // Store the current number of funcrefs, this may be used in // ISN_LOOPEND. The variable index is always one more than the loop // variable index. - tv = STACK_TV_VAR(iptr->isn_arg.forloop.for_idx + 1); + tv = STACK_TV_VAR(iptr->isn_arg.forloop.for_loop_idx + 1); tv->vval.v_number = ectx->ec_funcrefs.ga_len; } @@ -2661,18 +2669,20 @@ execute_endloop(isn_T *iptr, ectx_T *ect endloop_T *endloop = &iptr->isn_arg.endloop; typval_T *tv_refcount = STACK_TV_VAR(endloop->end_funcref_idx); int prev_closure_count = tv_refcount->vval.v_number; + int depth = endloop->end_depth; garray_T *gap = &ectx->ec_funcrefs; int closure_in_use = FALSE; loopvars_T *loopvars; typval_T *stack; int idx; - // Check if any created closure is still being referenced. + // Check if any created closure is still being referenced and loopvars have + // not been saved yet for the current depth. for (idx = prev_closure_count; idx < gap->ga_len; ++idx) { partial_T *pt = ((partial_T **)gap->ga_data)[idx]; - if (pt->pt_refcount > 1 && pt->pt_loopvars == NULL) + if (pt->pt_refcount > 1 && pt->pt_loopvars[depth] == NULL) { int refcount = pt->pt_refcount; int i; @@ -2728,14 +2738,14 @@ execute_endloop(isn_T *iptr, ectx_T *ect { partial_T *pt = ((partial_T **)gap->ga_data)[idx]; - if (pt->pt_refcount > 1 && pt->pt_loopvars == NULL) + if (pt->pt_refcount > 1 && pt->pt_loopvars[depth] == NULL) { ++loopvars->lvs_refcount; - pt->pt_loopvars = loopvars; - - pt->pt_outer.out_loop_stack = &loopvars->lvs_ga; - pt->pt_outer.out_loop_var_idx -= ectx->ec_frame_idx - + STACK_FRAME_SIZE + endloop->end_var_idx; + pt->pt_loopvars[depth] = loopvars; + + pt->pt_outer.out_loop[depth].stack = &loopvars->lvs_ga; + pt->pt_outer.out_loop[depth].var_idx -= + ectx->ec_frame_idx + STACK_FRAME_SIZE + endloop->end_var_idx; } } @@ -2747,37 +2757,44 @@ execute_endloop(isn_T *iptr, ectx_T *ect * loopvars may be the only reference to the partials in the local variables. * Go over all of them, the funcref and can be freed if all partials * referencing the loopvars have a reference count of one. + * Return TRUE if it was freed. */ - void + int loopvars_check_refcount(loopvars_T *loopvars) { int i; garray_T *gap = &loopvars->lvs_ga; int done = 0; + typval_T *stack = gap->ga_data; if (loopvars->lvs_refcount > loopvars->lvs_min_refcount) - return; + return FALSE; for (i = 0; i < gap->ga_len; ++i) { - typval_T *tv = ((typval_T *)gap->ga_data) + i; + typval_T *tv = ((typval_T *)gap->ga_data) + i; if (tv->v_type == VAR_PARTIAL && tv->vval.v_partial != NULL - && tv->vval.v_partial->pt_loopvars == loopvars && tv->vval.v_partial->pt_refcount == 1) - ++done; + { + int depth; + + for (depth = 0; depth < MAX_LOOP_DEPTH; ++depth) + if (tv->vval.v_partial->pt_loopvars[depth] == loopvars) + ++done; + } } - if (done == loopvars->lvs_min_refcount) - { - typval_T *stack = gap->ga_data; - - // All partials referencing the loopvars have a reference count of - // one, thus the loopvars is no longer of use. - for (i = 0; i < gap->ga_len; ++i) - clear_tv(stack + i); - vim_free(stack); - remove_loopvars_from_list(loopvars); - vim_free(loopvars); - } + if (done != loopvars->lvs_min_refcount) + return FALSE; + + // All partials referencing the loopvars have a reference count of + // one, thus the loopvars is no longer of use. + stack = gap->ga_data; + for (i = 0; i < gap->ga_len; ++i) + clear_tv(stack + i); + vim_free(stack); + remove_loopvars_from_list(loopvars); + vim_free(loopvars); + return TRUE; } /* @@ -3804,12 +3821,13 @@ exec_instructions(ectx_T *ectx) iemsg("LOADOUTER depth more than scope levels"); goto theend; } - if (depth == OUTER_LOOP_DEPTH) + if (depth < 0) // Variable declared in loop. May be copied if the // loop block has already ended. - tv = ((typval_T *)outer->out_loop_stack->ga_data) - + outer->out_loop_var_idx - + iptr->isn_arg.outer.outer_idx; + tv = ((typval_T *)outer->out_loop[-depth - 1] + .stack->ga_data) + + outer->out_loop[-depth - 1].var_idx + + iptr->isn_arg.outer.outer_idx; else // Variable declared in a function. May be copied if // the function has already returned. @@ -4142,8 +4160,7 @@ exec_instructions(ectx_T *ectx) goto theend; } if (fill_partial_and_closure(pt, ufunc, - extra == NULL ? 0 : extra->fre_loop_var_idx, - extra == NULL ? 0 : extra->fre_loop_var_count, + extra == NULL ? NULL : &extra->fre_loopvar_info, ectx) == FAIL) goto theend; tv = STACK_TV_BOT(0); @@ -4160,8 +4177,8 @@ exec_instructions(ectx_T *ectx) 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) + arg->nfa_global, &arg->nfa_loopvar_info, + ectx) == FAIL) goto theend; } break; @@ -4236,7 +4253,7 @@ exec_instructions(ectx_T *ectx) ectx->ec_iidx = iptr->isn_arg.whileloop.while_end; // Store the current funccal count, may be used by - // ISN_LOOPEND later + // ISN_ENDLOOP later tv = STACK_TV_VAR( iptr->isn_arg.whileloop.while_funcref_idx); tv->vval.v_number = ectx->ec_funcrefs.ga_len; @@ -5581,6 +5598,11 @@ call_def_function( // Check the function was really compiled. dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + ufunc->uf_dfunc_idx; + if (dfunc->df_ufunc == NULL) + { + semsg(_(e_function_was_deleted_str), printable_func_name(ufunc)); + return FAIL; + } if (INSTRUCTIONS(dfunc) == NULL) { iemsg("using call_def_function() on not compiled function"); @@ -5717,8 +5739,13 @@ call_def_function( if (partial != NULL) { outer_T *outer = get_pt_outer(partial); - - if (outer->out_stack == NULL && outer->out_loop_stack == NULL) + int depth; + void *ptr = outer->out_stack; + + // see if any stack was set + for (depth = 0; ptr == NULL && depth < MAX_LOOP_DEPTH; ++depth) + ptr = outer->out_loop[depth].stack; + if (ptr == NULL) { if (current_ectx != NULL) { @@ -5767,6 +5794,8 @@ call_def_function( ectx.ec_stack.ga_len += dfunc->df_varcount; if (dfunc->df_has_closure) { + // Initialize the variable that counts how many closures were + // created. This is used in handle_closure_in_use(). STACK_TV_VAR(idx)->v_type = VAR_NUMBER; STACK_TV_VAR(idx)->vval.v_number = 0; ++ectx.ec_stack.ga_len; @@ -5912,6 +5941,32 @@ may_invoke_defer_funcs(ectx_T *ectx) } /* + * Return loopvarinfo in a printable form in allocated memory. + */ + static char_u * +printable_loopvarinfo(loopvarinfo_T *lvi) +{ + garray_T ga; + int depth; + + ga_init2(&ga, 1, 100); + for (depth = 0; depth < lvi->lvi_depth; ++depth) + { + if (ga_grow(&ga, 50) == FAIL) + break; + if (lvi->lvi_loop[depth].var_idx == 0) + STRCPY(ga.ga_data + ga.ga_len, " -"); + else + vim_snprintf(ga.ga_data + ga.ga_len, 50, " $%d-$%d", + lvi->lvi_loop[depth].var_idx, + lvi->lvi_loop[depth].var_idx + + lvi->lvi_loop[depth].var_count - 1); + ga.ga_len = STRLEN(ga.ga_data); + } + return ga.ga_data; +} + +/* * List instructions "instr" up to "instr_count" or until ISN_FINISH. * "ufunc" has the source lines, NULL for the instructions of ISN_SUBSTITUTE. * "pfx" is prefixed to every line. @@ -6072,12 +6127,13 @@ list_instructions(char *pfx, isn_T *inst if (outer->outer_idx < 0) smsg("%s%4d LOADOUTER level %d arg[%d]", pfx, current, - 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); + outer->outer_depth, + outer->outer_idx + STACK_FRAME_SIZE); + else if (outer->outer_depth < 0) + smsg("%s%4d LOADOUTER $%d in loop level %d", + pfx, current, + outer->outer_idx, + -outer->outer_depth); else smsg("%s%4d LOADOUTER level %d $%d", pfx, current, outer->outer_depth, @@ -6400,29 +6456,36 @@ list_instructions(char *pfx, isn_T *inst } else name = extra->fre_func_name; - if (extra == NULL || extra->fre_loop_var_count == 0) + if (extra == NULL || extra->fre_loopvar_info.lvi_depth == 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); + { + char_u *info = printable_loopvarinfo( + &extra->fre_loopvar_info); + + smsg("%s%4d FUNCREF %s vars %s", pfx, current, + name, info); + vim_free(info); + } } break; case ISN_NEWFUNC: { - newfuncarg_T *arg = iptr->isn_arg.newfunc.nf_arg; - - if (arg->nfa_loop_var_count == 0) + newfuncarg_T *arg = iptr->isn_arg.newfunc.nf_arg; + + if (arg->nfa_loopvar_info.lvi_depth == 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); + { + char_u *info = printable_loopvarinfo( + &arg->nfa_loopvar_info); + + smsg("%s%4d NEWFUNC %s %s vars %s", pfx, current, + arg->nfa_lambda, arg->nfa_global, info); + vim_free(info); + } } break; @@ -6479,7 +6542,7 @@ list_instructions(char *pfx, isn_T *inst forloop_T *forloop = &iptr->isn_arg.forloop; smsg("%s%4d FOR $%d -> %d", pfx, current, - forloop->for_idx, forloop->for_end); + forloop->for_loop_idx, forloop->for_end); } break; @@ -6487,10 +6550,12 @@ list_instructions(char *pfx, isn_T *inst { endloop_T *endloop = &iptr->isn_arg.endloop; - smsg("%s%4d ENDLOOP $%d save $%d - $%d", pfx, current, + smsg("%s%4d ENDLOOP ref $%d save $%d-$%d depth %d", + pfx, current, endloop->end_funcref_idx, endloop->end_var_idx, - endloop->end_var_idx + endloop->end_var_count - 1); + endloop->end_var_idx + endloop->end_var_count - 1, + endloop->end_depth); } break; diff --git a/src/vim9instr.c b/src/vim9instr.c --- a/src/vim9instr.c +++ b/src/vim9instr.c @@ -997,6 +997,7 @@ generate_LOADOUTER( cctx_T *cctx, int idx, int nesting, + int loop_depth, int loop_idx, type_T *type) { @@ -1008,9 +1009,9 @@ generate_LOADOUTER( 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? + // of the loop. isn->isn_arg.outer.outer_idx = idx - loop_idx; - isn->isn_arg.outer.outer_depth = OUTER_LOOP_DEPTH; + isn->isn_arg.outer.outer_depth = -loop_depth - 1; } else { @@ -1207,8 +1208,8 @@ generate_FUNCREF( isn_T *isn; type_T *type; funcref_extra_T *extra; - short loop_var_idx; - short loop_var_count; + loopvarinfo_T loopinfo; + int has_vars; RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr(cctx, ISN_FUNCREF)) == NULL) @@ -1216,20 +1217,22 @@ generate_FUNCREF( 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) + has_vars = get_loop_var_info(cctx, &loopinfo); + if (ufunc->uf_def_status == UF_NOT_COMPILED || has_vars) { 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; + extra->fre_loopvar_info = loopinfo; } if (ufunc->uf_def_status == UF_NOT_COMPILED) extra->fre_func_name = vim_strsave(ufunc->uf_name); else isn->isn_arg.funcref.fr_dfunc_idx = ufunc->uf_dfunc_idx; + + // Reserve an extra variable to keep track of the number of closures + // created. cctx->ctx_has_closure = 1; // If the referenced function is a closure, it may use items further up in @@ -1252,9 +1255,7 @@ generate_FUNCREF( generate_NEWFUNC( cctx_T *cctx, char_u *lambda_name, - char_u *func_name, - short loop_var_idx, - short loop_var_count) + char_u *func_name) { isn_T *isn; int ret = OK; @@ -1271,11 +1272,14 @@ generate_NEWFUNC( ret = FAIL; else { + // Reserve an extra variable to keep track of the number of + // closures created. + cctx->ctx_has_closure = 1; + 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; + (void)get_loop_var_info(cctx, &arg->nfa_loopvar_info); return OK; } } @@ -1371,27 +1375,25 @@ generate_FOR(cctx_T *cctx, int loop_idx) RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr(cctx, ISN_FOR)) == NULL) return FAIL; - isn->isn_arg.forloop.for_idx = loop_idx; + isn->isn_arg.forloop.for_loop_idx = loop_idx; // type doesn't matter, will be stored next return push_type_stack(cctx, &t_any); } int -generate_ENDLOOP( - cctx_T *cctx, - int funcref_idx, - int prev_local_count) +generate_ENDLOOP(cctx_T *cctx, loop_info_T *loop_info) { isn_T *isn; RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr(cctx, ISN_ENDLOOP)) == NULL) return FAIL; - isn->isn_arg.endloop.end_funcref_idx = funcref_idx; - isn->isn_arg.endloop.end_var_idx = prev_local_count; + isn->isn_arg.endloop.end_depth = loop_info->li_depth; + isn->isn_arg.endloop.end_funcref_idx = loop_info->li_funcref_idx; + isn->isn_arg.endloop.end_var_idx = loop_info->li_local_count; isn->isn_arg.endloop.end_var_count = - cctx->ctx_locals.ga_len - prev_local_count; + cctx->ctx_locals.ga_len - loop_info->li_local_count; return OK; }