Patch 9.0.0370

8 views
Skip to first unread message

Bram Moolenaar

unread,
Sep 3, 2022, 4:36:36 PM9/3/22
to vim...@googlegroups.com

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(&current_funccal->fc_defer, sizeof(defer_T), 10);
+ if (ga_grow(&current_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(&current_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 ///

Bram Moolenaar

unread,
Sep 3, 2022, 4:54:39 PM9/3/22
to vim...@googlegroups.com, Bram Moolenaar

I wrote:

> 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

This is a nice way to cleanup. The idea comes from Go (although there
is probably another language where Go got the idea).

Note that the "paint is still wet" on this. It probably doesn't always
work, doesn't cleanup in case of failures, needs more testing, etc.
But you can see how it works. Eventually it will be more reliable than
other mechanisms.

Feedback welcome.

--
Article in the first Free Software Magazine: "Bram Moolenaar studied
electrical engineering at the Technical University of Delft and
graduated in 1985 on a multi-processor Unix architecture."
Response by "dimator": Could the school not afford a proper
stage for the ceremony?
Reply all
Reply to author
Forward
0 new messages