To: vim_dev@googlegroups.com Subject: Patch 9.0.0397 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 9.0.0397 Problem: :defer not tested with exceptions and ":qa!". Solution: Test :defer works when exceptions are thrown and when ":qa!" is used. Invoke the deferred calls on exit. Files: src/main.c, src/userfunc.c, src/proto/userfunc.pro, src/vim9execute.c, src/proto/vim9execute.pro, src/structs.h, src/eval.c, src/testdir/test_user_func.vim *** ../vim-9.0.0396/src/main.c 2022-08-29 15:06:46.720715534 +0100 --- src/main.c 2022-09-06 17:42:25.668782607 +0100 *************** *** 1583,1588 **** --- 1583,1593 ---- if (!is_not_a_term_or_gui()) windgoto((int)Rows - 1, 0); + #ifdef FEAT_EVAL + // Invoked all deferred functions in the function stack. + invoke_all_defer(); + #endif + #if defined(FEAT_EVAL) || defined(FEAT_SYN_HL) // Optionally print hashtable efficiency. hash_debug_results(); *** ../vim-9.0.0396/src/userfunc.c 2022-09-05 21:21:21.135941382 +0100 --- src/userfunc.c 2022-09-06 18:23:08.627100776 +0100 *************** *** 33,38 **** --- 33,39 ---- static void func_clear(ufunc_T *fp, int force); static int func_free(ufunc_T *fp, int force); static char_u *untrans_function_name(char_u *name); + static void handle_defer_one(funccall_T *funccal); void func_init() *************** *** 2651,2657 **** profile_may_start_func(&profile_info, fp, caller); #endif sticky_cmdmod_flags = 0; ! call_def_function(fp, argcount, argvars, funcexe->fe_partial, rettv); funcdepth_decrement(); #ifdef FEAT_PROFILE if (do_profiling == PROF_YES && (fp->uf_profiling --- 2652,2659 ---- profile_may_start_func(&profile_info, fp, caller); #endif sticky_cmdmod_flags = 0; ! call_def_function(fp, argcount, argvars, funcexe->fe_partial, ! fc, rettv); funcdepth_decrement(); #ifdef FEAT_PROFILE if (do_profiling == PROF_YES && (fp->uf_profiling *************** *** 2906,2912 **** DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); // Invoke functions added with ":defer". ! handle_defer(); --RedrawingDisabled; --- 2908,2914 ---- DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); // Invoke functions added with ":defer". ! handle_defer_one(current_funccal); --RedrawingDisabled; *************** *** 5660,5675 **** /* * Invoked after a functions has finished: invoke ":defer" functions. */ ! void ! handle_defer(void) { int idx; ! for (idx = current_funccal->fc_defer.ga_len - 1; idx >= 0; --idx) { funcexe_T funcexe; typval_T rettv; ! defer_T *dr = ((defer_T *)current_funccal->fc_defer.ga_data) + idx; int i; CLEAR_FIELD(funcexe); --- 5662,5677 ---- /* * Invoked after a functions has finished: invoke ":defer" functions. */ ! static void ! handle_defer_one(funccall_T *funccal) { int idx; ! for (idx = funccal->fc_defer.ga_len - 1; idx >= 0; --idx) { funcexe_T funcexe; typval_T rettv; ! defer_T *dr = ((defer_T *)funccal->fc_defer.ga_data) + idx; int i; CLEAR_FIELD(funcexe); *************** *** 5683,5689 **** for (i = dr->dr_argcount - 1; i >= 0; --i) clear_tv(&dr->dr_argvars[i]); } ! ga_clear(¤t_funccal->fc_defer); } /* --- 5685,5713 ---- for (i = dr->dr_argcount - 1; i >= 0; --i) clear_tv(&dr->dr_argvars[i]); } ! ga_clear(&funccal->fc_defer); ! } ! ! /* ! * Called when exiting: call all defer functions. ! */ ! void ! invoke_all_defer(void) ! { ! funccall_T *funccal; ! ! for (funccal = current_funccal; funccal != NULL; funccal = funccal->caller) ! if (funccal->fc_ectx != NULL) ! { ! // :def function ! unwind_def_callstack(funccal->fc_ectx); ! may_invoke_defer_funcs(funccal->fc_ectx); ! } ! else ! { ! // legacy function ! handle_defer_one(funccal); ! } } /* *** ../vim-9.0.0396/src/proto/userfunc.pro 2022-09-04 15:40:31.816188110 +0100 --- src/proto/userfunc.pro 2022-09-06 17:43:17.560601478 +0100 *************** *** 59,65 **** void func_ptr_ref(ufunc_T *fp); void ex_return(exarg_T *eap); int add_defer(char_u *name, int argcount_arg, typval_T *argvars); ! void handle_defer(void); void ex_call(exarg_T *eap); int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv); void discard_pending_return(void *rettv); --- 59,65 ---- void func_ptr_ref(ufunc_T *fp); void ex_return(exarg_T *eap); int add_defer(char_u *name, int argcount_arg, typval_T *argvars); ! void invoke_all_defer(void); void ex_call(exarg_T *eap); int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv); void discard_pending_return(void *rettv); *** ../vim-9.0.0396/src/vim9execute.c 2022-09-05 10:47:10.536279263 +0100 --- src/vim9execute.c 2022-09-06 18:26:04.254681624 +0100 *************** *** 5171,5183 **** done: ret = OK; theend: ! { ! dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) ! + ectx->ec_dfunc_idx; ! ! if (dfunc->df_defer_var_idx > 0) ! invoke_defer_funcs(ectx); ! } dict_stack_clear(dict_stack_len_at_start); ectx->ec_trylevel_at_start = save_trylevel_at_start; --- 5171,5177 ---- done: ret = OK; theend: ! may_invoke_defer_funcs(ectx); dict_stack_clear(dict_stack_len_at_start); ectx->ec_trylevel_at_start = save_trylevel_at_start; *************** *** 5258,5263 **** --- 5252,5258 ---- int argc_arg, // nr of arguments typval_T *argv, // arguments partial_T *partial, // optional partial for context + funccall_T *funccal, typval_T *rettv) // return value { ectx_T ectx; // execution context *************** *** 5494,5499 **** --- 5489,5498 ---- ectx.ec_instr = INSTRUCTIONS(dfunc); } + // Store the execution context in funccal, used by invoke_all_defer(). + if (funccal != NULL) + funccal->fc_ectx = &ectx; + // Following errors are in the function, not the caller. // Commands behave like vim9script. estack_push_ufunc(ufunc, 1); *************** *** 5537,5544 **** } // When failed need to unwind the call stack. ! while (ectx.ec_frame_idx != ectx.ec_initial_frame_idx) ! func_return(&ectx); // Deal with any remaining closures, they may be in use somewhere. if (ectx.ec_funcrefs.ga_len > 0) --- 5536,5542 ---- } // When failed need to unwind the call stack. ! unwind_def_callstack(&ectx); // Deal with any remaining closures, they may be in use somewhere. if (ectx.ec_funcrefs.ga_len > 0) *************** *** 5604,5609 **** --- 5602,5631 ---- } /* + * Called when a def function has finished (possibly failed). + * Invoke all the function returns to clean up and invoke deferred functions, + * except the toplevel one. + */ + void + unwind_def_callstack(ectx_T *ectx) + { + while (ectx->ec_frame_idx != ectx->ec_initial_frame_idx) + func_return(ectx); + } + + /* + * Invoke any deffered functions for the top function in "ectx". + */ + void + may_invoke_defer_funcs(ectx_T *ectx) + { + dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + ectx->ec_dfunc_idx; + + if (dfunc->df_defer_var_idx > 0) + invoke_defer_funcs(ectx); + } + + /* * 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. *** ../vim-9.0.0396/src/proto/vim9execute.pro 2022-09-04 15:40:31.816188110 +0100 --- src/proto/vim9execute.pro 2022-09-06 18:20:03.231539841 +0100 *************** *** 13,19 **** int may_break_in_function(ufunc_T *ufunc); int exe_typval_instr(typval_T *tv, typval_T *rettv); char_u *exe_substitute_instr(void); ! int call_def_function(ufunc_T *ufunc, int argc_arg, typval_T *argv, partial_T *partial, typval_T *rettv); void set_context_in_disassemble_cmd(expand_T *xp, char_u *arg); char_u *get_disassemble_argument(expand_T *xp, int idx); void ex_disassemble(exarg_T *eap); --- 13,21 ---- int may_break_in_function(ufunc_T *ufunc); int exe_typval_instr(typval_T *tv, typval_T *rettv); char_u *exe_substitute_instr(void); ! int call_def_function(ufunc_T *ufunc, int argc_arg, typval_T *argv, partial_T *partial, funccall_T *funccal, typval_T *rettv); ! void unwind_def_callstack(ectx_T *ectx); ! void may_invoke_defer_funcs(ectx_T *ectx); void set_context_in_disassemble_cmd(expand_T *xp, char_u *arg); char_u *get_disassemble_argument(expand_T *xp, int idx); void ex_disassemble(exarg_T *eap); *** ../vim-9.0.0396/src/structs.h 2022-09-03 21:35:50.184158219 +0100 --- src/structs.h 2022-09-06 18:00:22.966072887 +0100 *************** *** 1753,1759 **** --- 1753,1763 ---- linenr_T breakpoint; // next line with breakpoint or zero int dbg_tick; // debug_tick when breakpoint was set int level; // top nesting level of executed function + garray_T fc_defer; // functions to be called on return + ectx_T *fc_ectx; // execution context for :def function, NULL + // otherwise + #ifdef FEAT_PROFILE proftime_T prof_child; // time spent in a child #endif *** ../vim-9.0.0396/src/eval.c 2022-09-03 21:53:24.623089721 +0100 --- src/eval.c 2022-09-06 18:09:06.693038108 +0100 *************** *** 263,270 **** if (partial->pt_func != NULL && partial->pt_func->uf_def_status != UF_NOT_COMPILED) { if (call_def_function(partial->pt_func, argc, argv, ! partial, rettv) == FAIL) return FAIL; } else --- 263,271 ---- if (partial->pt_func != NULL && partial->pt_func->uf_def_status != UF_NOT_COMPILED) { + // FIXME: should create a funccal and link it in current_funccal. if (call_def_function(partial->pt_func, argc, argv, ! partial, NULL, rettv) == FAIL) return FAIL; } else *** ../vim-9.0.0396/src/testdir/test_user_func.vim 2022-09-05 21:21:21.135941382 +0100 --- src/testdir/test_user_func.vim 2022-09-06 17:24:39.414887963 +0100 *************** *** 581,585 **** --- 581,629 ---- call assert_fails('defer Part("arg2")', 'E1300:') endfunc + func DeferLevelTwo() + call writefile(['text'], 'XDeleteTwo', 'D') + throw 'someerror' + endfunc + + def DeferLevelOne() + call writefile(['text'], 'XDeleteOne', 'D') + call g:DeferLevelTwo() + enddef + + func Test_defer_throw() + let caught = 'no' + try + call DeferLevelOne() + catch /someerror/ + let caught = 'yes' + endtry + call assert_equal('yes', caught) + call assert_false(filereadable('XDeleteOne')) + call assert_false(filereadable('XDeleteTwo')) + endfunc + + func Test_defer_quitall() + let lines =<< trim END + vim9script + func DeferLevelTwo() + call writefile(['text'], 'XQuitallTwo', 'D') + qa! + endfunc + + def DeferLevelOne() + call writefile(['text'], 'XQuitallOne', 'D') + call DeferLevelTwo() + enddef + + DeferLevelOne() + END + call writefile(lines, 'XdeferQuitall', 'D') + let res = system(GetVimCommandClean() .. ' -X -S XdeferQuitall') + call assert_equal(0, v:shell_error) + call assert_false(filereadable('XQuitallOne')) + call assert_false(filereadable('XQuitallTwo')) + endfunc + " vim: shiftwidth=2 sts=2 expandtab *** ../vim-9.0.0396/src/version.c 2022-09-06 17:00:11.351047779 +0100 --- src/version.c 2022-09-06 17:20:08.182247054 +0100 *************** *** 705,706 **** --- 705,708 ---- { /* Add new patch number below this line */ + /**/ + 397, /**/ -- hundred-and-one symptoms of being an internet addict: 17. You turn on your intercom when leaving the room so you can hear if new e-mail arrives. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///