Patch 9.0.0370
Problem: Cleaning up afterwards can make a function messy.
Solution: Add the :defer command.
Files: runtime/doc/eval.txt, src/ex_cmds.h, src/ex_cmdidxs.h,
src/userfunc.c, src/proto/
userfunc.pro, src/structs.h,
src/vim9compile.c, src/vim9cmds.c, src/proto/
vim9cmds.pro,
src/vim9.h, src/vim9instr.c, src/proto/
vim9instr.pro,
src/vim9expr.c, src/proto/
vim9expr.pro, src/vim9execute.c,
src/testdir/test_user_func.vim, src/testdir/test_vim9_func.vim,
src/testdir/test_vim9_disassemble.vim
*** ../vim-9.0.0369/runtime/doc/eval.txt 2022-08-29 18:16:11.574636829 +0100
--- runtime/doc/eval.txt 2022-09-03 17:36:27.213054907 +0100
***************
*** 2978,2983 ****
--- 2979,3040 ----
let y = GetList()->Filter()
+ CLEANING UP IN A FUNCTION ~
+ *:defer*
+ :defer {func}({args}) Call {func} when the current function is done.
+ {args} are evaluated here.
+
+ Quite often a command in a function has a global effect, which must be undone
+ when the function finishes. Handling this in all kinds of situations can be a
+ hassle. Especially when an unexpected error is encountered. This can be done
+ with `try` / `finally` blocks, but this gets complicated when there is more
+ than one.
+
+ A much simpler solution is using `defer`. It schedules a function call when
+ the function is returning, no matter if there is an error. Example: >
+ func Filter(text)
+ call writefile(a:text, 'Tempfile')
+ call system('filter < Tempfile > Outfile')
+ call Handle('Outfile')
+ call delete('Tempfile')
+ call delete('Outfile')
+ endfunc
+
+ Here 'Tempfile' and 'Outfile' will not be deleted if something causes the
+ function to abort. `:defer` can be used to avoid that: >
+ func Filter(text)
+ call writefile(a:text, 'Tempfile')
+ defer delete('Tempfile')
+ defer delete('Outfile')
+ call system('filter < Tempfile > Outfile')
+ call Handle('Outfile')
+ endfunc
+
+ Note that deleting "Outfile" is scheduled before calling system(), since it
+ can be created even when `system()` fails.
+
+ The defered functions are called in reverse order, the last one added is
+ executed first. A useless example: >
+ func Useless()
+ for s in range(3)
+ defer execute('echomsg "number ' .. s .. '"')
+ endfor
+ endfunc
+
+ Now `:messages` shows:
+ number 2
+ number 1
+ number 0
+
+ Any return value of the deferred function is discarded. The function cannot
+ be followed by anything, such as "->func" or ".member". Currently `:defer
+ GetArg()->TheFunc()` does not work, it may work in a later version.
+
+ Errors are reported but do not cause aborting execution of deferred functions.
+
+ No range is accepted.
+
+
AUTOMATICALLY LOADING FUNCTIONS ~
*autoload-functions*
When using many or large functions, it's possible to automatically define them
*** ../vim-9.0.0369/src/ex_cmds.h 2022-08-31 17:48:05.711547579 +0100
--- src/ex_cmds.h 2022-09-03 14:45:42.389458416 +0100
***************
*** 467,472 ****
--- 467,475 ----
EXCMD(CMD_defcompile, "defcompile", ex_defcompile,
EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK|EX_TRLBAR|EX_EXTRA,
ADDR_NONE),
+ EXCMD(CMD_defer, "defer", ex_call,
+ EX_NEEDARG|EX_EXTRA|EX_NOTRLCOM|EX_EXPR_ARG|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK,
+ ADDR_NONE),
EXCMD(CMD_delcommand, "delcommand", ex_delcommand,
EX_NEEDARG|EX_WORD1|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
ADDR_NONE),
*** ../vim-9.0.0369/src/ex_cmdidxs.h 2022-08-31 17:48:05.711547579 +0100
--- src/ex_cmdidxs.h 2022-09-03 14:34:48.226709187 +0100
***************
*** 9,36 ****
/* b */ 21,
/* c */ 45,
/* d */ 112,
! /* e */ 137,
! /* f */ 166,
! /* g */ 183,
! /* h */ 189,
! /* i */ 199,
! /* j */ 219,
! /* k */ 221,
! /* l */ 226,
! /* m */ 289,
! /* n */ 307,
! /* o */ 327,
! /* p */ 339,
! /* q */ 378,
! /* r */ 381,
! /* s */ 401,
! /* t */ 471,
! /* u */ 517,
! /* v */ 528,
! /* w */ 549,
! /* x */ 563,
! /* y */ 573,
! /* z */ 574
};
/*
--- 9,36 ----
/* b */ 21,
/* c */ 45,
/* d */ 112,
! /* e */ 138,
! /* f */ 167,
! /* g */ 184,
! /* h */ 190,
! /* i */ 200,
! /* j */ 220,
! /* k */ 222,
! /* l */ 227,
! /* m */ 290,
! /* n */ 308,
! /* o */ 328,
! /* p */ 340,
! /* q */ 379,
! /* r */ 382,
! /* s */ 402,
! /* t */ 472,
! /* u */ 518,
! /* v */ 529,
! /* w */ 550,
! /* x */ 564,
! /* y */ 574,
! /* z */ 575
};
/*
***************
*** 44,50 ****
/* a */ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 0, 0, 8, 17, 0, 18, 0, 0, 0, 0, 0 },
/* b */ { 2, 0, 0, 5, 6, 8, 0, 0, 0, 0, 0, 9, 10, 11, 12, 13, 0, 14, 0, 0, 0, 0, 23, 0, 0, 0 },
/* c */ { 3, 12, 16, 18, 20, 22, 25, 0, 0, 0, 0, 33, 38, 41, 47, 57, 59, 60, 61, 0, 63, 0, 66, 0, 0, 0 },
! /* d */ { 0, 0, 0, 0, 0, 0, 0, 0, 8, 18, 0, 19, 0, 0, 20, 0, 0, 22, 23, 0, 0, 0, 0, 0, 0, 0 },
/* e */ { 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 9, 11, 12, 0, 0, 0, 0, 0, 0, 0, 23, 0, 24, 0, 0 },
/* f */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0 },
/* g */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 4, 5, 0, 0, 0, 0 },
--- 44,50 ----
/* a */ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 0, 0, 8, 17, 0, 18, 0, 0, 0, 0, 0 },
/* b */ { 2, 0, 0, 5, 6, 8, 0, 0, 0, 0, 0, 9, 10, 11, 12, 13, 0, 14, 0, 0, 0, 0, 23, 0, 0, 0 },
/* c */ { 3, 12, 16, 18, 20, 22, 25, 0, 0, 0, 0, 33, 38, 41, 47, 57, 59, 60, 61, 0, 63, 0, 66, 0, 0, 0 },
! /* d */ { 0, 0, 0, 0, 0, 0, 0, 0, 9, 19, 0, 20, 0, 0, 21, 0, 0, 23, 24, 0, 0, 0, 0, 0, 0, 0 },
/* e */ { 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 9, 11, 12, 0, 0, 0, 0, 0, 0, 0, 23, 0, 24, 0, 0 },
/* f */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0 },
/* g */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 4, 5, 0, 0, 0, 0 },
***************
*** 69,72 ****
/* z */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
! static const int command_count = 591;
--- 69,72 ----
/* z */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
! static const int command_count = 592;
*** ../vim-9.0.0369/src/userfunc.c 2022-08-28 18:52:06.667888932 +0100
--- src/userfunc.c 2022-09-03 16:51:07.136075989 +0100
***************
*** 1728,1771 ****
}
/*
! * Allocate a variable for the result of a function.
! * Return OK or FAIL.
*/
! int
! get_func_tv(
! char_u *name, // name of the function
! int len, // length of "name" or -1 to use strlen()
! typval_T *rettv,
! char_u **arg, // argument, pointing to the '('
! evalarg_T *evalarg, // for line continuation
! funcexe_T *funcexe) // various values
{
! char_u *argp;
int ret = OK;
- typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments
- int argcount = 0; // number of arguments found
int vim9script = in_vim9script();
int evaluate = evalarg == NULL
? FALSE : (evalarg->eval_flags & EVAL_EVALUATE);
! /*
! * Get the arguments.
! */
! argp = *arg;
! while (argcount < MAX_FUNC_ARGS - (funcexe->fe_partial == NULL ? 0
! : funcexe->fe_partial->pt_argc))
{
// skip the '(' or ',' and possibly line breaks
argp = skipwhite_and_linebreak(argp + 1, evalarg);
if (*argp == ')' || *argp == ',' || *argp == NUL)
break;
! if (eval1(&argp, &argvars[argcount], evalarg) == FAIL)
{
ret = FAIL;
break;
}
! ++argcount;
// The comma should come right after the argument, but this wasn't
// checked previously, thus only enforce it in Vim9 script.
if (vim9script)
--- 1728,1763 ----
}
/*
! * Get function arguments at "*arg" and advance it.
! * Return them in "*argvars[MAX_FUNC_ARGS + 1]" and the count in "argcount".
*/
! static int
! get_func_arguments(
! char_u **arg,
! evalarg_T *evalarg,
! int partial_argc,
! typval_T *argvars,
! int *argcount)
{
! char_u *argp = *arg;
int ret = OK;
int vim9script = in_vim9script();
int evaluate = evalarg == NULL
? FALSE : (evalarg->eval_flags & EVAL_EVALUATE);
! while (*argcount < MAX_FUNC_ARGS - partial_argc)
{
// skip the '(' or ',' and possibly line breaks
argp = skipwhite_and_linebreak(argp + 1, evalarg);
if (*argp == ')' || *argp == ',' || *argp == NUL)
break;
! if (eval1(&argp, &argvars[*argcount], evalarg) == FAIL)
{
ret = FAIL;
break;
}
! ++*argcount;
// The comma should come right after the argument, but this wasn't
// checked previously, thus only enforce it in Vim9 script.
if (vim9script)
***************
*** 1791,1801 ****
--- 1783,1823 ----
break;
}
}
+
argp = skipwhite_and_linebreak(argp, evalarg);
if (*argp == ')')
++argp;
else
ret = FAIL;
+ *arg = argp;
+ return ret;
+ }
+
+ /*
+ * Call a function and put the result in "rettv".
+ * Return OK or FAIL.
+ */
+ int
+ get_func_tv(
+ char_u *name, // name of the function
+ int len, // length of "name" or -1 to use strlen()
+ typval_T *rettv,
+ char_u **arg, // argument, pointing to the '('
+ evalarg_T *evalarg, // for line continuation
+ funcexe_T *funcexe) // various values
+ {
+ char_u *argp;
+ int ret = OK;
+ typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments
+ int argcount = 0; // number of arguments found
+ int vim9script = in_vim9script();
+ int evaluate = evalarg == NULL
+ ? FALSE : (evalarg->eval_flags & EVAL_EVALUATE);
+
+ argp = *arg;
+ ret = get_func_arguments(&argp, evalarg,
+ (funcexe->fe_partial == NULL ? 0 : funcexe->fe_partial->pt_argc),
+ argvars, &argcount);
if (ret == OK)
{
***************
*** 2884,2889 ****
--- 2906,2914 ----
do_cmdline(NULL, get_func_line, (void *)fc,
DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
+ // Invoke functions added with ":defer".
+ handle_defer();
+
--RedrawingDisabled;
// when the function was aborted because of an error, return -1
***************
*** 5457,5464 ****
--- 5482,5645 ----
clear_evalarg(&evalarg, eap);
}
+ static int
+ ex_call_inner(
+ exarg_T *eap,
+ char_u *name,
+ char_u **arg,
+ char_u *startarg,
+ funcexe_T *funcexe_init,
+ evalarg_T *evalarg)
+ {
+ linenr_T lnum;
+ int doesrange;
+ typval_T rettv;
+ int failed = FALSE;
+
+ /*
+ * When skipping, evaluate the function once, to find the end of the
+ * arguments.
+ * When the function takes a range, this is discovered after the first
+ * call, and the loop is broken.
+ */
+ if (eap->skip)
+ {
+ ++emsg_skip;
+ lnum = eap->line2; // do it once, also with an invalid range
+ }
+ else
+ lnum = eap->line1;
+ for ( ; lnum <= eap->line2; ++lnum)
+ {
+ funcexe_T funcexe;
+
+ if (!eap->skip && eap->addr_count > 0)
+ {
+ if (lnum > curbuf->b_ml.ml_line_count)
+ {
+ // If the function deleted lines or switched to another buffer
+ // the line number may become invalid.
+ emsg(_(e_invalid_range));
+ break;
+ }
+ curwin->w_cursor.lnum = lnum;
+ curwin->w_cursor.col = 0;
+ curwin->w_cursor.coladd = 0;
+ }
+ *arg = startarg;
+
+ funcexe = *funcexe_init;
+ funcexe.fe_doesrange = &doesrange;
+ rettv.v_type = VAR_UNKNOWN; // clear_tv() uses this
+ if (get_func_tv(name, -1, &rettv, arg, evalarg, &funcexe) == FAIL)
+ {
+ failed = TRUE;
+ break;
+ }
+ if (has_watchexpr())
+ dbg_check_breakpoint(eap);
+
+ // Handle a function returning a Funcref, Dictionary or List.
+ if (handle_subscript(arg, NULL, &rettv,
+ eap->skip ? NULL : &EVALARG_EVALUATE, TRUE) == FAIL)
+ {
+ failed = TRUE;
+ break;
+ }
+
+ clear_tv(&rettv);
+ if (doesrange || eap->skip)
+ break;
+
+ // Stop when immediately aborting on error, or when an interrupt
+ // occurred or an exception was thrown but not caught.
+ // get_func_tv() returned OK, so that the check for trailing
+ // characters below is executed.
+ if (aborting())
+ break;
+ }
+ if (eap->skip)
+ --emsg_skip;
+ return failed;
+ }
+
+ /*
+ * Core part of ":defer func(arg)". "arg" points to the "(" and is advanced.
+ * Returns FAIL or OK.
+ */
+ static int
+ ex_defer_inner(char_u *name, char_u **arg, evalarg_T *evalarg)
+ {
+ typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments
+ int argcount = 0; // number of arguments found
+ defer_T *dr;
+ int ret = FAIL;
+ char_u *saved_name;
+
+ if (current_funccal == NULL)
+ {
+ semsg(_(e_str_not_inside_function), "defer");
+ return FAIL;
+ }
+ if (get_func_arguments(arg, evalarg, FALSE, argvars, &argcount) == FAIL)
+ goto theend;
+ saved_name = vim_strsave(name);
+ if (saved_name == NULL)
+ goto theend;
+
+ if (current_funccal->fc_defer.ga_itemsize == 0)
+ ga_init2(¤t_funccal->fc_defer, sizeof(defer_T), 10);
+ if (ga_grow(¤t_funccal->fc_defer, 1) == FAIL)
+ goto theend;
+ dr = ((defer_T *)current_funccal->fc_defer.ga_data)
+ + current_funccal->fc_defer.ga_len++;
+ dr->dr_name = saved_name;
+ dr->dr_argcount = argcount;
+ while (argcount > 0)
+ {
+ --argcount;
+ dr->dr_argvars[argcount] = argvars[argcount];
+ }
+ ret = OK;
+
+ theend:
+ while (--argcount >= 0)
+ clear_tv(&argvars[argcount]);
+ return ret;
+ }
+
+ /*
+ * 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);
+ funcexe.fe_evaluate = TRUE;
+
+ rettv.v_type = VAR_UNKNOWN; // clear_tv() uses this
+ call_func(dr->dr_name, -1, &rettv,
+ dr->dr_argcount, dr->dr_argvars, &funcexe);
+ clear_tv(&rettv);
+ vim_free(dr->dr_name);
+ for (i = dr->dr_argcount - 1; i >= 0; --i)
+ clear_tv(&dr->dr_argvars[i]);
+ }
+ ga_clear(¤t_funccal->fc_defer);
+ }
+
/*
* ":1,25call func(arg1, arg2)" function call.
+ * ":defer func(arg1, arg2)" deferred function call.
*/
void
ex_call(exarg_T *eap)
***************
*** 5468,5476 ****
char_u *name;
char_u *tofree;
int len;
- typval_T rettv;
- linenr_T lnum;
- int doesrange;
int failed = FALSE;
funcdict_T fudi;
partial_T *partial = NULL;
--- 5649,5654 ----
***************
*** 5482,5487 ****
--- 5660,5667 ----
fill_evalarg_from_eap(&evalarg, eap, eap->skip);
if (eap->skip)
{
+ typval_T rettv;
+
// trans_function_name() doesn't work well when skipping, use eval0()
// instead to skip to any following command, e.g. for:
// :if 0 | call dict.foo().bar() | endif
***************
*** 5531,5612 ****
goto end;
}
! /*
! * When skipping, evaluate the function once, to find the end of the
! * arguments.
! * When the function takes a range, this is discovered after the first
! * call, and the loop is broken.
! */
! if (eap->skip)
{
! ++emsg_skip;
! lnum = eap->line2; // do it once, also with an invalid range
}
else
- lnum = eap->line1;
- for ( ; lnum <= eap->line2; ++lnum)
{
funcexe_T funcexe;
- if (!eap->skip && eap->addr_count > 0)
- {
- if (lnum > curbuf->b_ml.ml_line_count)
- {
- // If the function deleted lines or switched to another buffer
- // the line number may become invalid.
- emsg(_(e_invalid_range));
- break;
- }
- curwin->w_cursor.lnum = lnum;
- curwin->w_cursor.col = 0;
- curwin->w_cursor.coladd = 0;
- }
- arg = startarg;
-
CLEAR_FIELD(funcexe);
! funcexe.fe_firstline = eap->line1;
! funcexe.fe_lastline = eap->line2;
! funcexe.fe_doesrange = &doesrange;
! funcexe.fe_evaluate = !eap->skip;
funcexe.fe_partial = partial;
funcexe.fe_selfdict = fudi.fd_dict;
! funcexe.fe_check_type = type;
funcexe.fe_found_var = found_var;
! rettv.v_type = VAR_UNKNOWN; // clear_tv() uses this
! if (get_func_tv(name, -1, &rettv, &arg, &evalarg, &funcexe) == FAIL)
! {
! failed = TRUE;
! break;
! }
! if (has_watchexpr())
! dbg_check_breakpoint(eap);
!
! // Handle a function returning a Funcref, Dictionary or List.
! if (handle_subscript(&arg, NULL, &rettv,
! eap->skip ? NULL : &EVALARG_EVALUATE, TRUE) == FAIL)
! {
! failed = TRUE;
! break;
! }
!
! clear_tv(&rettv);
! if (doesrange || eap->skip)
! break;
!
! // Stop when immediately aborting on error, or when an interrupt
! // occurred or an exception was thrown but not caught.
! // get_func_tv() returned OK, so that the check for trailing
! // characters below is executed.
! if (aborting())
! break;
}
- if (eap->skip)
- --emsg_skip;
// When inside :try we need to check for following "| catch" or "| endtry".
// Not when there was an error, but do check if an exception was thrown.
! if ((!aborting() || did_throw)
! && (!failed || eap->cstack->cs_trylevel > 0))
{
// Check for trailing illegal characters and a following command.
arg = skipwhite(arg);
--- 5711,5739 ----
goto end;
}
! if (eap->cmdidx == CMD_defer)
{
! arg = startarg;
! failed = ex_defer_inner(name, &arg, &evalarg) == FAIL;
}
else
{
funcexe_T funcexe;
CLEAR_FIELD(funcexe);
! funcexe.fe_check_type = type;
funcexe.fe_partial = partial;
funcexe.fe_selfdict = fudi.fd_dict;
! funcexe.fe_firstline = eap->line1;
! funcexe.fe_lastline = eap->line2;
funcexe.fe_found_var = found_var;
! funcexe.fe_evaluate = !eap->skip;
! failed = ex_call_inner(eap, name, &arg, startarg, &funcexe, &evalarg);
}
// When inside :try we need to check for following "| catch" or "| endtry".
// Not when there was an error, but do check if an exception was thrown.
! if ((!aborting() || did_throw) && (!failed || eap->cstack->cs_trylevel > 0))
{
// Check for trailing illegal characters and a following command.
arg = skipwhite(arg);
*** ../vim-9.0.0369/src/proto/
userfunc.pro 2022-06-29 12:54:48.068572061 +0100
--- src/proto/
userfunc.pro 2022-09-03 15:24:43.479993899 +0100
***************
*** 58,63 ****
--- 58,64 ----
void func_ref(char_u *name);
void func_ptr_ref(ufunc_T *fp);
void ex_return(exarg_T *eap);
+ 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);
*** ../vim-9.0.0369/src/structs.h 2022-08-29 18:16:11.578636822 +0100
--- src/structs.h 2022-09-03 15:58:17.286657171 +0100
***************
*** 1753,1758 ****
--- 1753,1759 ----
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
#ifdef FEAT_PROFILE
proftime_T prof_child; // time spent in a child
#endif
***************
*** 1767,1772 ****
--- 1768,1781 ----
// "func"
};
+ // structure used as item in "fc_defer"
+ typedef struct
+ {
+ char_u *dr_name; // function name, allocated
+ typval_T dr_argvars[MAX_FUNC_ARGS + 1];
+ int dr_argcount;
+ } defer_T;
+
/*
* Struct used by trans_function_name()
*/
***************
*** 2850,2856 ****
int b_u_synced; // entry lists are synced
long b_u_seq_last; // last used undo sequence number
long b_u_save_nr_last; // counter for last file write
! long b_u_seq_cur; // hu_seq of header below which we are now
time_T b_u_time_cur; // uh_time of header below which we are now
long b_u_save_nr_cur; // file write nr after which we are now
--- 2859,2865 ----
int b_u_synced; // entry lists are synced
long b_u_seq_last; // last used undo sequence number
long b_u_save_nr_last; // counter for last file write
! long b_u_seq_cur; // uh_seq of header below which we are now
time_T b_u_time_cur; // uh_time of header below which we are now
long b_u_save_nr_cur; // file write nr after which we are now
*** ../vim-9.0.0369/src/vim9compile.c 2022-09-03 10:52:18.395075356 +0100
--- src/vim9compile.c 2022-09-03 21:06:45.596800531 +0100
***************
*** 2373,2379 ****
r = generate_PUSHBLOB(cctx, blob_alloc());
break;
case VAR_FUNC:
! r = generate_PUSHFUNC(cctx, NULL, &t_func_void);
break;
case VAR_LIST:
r = generate_NEWLIST(cctx, 0, FALSE);
--- 2373,2379 ----
r = generate_PUSHBLOB(cctx, blob_alloc());
break;
case VAR_FUNC:
! r = generate_PUSHFUNC(cctx, NULL, &t_func_void, TRUE);
break;
case VAR_LIST:
r = generate_NEWLIST(cctx, 0, FALSE);
***************
*** 2748,2753 ****
--- 2748,2754 ----
// Was compiled in this mode before: Free old instructions.
delete_def_function_contents(dfunc, FALSE);
ga_clear_strings(&dfunc->df_var_names);
+ dfunc->df_defer_var_idx = 0;
}
else
{
***************
*** 3249,3254 ****
--- 3250,3259 ----
line = compile_eval(p, &cctx);
break;
+ case CMD_defer:
+ line = compile_defer(p, &cctx);
+ break;
+
case CMD_echo:
case CMD_echon:
case CMD_echoconsole:
*** ../vim-9.0.0369/src/vim9cmds.c 2022-09-01 16:00:49.730496296 +0100
--- src/vim9cmds.c 2022-09-03 21:11:16.524703244 +0100
***************
*** 1654,1659 ****
--- 1654,1662 ----
return p;
}
+ /*
+ * Compile an expression or function call.
+ */
char_u *
compile_eval(char_u *arg, cctx_T *cctx)
{
***************
*** 1682,1687 ****
--- 1685,1751 ----
}
/*
+ * Compile "defer func(arg)".
+ */
+ char_u *
+ compile_defer(char_u *arg_start, cctx_T *cctx)
+ {
+ char_u *p;
+ char_u *arg = arg_start;
+ int argcount = 0;
+ dfunc_T *dfunc;
+ type_T *type;
+ int func_idx;
+
+ // Get a funcref for the function name.
+ // TODO: better way to find the "(".
+ p = vim_strchr(arg, '(');
+ if (p == NULL)
+ {
+ semsg(_(e_missing_parenthesis_str), arg);
+ return NULL;
+ }
+ *p = NUL;
+ func_idx = find_internal_func(arg);
+ if (func_idx >= 0)
+ // TODO: better type
+ generate_PUSHFUNC(cctx, (char_u *)internal_func_name(func_idx),
+ &t_func_any, FALSE);
+ else if (compile_expr0(&arg, cctx) == FAIL)
+ return NULL;
+ *p = '(';
+
+ // check for function type
+ type = get_type_on_stack(cctx, 0);
+ if (type->tt_type != VAR_FUNC)
+ {
+ emsg(_(e_function_name_required));
+ return NULL;
+ }
+
+ // compile the arguments
+ arg = skipwhite(p + 1);
+ if (compile_arguments(&arg, cctx, &argcount, CA_NOT_SPECIAL) == FAIL)
+ return NULL;
+
+ // TODO: check argument count with "type"
+
+ dfunc = ((dfunc_T *)def_functions.ga_data) + cctx->ctx_ufunc->uf_dfunc_idx;
+ if (dfunc->df_defer_var_idx == 0)
+ {
+ lvar_T *lvar = reserve_local(cctx, (char_u *)"@defer@", 7,
+ TRUE, &t_list_any);
+ if (lvar == NULL)
+ return NULL;
+ dfunc->df_defer_var_idx = lvar->lv_idx + 1;
+ }
+ if (generate_DEFER(cctx, dfunc->df_defer_var_idx - 1, argcount) == FAIL)
+ return NULL;
+
+ return skipwhite(arg);
+ }
+
+ /*
* compile "echo expr"
* compile "echomsg expr"
* compile "echoerr expr"
*** ../vim-9.0.0369/src/proto/
vim9cmds.pro 2022-06-27 23:15:29.000000000 +0100
--- src/proto/
vim9cmds.pro 2022-09-03 19:27:40.826629675 +0100
***************
*** 21,26 ****
--- 21,27 ----
char_u *compile_endtry(char_u *arg, cctx_T *cctx);
char_u *compile_throw(char_u *arg, cctx_T *cctx);
char_u *compile_eval(char_u *arg, cctx_T *cctx);
+ char_u *compile_defer(char_u *arg_start, cctx_T *cctx);
char_u *compile_mult_expr(char_u *arg, int cmdidx, cctx_T *cctx);
char_u *compile_put(char_u *arg, exarg_T *eap, cctx_T *cctx);
char_u *compile_exec(char_u *line_arg, exarg_T *eap, cctx_T *cctx);
*** ../vim-9.0.0369/src/vim9.h 2022-09-01 16:00:49.726496301 +0100
--- src/vim9.h 2022-09-03 19:18:53.055031709 +0100
***************
*** 113,118 ****
--- 113,119 ----
ISN_FUNCREF, // push a function ref to dfunc isn_arg.funcref
ISN_NEWFUNC, // create a global function from a lambda function
ISN_DEF, // list functions
+ ISN_DEFER, // :defer argument count is isn_arg.number
// expression operations
ISN_JUMP, // jump if condition is matched isn_arg.jump
***************
*** 419,424 ****
--- 420,431 ----
int dbg_break_lnum; // first line to break after
} debug_T;
+ // arguments to ISN_DEFER
+ typedef struct {
+ int defer_var_idx; // local variable index for defer list
+ int defer_argcount; // number of arguments
+ } deferins_T;
+
/*
* Instruction
*/
***************
*** 468,473 ****
--- 475,481 ----
tobool_T tobool;
getitem_T getitem;
debug_T debug;
+ deferins_T defer;
} isn_arg;
};
***************
*** 498,503 ****
--- 506,514 ----
int df_varcount; // number of local variables
int df_has_closure; // one if a closure was created
+ int df_defer_var_idx; // index of local variable that has a list
+ // of deferred function calls; zero if not
+ // set
};
// Number of entries used by stack frame for a function call.
***************
*** 735,740 ****
--- 746,760 ----
// lhs_name is not NULL
};
+ /*
+ * List of special functions for "compile_arguments()".
+ */
+ typedef enum {
+ CA_NOT_SPECIAL,
+ CA_SEARCHPAIR, // {skip} in searchpair() and searchpairpos()
+ CA_SUBSTITUTE, // {sub} in substitute(), when prefixed with \=
+ } ca_special_T;
+
// flags for typval2type()
#define TVTT_DO_MEMBER 1
#define TVTT_MORE_SPECIFIC 2 // get most specific type for member
*** ../vim-9.0.0369/src/vim9instr.c 2022-09-01 17:26:14.163194043 +0100
--- src/vim9instr.c 2022-09-03 21:08:12.132769664 +0100
***************
*** 616,622 ****
case VAR_FUNC:
if (tv->vval.v_string != NULL)
iemsg("non-null function constant not supported");
! generate_PUSHFUNC(cctx, NULL, &t_func_unknown);
break;
case VAR_PARTIAL:
if (tv->vval.v_partial != NULL)
--- 616,622 ----
case VAR_FUNC:
if (tv->vval.v_string != NULL)
iemsg("non-null function constant not supported");
! generate_PUSHFUNC(cctx, NULL, &t_func_unknown, TRUE);
break;
case VAR_PARTIAL:
if (tv->vval.v_partial != NULL)
***************
*** 796,804 ****
/*
* Generate an ISN_PUSHFUNC instruction with name "name".
*/
int
! generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type)
{
isn_T *isn;
char_u *funcname;
--- 796,806 ----
/*
* Generate an ISN_PUSHFUNC instruction with name "name".
+ * When "may_prefix" is TRUE prefix "g:" unless "name" is script-local or
+ * autoload.
*/
int
! generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type, int may_prefix)
{
isn_T *isn;
char_u *funcname;
***************
*** 808,814 ****
return FAIL;
if (name == NULL)
funcname = NULL;
! else if (*name == K_SPECIAL // script-local
|| vim_strchr(name, AUTOLOAD_CHAR) != NULL) // autoload
funcname = vim_strsave(name);
else
--- 810,817 ----
return FAIL;
if (name == NULL)
funcname = NULL;
! else if (!may_prefix
! || *name == K_SPECIAL // script-local
|| vim_strchr(name, AUTOLOAD_CHAR) != NULL) // autoload
funcname = vim_strsave(name);
else
***************
*** 1679,1684 ****
--- 1682,1703 ----
}
/*
+ * Generate an ISN_DEFER instruction.
+ */
+ int
+ generate_DEFER(cctx_T *cctx, int var_idx, int argcount)
+ {
+ isn_T *isn;
+
+ RETURN_OK_IF_SKIP(cctx);
+ if ((isn = generate_instr_drop(cctx, ISN_DEFER, argcount + 1)) == NULL)
+ return FAIL;
+ isn->isn_arg.defer.defer_var_idx = var_idx;
+ isn->isn_arg.defer.defer_argcount = argcount;
+ return OK;
+ }
+
+ /*
* Generate an ISN_STRINGMEMBER instruction.
*/
int
***************
*** 2240,2245 ****
--- 2259,2265 ----
case ISN_CONCAT:
case ISN_COND2BOOL:
case ISN_DEBUG:
+ case ISN_DEFER:
case ISN_DROP:
case ISN_ECHO:
case ISN_ECHOCONSOLE:
***************
*** 2296,2316 ****
case ISN_STOREINDEX:
case ISN_STORENR:
case ISN_SOURCE:
! case ISN_STOREOUTER:
! case ISN_STORERANGE:
! case ISN_STOREREG:
! case ISN_STOREV:
! case ISN_STRINDEX:
! case ISN_STRSLICE:
! case ISN_THROW:
! case ISN_TRYCONT:
! case ISN_UNLETINDEX:
! case ISN_UNLETRANGE:
! case ISN_UNPACK:
! case ISN_USEDICT:
// nothing allocated
break;
! }
}
void
--- 2316,2336 ----
case ISN_STOREINDEX:
case ISN_STORENR:
case ISN_SOURCE:
! case ISN_STOREOUTER:
! case ISN_STORERANGE:
! case ISN_STOREREG:
! case ISN_STOREV:
! case ISN_STRINDEX:
! case ISN_STRSLICE:
! case ISN_THROW:
! case ISN_TRYCONT:
! case ISN_UNLETINDEX:
! case ISN_UNLETRANGE:
! case ISN_UNPACK:
! case ISN_USEDICT:
// nothing allocated
break;
! }
}
void
*** ../vim-9.0.0369/src/proto/
vim9instr.pro 2022-06-27 23:15:30.000000000 +0100
--- src/proto/
vim9instr.pro 2022-09-03 21:06:35.156804223 +0100
***************
*** 23,29 ****
int generate_PUSHCHANNEL(cctx_T *cctx);
int generate_PUSHJOB(cctx_T *cctx);
int generate_PUSHBLOB(cctx_T *cctx, blob_T *blob);
! int generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type);
int generate_AUTOLOAD(cctx_T *cctx, char_u *name, type_T *type);
int generate_GETITEM(cctx_T *cctx, int index, int with_op);
int generate_SLICE(cctx_T *cctx, int count);
--- 23,29 ----
int generate_PUSHCHANNEL(cctx_T *cctx);
int generate_PUSHJOB(cctx_T *cctx);
int generate_PUSHBLOB(cctx_T *cctx, blob_T *blob);
! int generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type, int may_prefix);
int generate_AUTOLOAD(cctx_T *cctx, char_u *name, type_T *type);
int generate_GETITEM(cctx_T *cctx, int index, int with_op);
int generate_SLICE(cctx_T *cctx, int count);
***************
*** 52,57 ****
--- 52,58 ----
int generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount);
int generate_UCALL(cctx_T *cctx, char_u *name, int argcount);
int generate_PCALL(cctx_T *cctx, int argcount, char_u *name, type_T *type, int at_top);
+ int generate_DEFER(cctx_T *cctx, int var_idx, int argcount);
int generate_STRINGMEMBER(cctx_T *cctx, char_u *name, size_t len);
int generate_ECHO(cctx_T *cctx, int with_white, int count);
int generate_MULT_EXPR(cctx_T *cctx, isntype_T isn_type, int count);
*** ../vim-9.0.0369/src/vim9expr.c 2022-08-20 14:51:13.810510131 +0100
--- src/vim9expr.c 2022-09-03 21:07:27.052785757 +0100
***************
*** 313,319 ****
// name. If a '(' follows it must be a function. Otherwise we
// don't know, it can be "script.Func".
if (cc == '(' || paren_follows_after_expr)
! res = generate_PUSHFUNC(cctx, auto_name, &t_func_any);
else
res = generate_AUTOLOAD(cctx, auto_name, &t_any);
vim_free(auto_name);
--- 313,319 ----
// name. If a '(' follows it must be a function. Otherwise we
// don't know, it can be "script.Func".
if (cc == '(' || paren_follows_after_expr)
! res = generate_PUSHFUNC(cctx, auto_name, &t_func_any, TRUE);
else
res = generate_AUTOLOAD(cctx, auto_name, &t_any);
vim_free(auto_name);
***************
*** 329,335 ****
char_u sid_name[MAX_FUNC_NAME_LEN];
func_name_with_sid(exp_name, import->imp_sid, sid_name);
! res = generate_PUSHFUNC(cctx, sid_name, &t_func_any);
}
else
res = generate_OLDSCRIPT(cctx, ISN_LOADEXPORT, exp_name,
--- 329,335 ----
char_u sid_name[MAX_FUNC_NAME_LEN];
func_name_with_sid(exp_name, import->imp_sid, sid_name);
! res = generate_PUSHFUNC(cctx, sid_name, &t_func_any, TRUE);
}
else
res = generate_OLDSCRIPT(cctx, ISN_LOADEXPORT, exp_name,
***************
*** 353,359 ****
if (ufunc != NULL)
{
// function call or function reference
! generate_PUSHFUNC(cctx, ufunc->uf_name, NULL);
return OK;
}
return FAIL;
--- 353,359 ----
if (ufunc != NULL)
{
// function call or function reference
! generate_PUSHFUNC(cctx, ufunc->uf_name, NULL, TRUE);
return OK;
}
return FAIL;
***************
*** 387,393 ****
if (func_needs_compiling(ufunc, compile_type)
&& compile_def_function(ufunc, TRUE, compile_type, NULL) == FAIL)
return FAIL;
! return generate_PUSHFUNC(cctx, ufunc->uf_name, ufunc->uf_func_type);
}
/*
--- 387,393 ----
if (func_needs_compiling(ufunc, compile_type)
&& compile_def_function(ufunc, TRUE, compile_type, NULL) == FAIL)
return FAIL;
! return generate_PUSHFUNC(cctx, ufunc->uf_name, ufunc->uf_func_type, TRUE);
}
/*
***************
*** 610,628 ****
}
/*
- * List of special functions for "compile_arguments".
- */
- typedef enum {
- CA_NOT_SPECIAL,
- CA_SEARCHPAIR, // {skip} in searchpair() and searchpairpos()
- CA_SUBSTITUTE, // {sub} in substitute(), when prefixed with \=
- } ca_special_T;
-
- /*
* Compile the argument expressions.
* "arg" points to just after the "(" and is advanced to after the ")"
*/
! static int
compile_arguments(
char_u **arg,
cctx_T *cctx,
--- 610,619 ----
}
/*
* Compile the argument expressions.
* "arg" points to just after the "(" and is advanced to after the ")"
*/
! int
compile_arguments(
char_u **arg,
cctx_T *cctx,
*** ../vim-9.0.0369/src/proto/
vim9expr.pro 2022-06-27 23:15:30.000000000 +0100
--- src/proto/
vim9expr.pro 2022-09-03 19:27:49.334623659 +0100
***************
*** 4,9 ****
--- 4,10 ----
int compile_member(int is_slice, int *keeping_dict, cctx_T *cctx);
int compile_load_scriptvar(cctx_T *cctx, char_u *name, char_u *start, char_u **end);
int compile_load(char_u **arg, char_u *end_arg, cctx_T *cctx, int is_expr, int error);
+ int compile_arguments(char_u **arg, cctx_T *cctx, int *argcount, ca_special_T special_fn);
char_u *to_name_end(char_u *arg, int use_namespace);
char_u *to_name_const_end(char_u *arg);
int get_lambda_tv_and_compile(char_u **arg, typval_T *rettv, int types_optional, evalarg_T *evalarg);
*** ../vim-9.0.0369/src/vim9execute.c 2022-09-01 16:00:49.730496296 +0100
--- src/vim9execute.c 2022-09-03 20:48:19.693212011 +0100
***************
*** 101,109 ****
--- 101,115 ----
static garray_T profile_info_ga = {0, 0, sizeof(profinfo_T), 20, NULL};
#endif
+ // Get pointer to item in the stack.
+ #define STACK_TV(idx) (((typval_T *)ectx->ec_stack.ga_data) + idx)
+
// Get pointer to item relative to the bottom of the stack, -1 is the last one.
#define STACK_TV_BOT(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_stack.ga_len + (idx))
+ // Get pointer to a local variable on the stack. Negative for arguments.
+ #define STACK_TV_VAR(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_frame_idx + STACK_FRAME_SIZE + idx)
+
void
to_string_error(vartype_T vartype)
{
***************
*** 610,618 ****
return OK;
}
- // Get pointer to item in the stack.
- #define STACK_TV(idx) (((typval_T *)ectx->ec_stack.ga_data) + idx)
-
// Double linked list of funcstack_T in use.
static funcstack_T *first_funcstack = NULL;
--- 616,621 ----
***************
*** 843,848 ****
--- 846,934 ----
}
/*
+ * Handle ISN_DEFER. Stack has a function reference and "argcount" arguments.
+ * The local variable that lists deferred functions is "var_idx".
+ * Returns OK or FAIL.
+ */
+ static int
+ add_defer_func(int var_idx, int argcount, ectx_T *ectx)
+ {
+ typval_T *defer_tv = STACK_TV_VAR(var_idx);
+ list_T *defer_l;
+ typval_T *func_tv;
+ list_T *l;
+ int i;
+ typval_T listval;
+
+ if (defer_tv->v_type != VAR_LIST)
+ {
+ // first one, allocate the list
+ if (rettv_list_alloc(defer_tv) == FAIL)
+ return FAIL;
+ }
+ defer_l = defer_tv->vval.v_list;
+
+ l = list_alloc_with_items(argcount + 1);
+ if (l == NULL)
+ return FAIL;
+ listval.v_type = VAR_LIST;
+ listval.vval.v_list = l;
+ listval.v_lock = 0;
+ if (list_insert_tv(defer_l, &listval,
+ defer_l == NULL ? NULL : defer_l->lv_first) == FAIL)
+ {
+ vim_free(l);
+ return FAIL;
+ }
+
+ func_tv = STACK_TV_BOT(-argcount - 1);
+ // TODO: check type is a funcref
+ list_set_item(l, 0, func_tv);
+
+ for (i = 1; i <= argcount; ++i)
+ list_set_item(l, i, STACK_TV_BOT(-argcount + i - 1));
+ ectx->ec_stack.ga_len -= argcount + 1;
+ return OK;
+ }
+
+ /*
+ * Invoked when returning from a function: Invoke any deferred calls.
+ */
+ static void
+ invoke_defer_funcs(ectx_T *ectx)
+ {
+ dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
+ + ectx->ec_dfunc_idx;
+ typval_T *defer_tv = STACK_TV_VAR(dfunc->df_defer_var_idx - 1);
+ listitem_T *li;
+
+ if (defer_tv->v_type != VAR_LIST)
+ return; // no function added
+ for (li = defer_tv->vval.v_list->lv_first; li != NULL; li = li->li_next)
+ {
+ list_T *l = li->li_tv.vval.v_list;
+ typval_T rettv;
+ typval_T argvars[MAX_FUNC_ARGS];
+ int i;
+ listitem_T *arg_li = l->lv_first;
+ funcexe_T funcexe;
+
+ for (i = 0; i < l->lv_len - 1; ++i)
+ {
+ arg_li = arg_li->li_next;
+ argvars[i] = arg_li->li_tv;
+ }
+
+ CLEAR_FIELD(funcexe);
+ funcexe.fe_evaluate = TRUE;
+ rettv.v_type = VAR_UNKNOWN;
+ (void)call_func(l->lv_first->li_tv.vval.v_string, -1,
+ &rettv, l->lv_len - 1, argvars, &funcexe);
+ clear_tv(&rettv);
+ }
+ }
+
+ /*
* Return from the current function.
*/
static int
***************
*** 876,881 ****
--- 962,970 ----
}
#endif
+ if (dfunc->df_defer_var_idx > 0)
+ invoke_defer_funcs(ectx);
+
// No check for uf_refcount being zero, cannot think of a way that would
// happen.
--dfunc->df_ufunc->uf_calls;
***************
*** 949,956 ****
return OK;
}
- #undef STACK_TV
-
/*
* Prepare arguments and rettv for calling a builtin or user function.
*/
--- 1038,1043 ----
***************
*** 1732,1747 ****
int subs_status;
} subs_expr_T;
- // Get pointer to item in the stack.
- #define STACK_TV(idx) (((typval_T *)ectx->ec_stack.ga_data) + idx)
-
- // Get pointer to item at the bottom of the stack, -1 is the bottom.
- #undef STACK_TV_BOT
- #define STACK_TV_BOT(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_stack.ga_len + idx)
-
- // Get pointer to a local variable on the stack. Negative for arguments.
- #define STACK_TV_VAR(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_frame_idx + STACK_FRAME_SIZE + idx)
-
// Set when calling do_debug().
static ectx_T *debug_context = NULL;
static int debug_var_count;
--- 1819,1824 ----
***************
*** 3670,3675 ****
--- 3747,3759 ----
}
break;
+ // :defer func(arg)
+ case ISN_DEFER:
+ if (add_defer_func(iptr->isn_arg.defer.defer_var_idx,
+ iptr->isn_arg.defer.defer_argcount, ectx) == FAIL)
+ goto on_error;
+ break;
+
// return from a :def function call without a value
case ISN_RETURN_VOID:
if (GA_GROW_FAILS(&ectx->ec_stack, 1))
***************
*** 5024,5029 ****
--- 5108,5121 ----
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;
return ret;
***************
*** 5903,5908 ****
--- 5995,6004 ----
case ISN_PCALL_END:
smsg("%s%4d PCALL end", pfx, current);
break;
+ case ISN_DEFER:
+ smsg("%s%4d DEFER %d args", pfx, current,
+ (int)iptr->isn_arg.defer.defer_argcount);
+ break;
case ISN_RETURN:
smsg("%s%4d RETURN", pfx, current);
break;
*** ../vim-9.0.0369/src/testdir/test_user_func.vim 2022-04-09 10:59:49.000000000 +0100
--- src/testdir/test_user_func.vim 2022-09-03 21:28:20.276325774 +0100
***************
*** 529,532 ****
--- 529,564 ----
bw!
endfunc
+ func AddDefer(arg)
+ call extend(g:deferred, [a:arg])
+ endfunc
+
+ func WithDeferTwo()
+ call extend(g:deferred, ['in Two'])
+ for nr in range(3)
+ defer AddDefer('Two' .. nr)
+ endfor
+ call extend(g:deferred, ['end Two'])
+ endfunc
+
+ func WithDeferOne()
+ call extend(g:deferred, ['in One'])
+ call writefile(['text'], 'Xfuncdefer')
+ defer delete('Xfuncdefer')
+ defer AddDefer('One')
+ call WithDeferTwo()
+ call extend(g:deferred, ['end One'])
+ endfunc
+
+ func Test_defer()
+ let g:deferred = []
+ call WithDeferOne()
+
+ call assert_equal(['in One', 'in Two', 'end Two', 'Two2', 'Two1', 'Two0', 'end One', 'One'], g:deferred)
+ unlet g:deferred
+
+ call assert_equal('', glob('Xfuncdefer'))
+ endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
*** ../vim-9.0.0369/src/testdir/test_vim9_func.vim 2022-08-30 18:42:13.170331381 +0100
--- src/testdir/test_vim9_func.vim 2022-09-03 21:22:34.784454129 +0100
***************
*** 4272,4277 ****
--- 4272,4307 ----
v9.CheckScriptFailure(lines, 'E777', 2)
enddef
+ def AddDefer(s: string)
+ g:deferred->extend([s])
+ enddef
+
+ def DeferTwo()
+ g:deferred->extend(['in Two'])
+ for n in range(3)
+ defer g:AddDefer('two' .. n)
+ endfor
+ g:deferred->extend(['end Two'])
+ enddef
+
+ def DeferOne()
+ g:deferred->extend(['in One'])
+ defer g:AddDefer('one')
+ g:DeferTwo()
+ g:deferred->extend(['end One'])
+
+ writefile(['text'], 'XdeferFile')
+ defer delete('XdeferFile')
+ enddef
+
+ def Test_defer()
+ g:deferred = []
+ g:DeferOne()
+ assert_equal(['in One', 'in Two', 'end Two', 'two2', 'two1', 'two0', 'end One', 'one'], g:deferred)
+ unlet g:deferred
+ assert_equal('', glob('XdeferFile'))
+ enddef
+
" The following messes up syntax highlight, keep near the end.
if has('python3')
def Test_python3_command()
*** ../vim-9.0.0369/src/testdir/test_vim9_disassemble.vim 2022-09-01 16:00:49.730496296 +0100
--- src/testdir/test_vim9_disassemble.vim 2022-09-03 21:12:37.848673708 +0100
***************
*** 2900,2903 ****
--- 2900,2918 ----
'10 RETURN void', instr)
enddef
+ def s:OneDefer()
+ defer delete("file")
+ enddef
+
+ def Test_disassemble_defer()
+ var instr = execute('disassemble s:OneDefer')
+ assert_match('OneDefer\_s*' ..
+ 'defer delete("file")\_s*' ..
+ '\d PUSHFUNC "delete"\_s*' ..
+ '\d PUSHS "file"\_s*' ..
+ '\d DEFER 1 args\_s*' ..
+ '\d RETURN\_s*',
+ instr)
+ enddef
+
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
*** ../vim-9.0.0369/src/version.c 2022-09-03 13:58:42.210028187 +0100
--- src/version.c 2022-09-03 21:28:53.004313588 +0100
***************
*** 709,710 ****
--- 709,712 ----
{ /* Add new patch number below this line */
+ /**/
+ 370,
/**/
--
Git catch 22: "merge is not possible because you have unmerged files."
/// Bram Moolenaar -- Br...@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 ///