aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/api/vim.c7
-rw-r--r--src/nvim/buffer_defs.h3
-rw-r--r--src/nvim/eval.c1542
-rw-r--r--src/nvim/eval.h38
-rw-r--r--src/nvim/eval.lua2
-rw-r--r--src/nvim/eval/typval_encode.c.h2
-rw-r--r--src/nvim/eval_defs.h104
-rw-r--r--src/nvim/getchar.c14
-rw-r--r--src/nvim/globals.h3
-rw-r--r--src/nvim/main.c5
-rw-r--r--src/nvim/normal.c10
-rw-r--r--src/nvim/regexp.c156
-rw-r--r--src/nvim/testdir/Makefile2
-rw-r--r--src/nvim/testdir/runtest.vim6
-rw-r--r--src/nvim/testdir/test_alot.vim2
-rw-r--r--src/nvim/testdir/test_cmdline.vim5
-rw-r--r--src/nvim/testdir/test_expr.vim65
-rw-r--r--src/nvim/testdir/test_filter_map.vim81
-rw-r--r--src/nvim/testdir/test_lambda.vim287
-rw-r--r--src/nvim/testdir/test_partial.vim21
-rw-r--r--src/nvim/version.c42
21 files changed, 1877 insertions, 520 deletions
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 1732ee0bae..fd0f98c017 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -221,11 +221,10 @@ Object nvim_call_function(String fname, Array args, Error *err)
// Call the function
typval_T rettv;
int dummy;
- int r = call_func((char_u *) fname.data, (int) fname.size,
- &rettv, (int) args.size, vim_args,
+ int r = call_func((char_u *)fname.data, (int)fname.size,
+ &rettv, (int)args.size, vim_args, NULL,
curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy,
- true,
- NULL, NULL);
+ true, NULL, NULL);
if (r == FAIL) {
api_set_error(err, Exception, _("Error calling function."));
}
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 73d06de964..9c5c7859ff 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -31,8 +31,6 @@ typedef struct {
#include "nvim/hashtab.h"
// for dict_T
#include "nvim/eval_defs.h"
-// for proftime_T
-#include "nvim/profile.h"
// for String
#include "nvim/api/private/defs.h"
// for Map(K, V)
@@ -90,7 +88,6 @@ typedef struct {
typedef struct window_S win_T;
typedef struct wininfo_S wininfo_T;
typedef struct frame_S frame_T;
-typedef int scid_T; /* script ID */
// for struct memline (it needs memfile_T)
#include "nvim/memline_defs.h"
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 18b5de7e62..57c2368523 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -200,22 +200,26 @@ static garray_T ga_scripts = {0, 0, sizeof(scriptvar_T *), 4, NULL};
static int echo_attr = 0; /* attributes used for ":echo" */
-/* Values for trans_function_name() argument: */
-#define TFN_INT 1 /* internal function name OK */
-#define TFN_QUIET 2 /* no error messages */
-#define TFN_NO_AUTOLOAD 4 /* do not use script autoloading */
-
-/* Values for get_lval() flags argument: */
-#define GLV_QUIET TFN_QUIET /* no error messages */
-#define GLV_NO_AUTOLOAD TFN_NO_AUTOLOAD /* do not use script autoloading */
-
-/* function flags */
-#define FC_ABORT 1 /* abort function on error */
-#define FC_RANGE 2 /* function accepts range */
-#define FC_DICT 4 /* Dict function, uses "self" */
-
-/* The names of packages that once were loaded are remembered. */
-static garray_T ga_loaded = {0, 0, sizeof(char_u *), 4, NULL};
+// Values for trans_function_name() argument:
+#define TFN_INT 1 // internal function name OK
+#define TFN_QUIET 2 // no error messages
+#define TFN_NO_AUTOLOAD 4 // do not use script autoloading
+#define TFN_NO_DEREF 8 // do not dereference a Funcref
+
+// Values for get_lval() flags argument:
+#define GLV_QUIET TFN_QUIET // no error messages
+#define GLV_NO_AUTOLOAD TFN_NO_AUTOLOAD // do not use script autoloading
+
+// function flags
+#define FC_ABORT 0x01 // abort function on error
+#define FC_RANGE 0x02 // function accepts range
+#define FC_DICT 0x04 // Dict function, uses "self"
+#define FC_CLOSURE 0x08 // closure, uses outer scope variables
+#define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0
+#define FC_REMOVED 0x20 // function redefined while uf_refcount > 0
+
+// The names of packages that once were loaded are remembered.
+static garray_T ga_loaded = { 0, 0, sizeof(char_u *), 4, NULL };
// List heads for garbage collection. Although there can be a reference loop
// from partial to dict to partial, we don't need to keep track of the partial,
@@ -223,38 +227,11 @@ static garray_T ga_loaded = {0, 0, sizeof(char_u *), 4, NULL};
static dict_T *first_dict = NULL; // list of all dicts
static list_T *first_list = NULL; // list of all lists
+#define FLEN_FIXED 40
+
#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j]
#define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j]
-#define VAR_SHORT_LEN 20 /* short variable name length */
-#define FIXVAR_CNT 12 /* number of fixed variables */
-
-/* structure to hold info for a function that is currently being executed. */
-typedef struct funccall_S funccall_T;
-
-struct funccall_S {
- ufunc_T *func; /* function being called */
- int linenr; /* next line to be executed */
- int returned; /* ":return" used */
- struct /* fixed variables for arguments */
- {
- dictitem_T var; /* variable (without room for name) */
- char_u room[VAR_SHORT_LEN]; /* room for the name */
- } fixvar[FIXVAR_CNT];
- dict_T l_vars; /* l: local function variables */
- dictitem_T l_vars_var; /* variable for l: scope */
- dict_T l_avars; /* a: argument variables */
- dictitem_T l_avars_var; /* variable for a: scope */
- list_T l_varlist; /* list for a:000 */
- listitem_T l_listitems[MAX_FUNC_ARGS]; /* listitems for a:000 */
- typval_T *rettv; /* return value */
- 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 */
- proftime_T prof_child; /* time spent in a child */
- funccall_T *caller; /* calling function or NULL */
-};
-
/*
* Info used by a ":for" loop.
*/
@@ -266,15 +243,6 @@ typedef struct {
} forinfo_T;
/*
- * Struct used by trans_function_name()
- */
-typedef struct {
- dict_T *fd_dict; /* Dictionary used */
- char_u *fd_newkey; /* new key in "dict" in allocated memory */
- dictitem_T *fd_di; /* Dictionary item used */
-} funcdict_T;
-
-/*
* enum used by var_flavour()
*/
typedef enum {
@@ -389,6 +357,7 @@ static struct vimvar {
VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO),
VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO),
VV(VV_VIM_DID_ENTER, "vim_did_enter", VAR_NUMBER, VV_RO),
+ VV(VV_TESTING, "testing", VAR_NUMBER, 0),
VV(VV_TYPE_NUMBER, "t_number", VAR_NUMBER, VV_RO),
VV(VV_TYPE_STRING, "t_string", VAR_NUMBER, VV_RO),
VV(VV_TYPE_FUNC, "t_func", VAR_NUMBER, VV_RO),
@@ -648,12 +617,11 @@ void eval_clear(void)
xfree(SCRIPT_SV(i));
ga_clear(&ga_scripts);
- /* unreferenced lists and dicts */
- (void)garbage_collect();
+ // unreferenced lists and dicts
+ (void)garbage_collect(false);
- /* functions */
+ // functions
free_all_functions();
- hash_clear(&func_hashtab);
}
#endif
@@ -1229,8 +1197,8 @@ int call_vim_function(
++sandbox;
}
- rettv->v_type = VAR_UNKNOWN; /* clear_tv() uses this */
- ret = call_func(func, (int)STRLEN(func), rettv, argc, argvars,
+ rettv->v_type = VAR_UNKNOWN; // clear_tv() uses this
+ ret = call_func(func, (int)STRLEN(func), rettv, argc, argvars, NULL,
curwin->w_cursor.lnum, curwin->w_cursor.lnum,
&doesrange, true, NULL, NULL);
if (safe) {
@@ -3064,7 +3032,10 @@ int do_unlet(char_u *name, int forceit)
return FAIL;
}
hi = hash_find(ht, varname);
- if (!HASHITEM_EMPTY(hi)) {
+ if (HASHITEM_EMPTY(hi)) {
+ hi = find_hi_in_scoped_ht((const char *)name, &ht);
+ }
+ if (hi != NULL && !HASHITEM_EMPTY(hi)) {
di = HI2DI(hi);
if (var_check_fixed(di->di_flags, name, false)
|| var_check_ro(di->di_flags, name, false)
@@ -4300,8 +4271,12 @@ static int eval7(
case '[': ret = get_list_tv(arg, rettv, evaluate);
break;
+ // Lambda: {arg, arg -> expr}
// Dictionary: {key: val, key: val}
- case '{': ret = get_dict_tv(arg, rettv, evaluate);
+ case '{': ret = get_lambda_tv(arg, rettv, evaluate);
+ if (ret == NOTDONE) {
+ ret = get_dict_tv(arg, rettv, evaluate);
+ }
break;
// Option value: &name
@@ -4354,6 +4329,11 @@ static int eval7(
} else {
if (**arg == '(') { // recursive!
partial_T *partial;
+
+ if (!evaluate) {
+ check_vars((const char *)s, len);
+ }
+
// If "s" is the name of a variable of type VAR_FUNC
// use its contents.
s = deref_func_name((const char *)s, &len, &partial, !evaluate);
@@ -4383,6 +4363,7 @@ static int eval7(
} else if (evaluate) {
ret = get_var_tv((const char *)s, len, rettv, NULL, true, false);
} else {
+ check_vars((const char *)s, len);
ret = OK;
}
}
@@ -4936,6 +4917,15 @@ static int get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate)
return OK;
}
+/// @return the function name of the partial.
+char_u *partial_name(partial_T *pt)
+{
+ if (pt->pt_name != NULL) {
+ return pt->pt_name;
+ }
+ return pt->pt_func->uf_name;
+}
+
static void partial_free(partial_T *pt)
{
for (int i = 0; i < pt->pt_argc; i++) {
@@ -4943,8 +4933,12 @@ static void partial_free(partial_T *pt)
}
xfree(pt->pt_argv);
dict_unref(pt->pt_dict);
- func_unref(pt->pt_name);
- xfree(pt->pt_name);
+ if (pt->pt_name != NULL) {
+ func_unref(pt->pt_name);
+ xfree(pt->pt_name);
+ } else {
+ func_ptr_unref(pt->pt_func);
+ }
xfree(pt);
}
@@ -5208,12 +5202,12 @@ static bool func_equal(
// empty and NULL function name considered the same
s1 = tv1->v_type == VAR_FUNC ? tv1->vval.v_string
- : tv1->vval.v_partial->pt_name;
+ : partial_name(tv1->vval.v_partial);
if (s1 != NULL && *s1 == NUL) {
s1 = NULL;
}
s2 = tv2->v_type == VAR_FUNC ? tv2->vval.v_string
- : tv2->vval.v_partial->pt_name;
+ : partial_name(tv2->vval.v_partial);
if (s2 != NULL && *s2 == NUL) {
s2 = NULL;
}
@@ -5282,7 +5276,8 @@ tv_equal (
return TRUE;
}
- // For VAR_FUNC and VAR_PARTIAL only compare the function name.
+ // For VAR_FUNC and VAR_PARTIAL compare the function name, bound dict and
+ // arguments.
if ((tv1->v_type == VAR_FUNC
|| (tv1->v_type == VAR_PARTIAL && tv1->vval.v_partial != NULL))
&& (tv2->v_type == VAR_FUNC
@@ -5811,6 +5806,9 @@ int get_copyID(void)
return current_copyID;
}
+// Used by get_func_tv()
+static garray_T funcargs = GA_EMPTY_INIT_VALUE;
+
/*
* Garbage collection for lists and dictionaries.
*
@@ -5833,16 +5831,19 @@ int get_copyID(void)
/// Do garbage collection for lists and dicts.
///
+/// @param testing true if called from test_garbagecollect_now().
/// @returns true if some memory was freed.
-bool garbage_collect(void)
+bool garbage_collect(bool testing)
{
bool abort = false;
#define ABORTING(func) abort = abort || func
- // Only do this once.
- want_garbage_collect = false;
- may_garbage_collect = false;
- garbage_collect_at_exit = false;
+ if (!testing) {
+ // Only do this once.
+ want_garbage_collect = false;
+ may_garbage_collect = false;
+ garbage_collect_at_exit = false;
+ }
// We advance by two because we add one for items referenced through
// previous_funccal.
@@ -5855,6 +5856,7 @@ bool garbage_collect(void)
// referenced through previous_funccal. This must be first, because if
// the item is referenced elsewhere the funccal must not be freed.
for (funccall_T *fc = previous_funccal; fc != NULL; fc = fc->caller) {
+ fc->fc_copyID = copyID + 1;
ABORTING(set_ref_in_ht)(&fc->l_vars.dv_hashtab, copyID + 1, NULL);
ABORTING(set_ref_in_ht)(&fc->l_avars.dv_hashtab, copyID + 1, NULL);
}
@@ -5930,10 +5932,14 @@ bool garbage_collect(void)
// function-local variables
for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) {
+ fc->fc_copyID = copyID;
ABORTING(set_ref_in_ht)(&fc->l_vars.dv_hashtab, copyID, NULL);
ABORTING(set_ref_in_ht)(&fc->l_avars.dv_hashtab, copyID, NULL);
}
+ // named functions (matters for closures)
+ ABORTING(set_ref_in_functions(copyID));
+
// Jobs
{
TerminalJobData *data;
@@ -5952,6 +5958,12 @@ bool garbage_collect(void)
})
}
+ // function call arguments, if v:testing is set.
+ for (int i = 0; i < funcargs.ga_len; i++) {
+ ABORTING(set_ref_in_item)(((typval_T **)funcargs.ga_data)[i],
+ copyID, NULL, NULL);
+ }
+
// v: vars
ABORTING(set_ref_in_ht)(&vimvarht, copyID, NULL);
@@ -6006,7 +6018,7 @@ bool garbage_collect(void)
if (did_free_funccal) {
// When a funccal was freed some more items might be garbage
// collected, so run again.
- (void)garbage_collect();
+ (void)garbage_collect(testing);
}
} else if (p_verbose > 0) {
verb_msg((char_u *)_(
@@ -6235,6 +6247,7 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
// A partial does not have a copyID, because it cannot contain itself.
if (pt != NULL) {
+ abort = set_ref_in_func(pt->pt_name, pt->pt_func, copyID);
if (pt->pt_dict != NULL) {
typval_T dtv;
@@ -6251,6 +6264,8 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
break;
}
case VAR_FUNC:
+ abort = set_ref_in_func(tv->vval.v_string, NULL, copyID);
+ break;
case VAR_UNKNOWN:
case VAR_SPECIAL:
case VAR_FLOAT:
@@ -6262,6 +6277,29 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
return abort;
}
+/// Set "copyID" in all functions available by name.
+bool set_ref_in_functions(int copyID)
+{
+ int todo;
+ hashitem_T *hi = NULL;
+ bool abort = false;
+ ufunc_T *fp;
+
+ todo = (int)func_hashtab.ht_used;
+ for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) {
+ if (!HASHITEM_EMPTY(hi)) {
+ todo--;
+ fp = HI2UF(hi);
+ if (!func_name_refcount(fp->uf_name)) {
+ abort = abort || set_ref_in_func(NULL, fp, copyID);
+ }
+ }
+ }
+ return abort;
+}
+
+
+
/// Mark all lists and dicts referenced in given mark
///
/// @returns true if setting references failed somehow.
@@ -6308,9 +6346,20 @@ static inline bool set_ref_dict(dict_T *dict, int copyID)
return false;
}
-/*
- * Allocate an empty header for a dictionary.
- */
+static bool set_ref_in_funccal(funccall_T *fc, int copyID)
+{
+ bool abort = false;
+
+ if (fc->fc_copyID != copyID) {
+ fc->fc_copyID = copyID;
+ abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL);
+ abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL);
+ abort = abort || set_ref_in_func(NULL, fc->func, copyID);
+ }
+ return abort;
+}
+
+/// Allocate an empty header for a dictionary.
dict_T *dict_alloc(void) FUNC_ATTR_NONNULL_RET
{
dict_T *d = xmalloc(sizeof(dict_T));
@@ -6855,6 +6904,232 @@ failret:
return OK;
}
+/// Get function arguments.
+static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs,
+ int *varargs, bool skip)
+{
+ bool mustend = false;
+ char_u *arg = *argp;
+ char_u *p = arg;
+ int c;
+ int i;
+
+ if (newargs != NULL) {
+ ga_init(newargs, (int)sizeof(char_u *), 3);
+ }
+
+ if (varargs != NULL) {
+ *varargs = false;
+ }
+
+ // Isolate the arguments: "arg1, arg2, ...)"
+ while (*p != endchar) {
+ if (p[0] == '.' && p[1] == '.' && p[2] == '.') {
+ if (varargs != NULL) {
+ *varargs = true;
+ }
+ p += 3;
+ mustend = true;
+ } else {
+ arg = p;
+ while (ASCII_ISALNUM(*p) || *p == '_') {
+ p++;
+ }
+ if (arg == p || isdigit(*arg)
+ || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0)
+ || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) {
+ if (!skip) {
+ EMSG2(_("E125: Illegal argument: %s"), arg);
+ }
+ break;
+ }
+ if (newargs != NULL) {
+ ga_grow(newargs, 1);
+ c = *p;
+ *p = NUL;
+ arg = vim_strsave(arg);
+ if (arg == NULL) {
+ *p = c;
+ goto err_ret;
+ }
+
+ // Check for duplicate argument name.
+ for (i = 0; i < newargs->ga_len; i++) {
+ if (STRCMP(((char_u **)(newargs->ga_data))[i], arg) == 0) {
+ EMSG2(_("E853: Duplicate argument name: %s"), arg);
+ xfree(arg);
+ goto err_ret;
+ }
+ }
+ ((char_u **)(newargs->ga_data))[newargs->ga_len] = arg;
+ newargs->ga_len++;
+
+ *p = c;
+ }
+ if (*p == ',') {
+ p++;
+ } else {
+ mustend = true;
+ }
+ }
+ p = skipwhite(p);
+ if (mustend && *p != endchar) {
+ if (!skip) {
+ EMSG2(_(e_invarg2), *argp);
+ }
+ break;
+ }
+ }
+ if (*p != endchar) {
+ goto err_ret;
+ }
+ p++; // skip "endchar"
+
+ *argp = p;
+ return OK;
+
+err_ret:
+ if (newargs != NULL) {
+ ga_clear_strings(newargs);
+ }
+ return FAIL;
+}
+
+/// Register function "fp" as using "current_funccal" as its scope.
+static void register_closure(ufunc_T *fp)
+{
+ if (fp->uf_scoped == current_funccal) {
+ // no change
+ return;
+ }
+ funccal_unref(fp->uf_scoped, fp, false);
+ fp->uf_scoped = current_funccal;
+ current_funccal->fc_refcount++;
+ ga_grow(&current_funccal->fc_funcs, 1);
+ ((ufunc_T **)current_funccal->fc_funcs.ga_data)
+ [current_funccal->fc_funcs.ga_len++] = fp;
+}
+
+/// Parse a lambda expression and get a Funcref from "*arg".
+///
+/// @return OK or FAIL. Returns NOTDONE for dict or {expr}.
+static int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
+{
+ garray_T newargs = GA_EMPTY_INIT_VALUE;
+ garray_T *pnewargs;
+ ufunc_T *fp = NULL;
+ int varargs;
+ int ret;
+ char_u *start = skipwhite(*arg + 1);
+ char_u *s, *e;
+ static int lambda_no = 0;
+ int *old_eval_lavars = eval_lavars_used;
+ int eval_lavars = false;
+
+ // First, check if this is a lambda expression. "->" must exists.
+ ret = get_function_args(&start, '-', NULL, NULL, true);
+ if (ret == FAIL || *start != '>') {
+ return NOTDONE;
+ }
+
+ // Parse the arguments again.
+ if (evaluate) {
+ pnewargs = &newargs;
+ } else {
+ pnewargs = NULL;
+ }
+ *arg = skipwhite(*arg + 1);
+ ret = get_function_args(arg, '-', pnewargs, &varargs, false);
+ if (ret == FAIL || **arg != '>') {
+ goto errret;
+ }
+
+ // Set up a flag for checking local variables and arguments.
+ if (evaluate) {
+ eval_lavars_used = &eval_lavars;
+ }
+
+ // Get the start and the end of the expression.
+ *arg = skipwhite(*arg + 1);
+ s = *arg;
+ ret = skip_expr(arg);
+ if (ret == FAIL) {
+ goto errret;
+ }
+ e = *arg;
+ *arg = skipwhite(*arg);
+ if (**arg != '}') {
+ goto errret;
+ }
+ (*arg)++;
+
+ if (evaluate) {
+ int len, flags = 0;
+ char_u *p;
+ char_u name[20];
+ partial_T *pt;
+ garray_T newlines;
+
+ lambda_no++;
+ snprintf((char *)name, sizeof(name), "<lambda>%d", lambda_no);
+
+ fp = (ufunc_T *)xcalloc(1, sizeof(ufunc_T) + STRLEN(name));
+ pt = (partial_T *)xcalloc(1, sizeof(partial_T));
+ if (pt == NULL) {
+ xfree(fp);
+ goto errret;
+ }
+
+ ga_init(&newlines, (int)sizeof(char_u *), 1);
+ ga_grow(&newlines, 1);
+
+ // Add "return " before the expression.
+ len = 7 + e - s + 1;
+ p = (char_u *)xmalloc(len);
+ ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p;
+ STRCPY(p, "return ");
+ STRLCPY(p + 7, s, e - s + 1);
+
+ fp->uf_refcount = 1;
+ STRCPY(fp->uf_name, name);
+ hash_add(&func_hashtab, UF2HIKEY(fp));
+ fp->uf_args = newargs;
+ fp->uf_lines = newlines;
+ if (current_funccal != NULL && eval_lavars) {
+ flags |= FC_CLOSURE;
+ register_closure(fp);
+ } else {
+ fp->uf_scoped = NULL;
+ }
+
+ fp->uf_tml_count = NULL;
+ fp->uf_tml_total = NULL;
+ fp->uf_tml_self = NULL;
+ fp->uf_profiling = false;
+ if (prof_def_func()) {
+ func_do_profile(fp);
+ }
+ fp->uf_varargs = true;
+ fp->uf_flags = flags;
+ fp->uf_calls = 0;
+ fp->uf_script_ID = current_SID;
+
+ pt->pt_func = fp;
+ pt->pt_refcount = 1;
+ rettv->vval.v_partial = pt;
+ rettv->v_type = VAR_PARTIAL;
+ }
+
+ eval_lavars_used = old_eval_lavars;
+ return OK;
+
+errret:
+ ga_clear_strings(&newargs);
+ xfree(fp);
+ eval_lavars_used = old_eval_lavars;
+ return FAIL;
+}
+
/// Convert the string to a floating point number
///
/// This uses strtod(). setlocale(LC_NUMERIC, "C") has been used earlier to
@@ -6977,9 +7252,6 @@ char_u *get_expr_name(expand_T *xp, int idx)
return get_user_var_name(xp, ++intidx);
}
-
-
-
/// Find internal function in hash functions
///
/// @param[in] name Name of the function.
@@ -7035,8 +7307,9 @@ static char_u *deref_func_name(const char *name, int *lenp,
if (partialp != NULL) {
*partialp = pt;
}
- *lenp = (int)STRLEN(pt->pt_name);
- return pt->pt_name;
+ char_u *s = partial_name(pt);
+ *lenp = (int)STRLEN(s);
+ return s;
}
return (char_u *)name;
@@ -7088,9 +7361,24 @@ get_func_tv (
ret = FAIL;
if (ret == OK) {
- ret = call_func(name, len, rettv, argcount, argvars,
+ int i = 0;
+
+ if (get_vim_var_nr(VV_TESTING)) {
+ // Prepare for calling garbagecollect_for_testing(), need to know
+ // what variables are used on the call stack.
+ if (funcargs.ga_itemsize == 0) {
+ ga_init(&funcargs, (int)sizeof(typval_T *), 50);
+ }
+ for (i = 0; i < argcount; i++) {
+ ga_grow(&funcargs, 1);
+ ((typval_T **)funcargs.ga_data)[funcargs.ga_len++] = &argvars[i];
+ }
+ }
+ ret = call_func(name, len, rettv, argcount, argvars, NULL,
firstline, lastline, doesrange, evaluate,
partial, selfdict);
+
+ funcargs.ga_len -= i;
} else if (!aborting()) {
if (argcount == MAX_FUNC_ARGS) {
emsg_funcname(N_("E740: Too many arguments for function %s"), name);
@@ -7115,6 +7403,7 @@ typedef enum {
ERROR_NONE,
ERROR_OTHER,
ERROR_BOTH,
+ ERROR_DELETED,
} FnameTransError;
#define FLEN_FIXED 40
@@ -7176,7 +7465,43 @@ static char_u *fname_trans_sid(const char_u *const name,
return fname;
}
+/// Mark all lists and dicts referenced through function "name" with "copyID".
+/// "list_stack" is used to add lists to be marked. Can be NULL.
+/// "ht_stack" is used to add hashtabs to be marked. Can be NULL.
+///
+/// @return true if setting references failed somehow.
+bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID)
+{
+ ufunc_T *fp = fp_in;
+ funccall_T *fc;
+ int error = ERROR_NONE;
+ char_u fname_buf[FLEN_FIXED + 1];
+ char_u *tofree = NULL;
+ char_u *fname;
+ bool abort = false;
+ if (name == NULL && fp_in == NULL) {
+ return false;
+ }
+
+ if (fp_in == NULL) {
+ fname = fname_trans_sid(name, fname_buf, &tofree, &error);
+ fp = find_func(fname);
+ }
+ if (fp != NULL) {
+ for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped) {
+ abort = abort || set_ref_in_funccal(fc, copyID);
+ }
+ }
+ xfree(tofree);
+ return abort;
+}
+
/// Call a function with its resolved parameters
+///
+/// "argv_func", when not NULL, can be used to fill in arguments only when the
+/// invoked function uses them. It is called like this:
+/// new_argcount = argv_func(current_argcount, argv, called_func_argcount)
+///
/// Return FAIL when the function can't be called, OK otherwise.
/// Also returns OK when an error was encountered while executing the function.
int
@@ -7187,6 +7512,7 @@ call_func(
int argcount_in, // number of "argvars"
typval_T *argvars_in, // vars for arguments, must have "argcount"
// PLUS ONE elements!
+ ArgvFunc argv_func, // function to fill in argvars
linenr_T firstline, // first line of range
linenr_T lastline, // last line of range
int *doesrange, // return: function handled range
@@ -7255,9 +7581,13 @@ call_func(
if (!builtin_function((const char *)rfname, -1)) {
// User defined function.
- fp = find_func(rfname);
+ if (partial != NULL && partial->pt_func != NULL) {
+ fp = partial->pt_func;
+ } else {
+ fp = find_func(rfname);
+ }
- /* Trigger FuncUndefined event, may load the function. */
+ // Trigger FuncUndefined event, may load the function.
if (fp == NULL
&& apply_autocmds(EVENT_FUNCUNDEFINED, rfname, rfname, TRUE, NULL)
&& !aborting()) {
@@ -7271,19 +7601,25 @@ call_func(
fp = find_func(rfname);
}
- if (fp != NULL) {
- if (fp->uf_flags & FC_RANGE)
- *doesrange = TRUE;
- if (argcount < fp->uf_args.ga_len)
+ if (fp != NULL && (fp->uf_flags & FC_DELETED)) {
+ error = ERROR_DELETED;
+ } else if (fp != NULL) {
+ if (argv_func != NULL) {
+ argcount = argv_func(argcount, argvars, fp->uf_args.ga_len);
+ }
+ if (fp->uf_flags & FC_RANGE) {
+ *doesrange = true;
+ }
+ if (argcount < fp->uf_args.ga_len) {
error = ERROR_TOOFEW;
- else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len)
+ } else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) {
error = ERROR_TOOMANY;
- else if ((fp->uf_flags & FC_DICT) && selfdict == NULL)
+ } else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) {
error = ERROR_DICT;
- else {
+ } else {
// Call the user function.
call_user_func(fp, argcount, argvars, rettv, firstline, lastline,
- (fp->uf_flags & FC_DICT) ? selfdict : NULL);
+ (fp->uf_flags & FC_DICT) ? selfdict : NULL);
error = ERROR_NONE;
}
}
@@ -7326,6 +7662,9 @@ call_func(
case ERROR_UNKNOWN:
emsg_funcname(N_("E117: Unknown function: %s"), name);
break;
+ case ERROR_DELETED:
+ emsg_funcname(N_("E933: Function was deleted: %s"), name);
+ break;
case ERROR_TOOMANY:
emsg_funcname(e_toomanyarg, name);
break;
@@ -8131,7 +8470,7 @@ int func_call(char_u *name, typval_T *args, partial_T *partial,
}
if (item == NULL) {
- r = call_func(name, (int)STRLEN(name), rettv, argc, argv,
+ r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL,
curwin->w_cursor.lnum, curwin->w_cursor.lnum,
&dummy, true, partial, selfdict);
}
@@ -8161,7 +8500,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr)
func = argvars[0].vval.v_string;
} else if (argvars[0].v_type == VAR_PARTIAL) {
partial = argvars[0].vval.v_partial;
- func = partial->pt_name;
+ func = partial_name(partial);
} else {
func = get_tv_string(&argvars[0]);
}
@@ -8594,11 +8933,6 @@ static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
- if (argvars[2].v_type != VAR_FUNC && argvars[2].v_type != VAR_STRING) {
- EMSG2(e_invarg2, "funcref");
- return;
- }
-
char *key_pattern = (char *)get_tv_string_chk(argvars + 1);
assert(key_pattern);
const size_t key_len = STRLEN(argvars[1].vval.v_string);
@@ -8610,6 +8944,7 @@ static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
Callback callback;
if (!callback_from_typval(&callback, &argvars[2])) {
+ EMSG2(e_invarg2, "funcref");
return;
}
@@ -8952,7 +9287,7 @@ static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr)
n = false; // Trailing garbage.
}
} else if (*p == '*') { // Internal or user defined function.
- n = function_exists(p + 1);
+ n = function_exists(p + 1, false);
} else if (*p == ':') {
n = cmd_exists(p + 1);
} else if (*p == '#') {
@@ -9296,8 +9631,7 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what)
*/
static void filter_map(typval_T *argvars, typval_T *rettv, int map)
{
- char_u buf[NUMBUFLEN];
- char_u *expr;
+ typval_T *expr;
listitem_T *li, *nli;
list_T *l = NULL;
dictitem_T *di;
@@ -9329,16 +9663,15 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)
return;
}
- expr = get_tv_string_buf_chk(&argvars[1], buf);
- /* On type errors, the preceding call has already displayed an error
- * message. Avoid a misleading error message for an empty string that
- * was not passed as argument. */
- if (expr != NULL) {
+ expr = &argvars[1];
+ // On type errors, the preceding call has already displayed an error
+ // message. Avoid a misleading error message for an empty string that
+ // was not passed as argument.
+ if (expr->v_type != VAR_UNKNOWN) {
prepare_vimvar(VV_VAL, &save_val);
- expr = skipwhite(expr);
- /* We reset "did_emsg" to be able to detect whether an error
- * occurred during evaluation of the expression. */
+ // We reset "did_emsg" to be able to detect whether an error
+ // occurred during evaluation of the expression.
save_did_emsg = did_emsg;
did_emsg = FALSE;
@@ -9402,20 +9735,46 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)
copy_tv(&argvars[0], rettv);
}
-static int filter_map_one(typval_T *tv, char_u *expr, int map, int *remp)
+static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp)
{
typval_T rettv;
+ typval_T argv[3];
+ char_u buf[NUMBUFLEN];
char_u *s;
int retval = FAIL;
+ int dummy;
copy_tv(tv, &vimvars[VV_VAL].vv_tv);
- s = expr;
- if (eval1(&s, &rettv, TRUE) == FAIL)
- goto theend;
- if (*s != NUL) { /* check for trailing chars after expr */
- EMSG2(_(e_invexpr2), s);
- clear_tv(&rettv);
- goto theend;
+ argv[0] = vimvars[VV_KEY].vv_tv;
+ argv[1] = vimvars[VV_VAL].vv_tv;
+ if (expr->v_type == VAR_FUNC) {
+ s = expr->vval.v_string;
+ if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, NULL,
+ 0L, 0L, &dummy, true, NULL, NULL) == FAIL) {
+ goto theend;
+ }
+ } else if (expr->v_type == VAR_PARTIAL) {
+ partial_T *partial = expr->vval.v_partial;
+
+ s = partial_name(partial);
+ if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, NULL,
+ 0L, 0L, &dummy, true, partial, NULL) == FAIL) {
+ goto theend;
+ }
+ } else {
+ s = get_tv_string_buf_chk(expr, buf);
+ if (s == NULL) {
+ goto theend;
+ }
+ s = skipwhite(s);
+ if (eval1(&s, &rettv, true) == FAIL) {
+ goto theend;
+ }
+
+ if (*s != NUL) { // check for trailing chars after expr
+ EMSG2(_(e_invexpr2), s);
+ goto theend;
+ }
}
if (map) {
/* map(): replace the list item value */
@@ -9674,15 +10033,14 @@ static void f_foreground(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
}
-/*
- * "function()" function
- */
-static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+static void common_function(typval_T *argvars, typval_T *rettv,
+ bool is_funcref, FunPtr fptr)
{
char_u *s;
char_u *name;
bool use_string = false;
partial_T *arg_pt = NULL;
+ char_u *trans_name = NULL;
if (argvars[0].v_type == VAR_FUNC) {
// function(MyFunc, [arg], dict)
@@ -9691,18 +10049,29 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr)
&& argvars[0].vval.v_partial != NULL) {
// function(dict.MyFunc, [arg])
arg_pt = argvars[0].vval.v_partial;
- s = arg_pt->pt_name;
+ s = partial_name(arg_pt);
} else {
// function('MyFunc', [arg], dict)
s = get_tv_string(&argvars[0]);
use_string = true;
}
- if (s == NULL || *s == NUL || (use_string && ascii_isdigit(*s))) {
- EMSG2(_(e_invarg2), s);
- } else if (use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL
- && !function_exists((const char *)s)) {
+ if ((use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL) || is_funcref) {
+ name = s;
+ trans_name = trans_function_name(&name, false,
+ TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD
+ | TFN_NO_DEREF, NULL, NULL);
+ if (*name != NUL) {
+ s = NULL;
+ }
+ }
+ if (s == NULL || *s == NUL || (use_string && ascii_isdigit(*s))
+ || (is_funcref && trans_name == NULL)) {
+ EMSG2(_(e_invarg2), use_string ? get_tv_string(&argvars[0]) : s);
// Don't check an autoload name for existence here.
+ } else if (trans_name != NULL
+ && (is_funcref ? find_func(trans_name) == NULL
+ : !translated_function_exists((const char *)trans_name))) {
EMSG2(_("E700: Unknown function: %s"), s);
} else {
int dict_idx = 0;
@@ -9743,7 +10112,7 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (argvars[dict_idx].v_type != VAR_DICT) {
EMSG(_("E922: expected a dict"));
xfree(name);
- return;
+ goto theend;
}
if (argvars[dict_idx].vval.v_dict == NULL) {
dict_idx = 0;
@@ -9754,7 +10123,7 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr)
EMSG(_("E923: Second argument of function() must be "
"a list or a dict"));
xfree(name);
- return;
+ goto theend;
}
list = argvars[arg_idx].vval.v_list;
if (list == NULL || list->lv_len == 0) {
@@ -9762,7 +10131,7 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
}
- if (dict_idx > 0 || arg_idx > 0 || arg_pt != NULL) {
+ if (dict_idx > 0 || arg_idx > 0 || arg_pt != NULL || is_funcref) {
partial_T *const pt = xcalloc(1, sizeof(*pt));
// result is a VAR_PARTIAL
@@ -9775,18 +10144,17 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (pt->pt_argv == NULL) {
xfree(pt);
xfree(name);
- return;
- } else {
- int i = 0;
- for (; i < arg_len; i++) {
- copy_tv(&arg_pt->pt_argv[i], &pt->pt_argv[i]);
- }
- if (lv_len > 0) {
- for (listitem_T *li = list->lv_first;
- li != NULL;
- li = li->li_next) {
- copy_tv(&li->li_tv, &pt->pt_argv[i++]);
- }
+ goto theend;
+ }
+ int i = 0;
+ for (; i < arg_len; i++) {
+ copy_tv(&arg_pt->pt_argv[i], &pt->pt_argv[i]);
+ }
+ if (lv_len > 0) {
+ for (listitem_T *li = list->lv_first;
+ li != NULL;
+ li = li->li_next) {
+ copy_tv(&li->li_tv, &pt->pt_argv[i++]);
}
}
}
@@ -9808,8 +10176,18 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
pt->pt_refcount = 1;
- pt->pt_name = name;
- func_ref(pt->pt_name);
+ if (arg_pt != NULL && arg_pt->pt_func != NULL) {
+ pt->pt_func = arg_pt->pt_func;
+ func_ptr_ref(pt->pt_func);
+ xfree(name);
+ } else if (is_funcref) {
+ pt->pt_func = find_func(trans_name);
+ func_ptr_ref(pt->pt_func);
+ xfree(name);
+ } else {
+ pt->pt_name = name;
+ func_ref(name);
+ }
rettv->v_type = VAR_PARTIAL;
rettv->vval.v_partial = pt;
@@ -9820,6 +10198,18 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr)
func_ref(name);
}
}
+theend:
+ xfree(trans_name);
+}
+
+static void f_funcref(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ common_function(argvars, rettv, true, fptr);
+}
+
+static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ common_function(argvars, rettv, false, fptr);
}
/// "garbagecollect()" function
@@ -9873,11 +10263,18 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (pt != NULL) {
char_u *what = get_tv_string(&argvars[1]);
+ char_u *n;
if (STRCMP(what, "func") == 0 || STRCMP(what, "name") == 0) {
rettv->v_type = (*what == 'f' ? VAR_FUNC : VAR_STRING);
- if (pt->pt_name != NULL) {
- rettv->vval.v_string = vim_strsave(pt->pt_name);
+ n = partial_name(pt);
+ if (n == NULL) {
+ rettv->vval.v_string = NULL;
+ } else {
+ rettv->vval.v_string = vim_strsave(n);
+ if (rettv->v_type == VAR_FUNC) {
+ func_ref(rettv->vval.v_string);
+ }
}
} else if (STRCMP(what, "dict") == 0) {
rettv->v_type = VAR_DICT;
@@ -11338,6 +11735,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)
"insert_expand",
"jumplist",
"keymap",
+ "lambda",
"langmap",
"libcall",
"linebreak",
@@ -15923,8 +16321,9 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero)
if (partial == NULL) {
func_name = sortinfo->item_compare_func;
} else {
- func_name = partial->pt_name;
+ func_name = partial_name(partial);
}
+
// Copy the values. This is needed to be able to set v_lock to VAR_FIXED
// in the copy without changing the original list items.
copy_tv(&si1->item->li_tv, &argv[0]);
@@ -15933,7 +16332,7 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero)
rettv.v_type = VAR_UNKNOWN; // clear_tv() uses this
res = call_func(func_name,
(int)STRLEN(func_name),
- &rettv, 2, argv, 0L, 0L, &dummy, true,
+ &rettv, 2, argv, NULL, 0L, 0L, &dummy, true,
partial, sortinfo->item_compare_selfdict);
clear_tv(&argv[0]);
clear_tv(&argv[1]);
@@ -16741,12 +17140,16 @@ static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
+ if (no < 0 || no >= NSUBEXP) {
+ EMSGN(_("E935: invalid submatch number: %d"), no);
+ return;
+ }
int retList = 0;
if (argvars[1].v_type != VAR_UNKNOWN) {
retList = get_tv_number_chk(&argvars[1], &error);
if (error) {
- return;
+ return;
}
}
@@ -16770,19 +17173,26 @@ static void f_substitute(typval_T *argvars, typval_T *rettv, FunPtr fptr)
char_u *str = get_tv_string_chk(&argvars[0]);
char_u *pat = get_tv_string_buf_chk(&argvars[1], patbuf);
- char_u *sub = get_tv_string_buf_chk(&argvars[2], subbuf);
+ char_u *sub = NULL;
+ typval_T *expr = NULL;
char_u *flg = get_tv_string_buf_chk(&argvars[3], flagsbuf);
+ if (argvars[2].v_type == VAR_FUNC || argvars[2].v_type == VAR_PARTIAL) {
+ expr = &argvars[2];
+ } else {
+ sub = get_tv_string_buf_chk(&argvars[2], subbuf);
+ }
+
rettv->v_type = VAR_STRING;
- if (str == NULL || pat == NULL || sub == NULL || flg == NULL)
+ if (str == NULL || pat == NULL || (sub == NULL && expr == NULL)
+ || flg == NULL) {
rettv->vval.v_string = NULL;
- else
- rettv->vval.v_string = do_string_sub(str, pat, sub, flg);
+ } else {
+ rettv->vval.v_string = do_string_sub(str, pat, sub, expr, flg);
+ }
}
-/*
- * "synID(lnum, col, trans)" function
- */
+/// "synID(lnum, col, trans)" function
static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
int id = 0;
@@ -17333,6 +17743,15 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
+// "test_garbagecollect_now()" function
+static void f_test_garbagecollect_now(typval_T *argvars,
+ typval_T *rettv, FunPtr fptr)
+{
+ // This is dangerous, any Lists and Dicts used internally may be freed
+ // while still in use.
+ garbage_collect(true);
+}
+
static bool callback_from_typval(Callback *callback, typval_T *arg)
{
if (arg->v_type == VAR_PARTIAL && arg->vval.v_partial != NULL) {
@@ -17411,7 +17830,7 @@ static bool callback_call(Callback *callback, int argcount_in,
case kCallbackPartial:
partial = callback->data.partial;
- name = partial->pt_name;
+ name = partial_name(partial);
break;
case kCallbackNone:
@@ -17424,7 +17843,7 @@ static bool callback_call(Callback *callback, int argcount_in,
int dummy;
return call_func(name, (int)STRLEN(name), rettv, argcount_in, argvars_in,
- curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy,
+ NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy,
true, partial, NULL);
}
@@ -18073,6 +18492,34 @@ static bool write_list(FILE *fd, list_T *list, bool binary)
return ret;
}
+/// Initializes a static list with 10 items.
+void init_static_list(staticList10_T *sl)
+{
+ list_T *l = &sl->sl_list;
+
+ memset(sl, 0, sizeof(staticList10_T));
+ l->lv_first = &sl->sl_items[0];
+ l->lv_last = &sl->sl_items[9];
+ l->lv_refcount = DO_NOT_FREE_CNT;
+ l->lv_lock = VAR_FIXED;
+ sl->sl_list.lv_len = 10;
+
+ for (int i = 0; i < 10; i++) {
+ listitem_T *li = &sl->sl_items[i];
+
+ if (i == 0) {
+ li->li_prev = NULL;
+ } else {
+ li->li_prev = li - 1;
+ }
+ if (i == 9) {
+ li->li_next = NULL;
+ } else {
+ li->li_next = li + 1;
+ }
+ }
+}
+
/// Saves a typval_T as a string.
///
/// For lists, replaces NLs with NUL and separates items with NLs.
@@ -18920,11 +19367,27 @@ static int get_var_tv(
return ret;
}
-/*
- * Handle expr[expr], expr[expr:expr] subscript and .name lookup.
- * Also handle function call with Funcref variable: func(expr)
- * Can all be combined: dict.func(expr)[idx]['func'](expr)
- */
+/// Check if variable "name[len]" is a local variable or an argument.
+/// If so, "*eval_lavars_used" is set to TRUE.
+static void check_vars(const char *name, size_t len)
+{
+ if (eval_lavars_used == NULL) {
+ return;
+ }
+
+ const char *varname;
+ hashtab_T *ht = find_var_ht(name, len, &varname);
+
+ if (ht == get_funccal_local_ht() || ht == get_funccal_args_ht()) {
+ if (find_var(name, len, NULL, true) != NULL) {
+ *eval_lavars_used = true;
+ }
+ }
+}
+
+/// Handle expr[expr], expr[expr:expr] subscript and .name lookup.
+/// Also handle function call with Funcref variable: func(expr)
+/// Can all be combined: dict.func(expr)[idx]['func'](expr)
static int
handle_subscript(
const char **const arg,
@@ -18955,7 +19418,7 @@ handle_subscript(
// Invoke the function. Recursive!
if (functv.v_type == VAR_PARTIAL) {
pt = functv.vval.v_partial;
- s = pt->pt_name;
+ s = partial_name(pt);
} else {
s = functv.vval.v_string;
}
@@ -19015,19 +19478,23 @@ static void set_selfdict(typval_T *rettv, dict_T *selfdict)
&& rettv->vval.v_partial->pt_dict != NULL) {
return;
}
- char_u *fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING
- ? rettv->vval.v_string
- : rettv->vval.v_partial->pt_name;
+ char_u *fname;
char_u *tofree = NULL;
ufunc_T *fp;
char_u fname_buf[FLEN_FIXED + 1];
int error;
- // Translate "s:func" to the stored function name.
- fname = fname_trans_sid(fname, fname_buf, &tofree, &error);
-
- fp = find_func(fname);
- xfree(tofree);
+ if (rettv->v_type == VAR_PARTIAL && rettv->vval.v_partial->pt_func != NULL) {
+ fp = rettv->vval.v_partial->pt_func;
+ } else {
+ fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING
+ ? rettv->vval.v_string
+ : rettv->vval.v_partial->pt_name;
+ // Translate "s:func" to the stored function name.
+ fname = fname_trans_sid(fname, fname_buf, &tofree, &error);
+ fp = find_func(fname);
+ xfree(tofree);
+ }
// Turn "dict.Func" into a partial for "Func" with "dict".
if (fp != NULL && (fp->uf_flags & FC_DICT)) {
@@ -19048,8 +19515,13 @@ static void set_selfdict(typval_T *rettv, dict_T *selfdict)
// Partial: copy the function name, use selfdict and copy
// args. Can't take over name or args, the partial might
// be referenced elsewhere.
- pt->pt_name = vim_strsave(ret_pt->pt_name);
- func_ref(pt->pt_name);
+ if (ret_pt->pt_name != NULL) {
+ pt->pt_name = vim_strsave(ret_pt->pt_name);
+ func_ref(pt->pt_name);
+ } else {
+ pt->pt_func = ret_pt->pt_func;
+ func_ptr_ref(pt->pt_func);
+ }
if (ret_pt->pt_argc > 0) {
size_t arg_size = sizeof(typval_T) * ret_pt->pt_argc;
pt->pt_argv = (typval_T *)xmalloc(arg_size);
@@ -19563,7 +20035,7 @@ char_u *get_tv_string_chk(const typval_T *varp)
return get_tv_string_buf_chk(varp, mybuf);
}
-static char_u *get_tv_string_buf_chk(const typval_T *varp, char_u *buf)
+char_u *get_tv_string_buf_chk(const typval_T *varp, char_u *buf)
FUNC_ATTR_NONNULL_ALL
{
switch (varp->v_type) {
@@ -19615,9 +20087,15 @@ static dictitem_T *find_var(const char *const name, const size_t name_len,
if (ht == NULL) {
return NULL;
}
- return find_var_in_ht(ht, *name,
- varname, name_len - (size_t)(varname - name),
- no_autoload || htp != NULL);
+ dictitem_T *ret = find_var_in_ht(ht, *name,
+ varname, name_len - (size_t)(varname - name),
+ no_autoload || htp != NULL);
+ if (ret != NULL) {
+ return ret;
+ }
+
+ // Search in parent scope for lambda
+ return find_var_in_scoped_ht(name, name_len, no_autoload || htp != NULL);
}
/// Find variable in hashtab
@@ -19697,6 +20175,26 @@ static funccall_T *get_funccal(void)
return funccal;
}
+/// Return the hashtable used for argument in the current funccal.
+/// Return NULL if there is no current funccal.
+static hashtab_T *get_funccal_args_ht(void)
+{
+ if (current_funccal == NULL) {
+ return NULL;
+ }
+ return &get_funccal()->l_avars.dv_hashtab;
+}
+
+/// Return the hashtable used for local variables in the current funccal.
+/// Return NULL if there is no current funccal.
+static hashtab_T *get_funccal_local_ht(void)
+{
+ if (current_funccal == NULL) {
+ return NULL;
+ }
+ return &get_funccal()->l_vars.dv_hashtab;
+}
+
/// Find the dict and hashtable used for a variable
///
/// @param[in] name Variable name, possibly with scope prefix.
@@ -20000,6 +20498,11 @@ set_var (
(const char *)varname, name_len - (size_t)(varname - name),
true);
+ // Search in parent scope which is possible to reference from lambda
+ if (v == NULL) {
+ v = find_var_in_scoped_ht((const char *)name, name_len, true);
+ }
+
if ((tv->v_type == VAR_FUNC || tv->v_type == VAR_PARTIAL)
&& var_check_func_name(name, v == NULL)) {
return;
@@ -20128,7 +20631,7 @@ var_check_func_name (
// Don't allow hiding a function. When "v" is not NULL we might be
// assigning another function to the same var, the type is checked
// below.
- if (new_var && function_exists((const char *)name)) {
+ if (new_var && function_exists((const char *)name, false)) {
EMSG2(_("E705: Variable name conflicts with existing function: %s"),
name);
return true;
@@ -20542,10 +21045,10 @@ void ex_function(exarg_T *eap)
char_u *line_arg = NULL;
garray_T newargs;
garray_T newlines;
- int varargs = FALSE;
- int mustend = FALSE;
+ int varargs = false;
int flags = 0;
ufunc_T *fp;
+ bool overwrite = false;
int indent;
int nesting;
char_u *skip_until = NULL;
@@ -20568,8 +21071,9 @@ void ex_function(exarg_T *eap)
if (!HASHITEM_EMPTY(hi)) {
--todo;
fp = HI2UF(hi);
- if (!isdigit(*fp->uf_name))
- list_func_head(fp, FALSE);
+ if (!func_name_refcount(fp->uf_name)) {
+ list_func_head(fp, false);
+ }
}
}
}
@@ -20726,59 +21230,11 @@ void ex_function(exarg_T *eap)
EMSG(_("E862: Cannot use g: here"));
}
- /*
- * Isolate the arguments: "arg1, arg2, ...)"
- */
- while (*p != ')') {
- if (p[0] == '.' && p[1] == '.' && p[2] == '.') {
- varargs = TRUE;
- p += 3;
- mustend = TRUE;
- } else {
- arg = p;
- while (ASCII_ISALNUM(*p) || *p == '_')
- ++p;
- if (arg == p || isdigit(*arg)
- || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0)
- || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) {
- if (!eap->skip)
- EMSG2(_("E125: Illegal argument: %s"), arg);
- break;
- }
- ga_grow(&newargs, 1);
- c = *p;
- *p = NUL;
- arg = vim_strsave(arg);
-
- /* Check for duplicate argument name. */
- for (int i = 0; i < newargs.ga_len; ++i)
- if (STRCMP(((char_u **)(newargs.ga_data))[i], arg) == 0) {
- EMSG2(_("E853: Duplicate argument name: %s"), arg);
- xfree(arg);
- goto erret;
- }
-
- ((char_u **)(newargs.ga_data))[newargs.ga_len] = arg;
- *p = c;
- newargs.ga_len++;
- if (*p == ',')
- ++p;
- else
- mustend = TRUE;
- }
- p = skipwhite(p);
- if (mustend && *p != ')') {
- if (!eap->skip)
- EMSG2(_(e_invarg2), eap->arg);
- break;
- }
- }
- if (*p != ')') {
- goto erret;
+ if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL) {
+ goto errret_2;
}
- ++p; // skip the ')'
- /* find extra arguments "range", "dict" and "abort" */
+ // find extra arguments "range", "dict", "abort" and "closure"
for (;; ) {
p = skipwhite(p);
if (STRNCMP(p, "range", 5) == 0) {
@@ -20790,8 +21246,18 @@ void ex_function(exarg_T *eap)
} else if (STRNCMP(p, "abort", 5) == 0) {
flags |= FC_ABORT;
p += 5;
- } else
+ } else if (STRNCMP(p, "closure", 7) == 0) {
+ flags |= FC_CLOSURE;
+ p += 7;
+ if (current_funccal == NULL) {
+ emsg_funcname(N_
+ ("E932 Closure function should not be at top level: %s"),
+ name == NULL ? (char_u *)"" : name);
+ goto erret;
+ }
+ } else {
break;
+ }
}
/* When there is a line break use what follows for the function body.
@@ -20981,16 +21447,25 @@ void ex_function(exarg_T *eap)
emsg_funcname(e_funcexts, name);
goto erret;
}
- if (fp->uf_refcount > 1 || fp->uf_calls > 0) {
+ if (fp->uf_calls > 0) {
emsg_funcname(N_("E127: Cannot redefine function %s: It is in use"),
name);
goto erret;
}
- /* redefine existing function */
- ga_clear_strings(&(fp->uf_args));
- ga_clear_strings(&(fp->uf_lines));
- xfree(name);
- name = NULL;
+ if (fp->uf_refcount > 1) {
+ // This function is referenced somewhere, don't redefine it but
+ // create a new one.
+ (fp->uf_refcount)--;
+ fp->uf_flags |= FC_REMOVED;
+ fp = NULL;
+ overwrite = true;
+ } else {
+ // redefine existing function
+ ga_clear_strings(&(fp->uf_args));
+ ga_clear_strings(&(fp->uf_lines));
+ xfree(name);
+ name = NULL;
+ }
}
} else {
char numbuf[20];
@@ -21042,7 +21517,7 @@ void ex_function(exarg_T *eap)
}
}
- fp = xmalloc(sizeof(ufunc_T) + STRLEN(name));
+ fp = xcalloc(1, sizeof(ufunc_T) + STRLEN(name));
if (fudi.fd_dict != NULL) {
if (fudi.fd_di == NULL) {
@@ -21066,14 +21541,22 @@ void ex_function(exarg_T *eap)
/* insert the new function in the function list */
STRCPY(fp->uf_name, name);
- if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) {
- xfree(fp);
- goto erret;
+ if (overwrite) {
+ hi = hash_find(&func_hashtab, name);
+ hi->hi_key = UF2HIKEY(fp);
+ } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) {
+ xfree(fp);
+ goto erret;
}
+ fp->uf_refcount = 1;
}
- fp->uf_refcount = 1;
fp->uf_args = newargs;
fp->uf_lines = newlines;
+ if ((flags & FC_CLOSURE) != 0) {
+ register_closure(fp);
+ } else {
+ fp->uf_scoped = NULL;
+ }
fp->uf_tml_count = NULL;
fp->uf_tml_total = NULL;
fp->uf_tml_self = NULL;
@@ -21088,6 +21571,7 @@ void ex_function(exarg_T *eap)
erret:
ga_clear_strings(&newargs);
+errret_2:
ga_clear_strings(&newlines);
ret_free:
xfree(skip_until);
@@ -21097,18 +21581,18 @@ ret_free:
need_wait_return |= saved_wait_return;
}
-/*
- * Get a function name, translating "<SID>" and "<SNR>".
- * Also handles a Funcref in a List or Dictionary.
- * Returns the function name in allocated memory, or NULL for failure.
- * flags:
- * TFN_INT: internal function name OK
- * TFN_QUIET: be quiet
- * TFN_NO_AUTOLOAD: do not use script autoloading
- * Advances "pp" to just after the function name (if no error).
- */
-static char_u *
-trans_function_name (
+/// Get a function name, translating "<SID>" and "<SNR>".
+/// Also handles a Funcref in a List or Dictionary.
+/// flags:
+/// TFN_INT: internal function name OK
+/// TFN_QUIET: be quiet
+/// TFN_NO_AUTOLOAD: do not use script autoloading
+/// TFN_NO_DEREF: do not dereference a Funcref
+/// Advances "pp" to just after the function name (if no error).
+///
+/// @return the function name in allocated memory, or NULL for failure.
+char_u *
+trans_function_name(
char_u **pp,
int skip, /* only find the end, don't evaluate */
int flags,
@@ -21178,7 +21662,7 @@ trans_function_name (
*pp = end;
} else if (lv.ll_tv->v_type == VAR_PARTIAL
&& lv.ll_tv->vval.v_partial != NULL) {
- name = vim_strsave(lv.ll_tv->vval.v_partial->pt_name);
+ name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial));
*pp = end;
if (partial != NULL) {
*partial = lv.ll_tv->vval.v_partial;
@@ -21210,7 +21694,7 @@ trans_function_name (
if (name == lv.ll_exp_name) {
name = NULL;
}
- } else {
+ } else if (!(flags & TFN_NO_DEREF)) {
len = (int)(end - *pp);
name = deref_func_name((const char *)(*pp), &len, partial,
flags & TFN_NO_AUTOLOAD);
@@ -21275,7 +21759,7 @@ trans_function_name (
goto theend;
}
- if (!skip && !(flags & TFN_QUIET)) {
+ if (!skip && !(flags & TFN_QUIET) && !(flags & TFN_NO_DEREF)) {
char_u *cp = vim_strchr(lv.ll_name, ':');
if (cp != NULL && cp < end) {
@@ -21375,15 +21859,16 @@ static void list_func_head(ufunc_T *fp, int indent)
if (fp->uf_flags & FC_DICT) {
msg_puts(" dict");
}
+ if (fp->uf_flags & FC_CLOSURE) {
+ msg_puts(" closure");
+ }
msg_clr_eos();
if (p_verbose > 0)
last_set_msg(fp->uf_script_ID);
}
-/*
- * Find a function by name, return pointer to it in ufuncs.
- * Return NULL for unknown function.
- */
+/// Find a function by name, return pointer to it in ufuncs.
+/// @return NULL for unknown function.
static ufunc_T *find_func(const char_u *name)
{
hashitem_T *hi;
@@ -21398,15 +21883,60 @@ static ufunc_T *find_func(const char_u *name)
void free_all_functions(void)
{
hashitem_T *hi;
+ ufunc_T *fp;
+ uint64_t skipped = 0;
+ uint64_t todo = 1;
+ uint64_t used;
+
+ // First clear what the functions contain. Since this may lower the
+ // reference count of a function, it may also free a function and change
+ // the hash table. Restart if that happens.
+ while (todo > 0) {
+ todo = func_hashtab.ht_used;
+ for (hi = func_hashtab.ht_array; todo > 0; hi++) {
+ if (!HASHITEM_EMPTY(hi)) {
+ // Only free functions that are not refcounted, those are
+ // supposed to be freed when no longer referenced.
+ fp = HI2UF(hi);
+ if (func_name_refcount(fp->uf_name)) {
+ skipped++;
+ } else {
+ used = func_hashtab.ht_used;
+ func_clear(fp, true);
+ if (used != func_hashtab.ht_used) {
+ skipped = 0;
+ break;
+ }
+ }
+ todo--;
+ }
+ }
+ }
- /* Need to start all over every time, because func_free() may change the
- * hash table. */
- while (func_hashtab.ht_used > 0)
- for (hi = func_hashtab.ht_array;; ++hi)
+ // Now actually free the functions. Need to start all over every time,
+ // because func_free() may change the hash table.
+ skipped = 0;
+ while (func_hashtab.ht_used > skipped) {
+ todo = func_hashtab.ht_used;
+ for (hi = func_hashtab.ht_array; todo > 0; hi++) {
if (!HASHITEM_EMPTY(hi)) {
- func_free(HI2UF(hi));
- break;
+ todo--;
+ // Only free functions that are not refcounted, those are
+ // supposed to be freed when no longer referenced.
+ fp = HI2UF(hi);
+ if (func_name_refcount(fp->uf_name)) {
+ skipped++;
+ } else {
+ func_free(fp);
+ skipped = 0;
+ break;
+ }
}
+ }
+ }
+ if (skipped == 0) {
+ hash_clear(&func_hashtab);
+ }
}
#endif
@@ -21422,16 +21952,19 @@ bool translated_function_exists(const char *name)
/// Check whether function with the given name exists
///
/// @param[in] name Function name.
+/// @param[in] no_deref Whether to dereference a Funcref.
///
/// @return True if it exists, false otherwise.
-static bool function_exists(const char *const name)
+static bool function_exists(const char *const name, bool no_deref)
{
char_u *nm = (char_u *)name;
bool n = false;
+ int flag = TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD;
- char *const p = (char *)trans_function_name(&nm, false,
- TFN_INT|TFN_QUIET|TFN_NO_AUTOLOAD,
- NULL, NULL);
+ if (no_deref) {
+ flag |= TFN_NO_DEREF;
+ }
+ char *const p = (char *)trans_function_name(&nm, false, flag, NULL, NULL);
nm = skipwhite(nm);
/* Only accept "funcname", "funcname ", "funcname (..." and
@@ -21443,9 +21976,13 @@ static bool function_exists(const char *const name)
return n;
}
-/// Return TRUE if "name" looks like a builtin function name: starts with a
+/// Checks if a builtin function with the given name exists.
+///
+/// @param[in] name name of the builtin function to check.
+/// @param[in] len length of "name", or -1 for NUL terminated.
+///
+/// @return true if "name" looks like a builtin function name: starts with a
/// lower case letter and doesn't contain AUTOLOAD_CHAR.
-/// "len" is the length of "name", or -1 for NUL terminated.
static bool builtin_function(const char *name, int len)
{
if (!ASCII_ISLOWER(name[0])) {
@@ -21719,8 +22256,10 @@ char_u *get_user_func_name(expand_T *xp, int idx)
++hi;
fp = HI2UF(hi);
- if (fp->uf_flags & FC_DICT)
- return (char_u *)""; /* don't show dict functions */
+ if ((fp->uf_flags & FC_DICT)
+ || STRNCMP(fp->uf_name, "<lambda>", 8) == 0) {
+ return (char_u *)""; // don't show dict and lambda functions
+ }
if (STRLEN(fp->uf_name) + 4 >= IOSIZE)
return fp->uf_name; /* prevents overflow */
@@ -21751,9 +22290,18 @@ static void cat_func_name(char_u *buf, ufunc_T *fp)
STRCPY(buf, fp->uf_name);
}
-/*
- * ":delfunction {name}"
- */
+/// There are two kinds of function names:
+/// 1. ordinary names, function defined with :function
+/// 2. numbered functions and lambdas
+/// For the first we only count the name stored in func_hashtab as a reference,
+/// using function() does not count as a reference, because the function is
+/// looked up by name.
+static bool func_name_refcount(char_u *name)
+{
+ return isdigit(*name) || *name == '<';
+}
+
+/// ":delfunction {name}"
void ex_delfunction(exarg_T *eap)
{
ufunc_T *fp = NULL;
@@ -21803,98 +22351,167 @@ void ex_delfunction(exarg_T *eap)
/* Delete the dict item that refers to the function, it will
* invoke func_unref() and possibly delete the function. */
dictitem_remove(fudi.fd_dict, fudi.fd_di);
- } else
- func_free(fp);
+ } else {
+ // A normal function (not a numbered function or lambda) has a
+ // refcount of 1 for the entry in the hashtable. When deleting
+ // it and the refcount is more than one, it should be kept.
+ // A numbered function or lambda should be kept if the refcount is
+ // one or more.
+ if (fp->uf_refcount > (func_name_refcount(fp->uf_name) ? 0 : 1)) {
+ // Function is still referenced somewhere. Don't free it but
+ // do remove it from the hashtable.
+ if (func_remove(fp)) {
+ fp->uf_refcount--;
+ }
+ fp->uf_flags |= FC_DELETED;
+ } else {
+ func_clear_free(fp, false);
+ }
+ }
}
}
-/*
- * Free a function and remove it from the list of functions.
- */
-static void func_free(ufunc_T *fp)
+/// Remove the function from the function hashtable. If the function was
+/// deleted while it still has references this was already done.
+///
+/// @return true if the entry was deleted, false if it wasn't found.
+static bool func_remove(ufunc_T *fp)
{
- hashitem_T *hi;
+ hashitem_T *hi = hash_find(&func_hashtab, UF2HIKEY(fp));
+
+ if (!HASHITEM_EMPTY(hi)) {
+ hash_remove(&func_hashtab, hi);
+ return true;
+ }
+
+ return false;
+}
- /* clear this function */
+/// Free all things that a function contains. Does not free the function
+/// itself, use func_free() for that.
+///
+/// param[in] force When true, we are exiting.
+static void func_clear(ufunc_T *fp, bool force)
+{
+ if (fp->uf_cleared) {
+ return;
+ }
+ fp->uf_cleared = true;
+
+ // clear this function
ga_clear_strings(&(fp->uf_args));
ga_clear_strings(&(fp->uf_lines));
xfree(fp->uf_tml_count);
xfree(fp->uf_tml_total);
xfree(fp->uf_tml_self);
+ funccal_unref(fp->uf_scoped, fp, force);
+}
- /* remove the function from the function hashtable */
- hi = hash_find(&func_hashtab, UF2HIKEY(fp));
- if (HASHITEM_EMPTY(hi))
- EMSG2(_(e_intern2), "func_free()");
- else
- hash_remove(&func_hashtab, hi);
-
+/// Free a function and remove it from the list of functions. Does not free
+/// what a function contains, call func_clear() first.
+///
+/// param[in] fp The function to free.
+static void func_free(ufunc_T *fp)
+{
+ // only remove it when not done already, otherwise we would remove a newer
+ // version of the function
+ if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0) {
+ func_remove(fp);
+ }
xfree(fp);
}
+/// Free all things that a function contains and free the function itself.
+///
+/// param[in] force When true, we are exiting.
+static void func_clear_free(ufunc_T *fp, bool force)
+{
+ func_clear(fp, force);
+ func_free(fp);
+}
+
/*
* Unreference a Function: decrement the reference count and free it when it
- * becomes zero. Only for numbered functions.
+ * becomes zero.
*/
void func_unref(char_u *name)
{
- ufunc_T *fp;
+ ufunc_T *fp = NULL;
- if (name != NULL && isdigit(*name)) {
- fp = find_func(name);
- if (fp == NULL) {
+ if (name == NULL || !func_name_refcount(name)) {
+ return;
+ }
+
+ fp = find_func(name);
+ if (fp == NULL && isdigit(*name)) {
#ifdef EXITFREE
- if (!entered_free_all_mem) {
- EMSG2(_(e_intern2), "func_unref()");
- }
+ if (!entered_free_all_mem) {
+ EMSG2(_(e_intern2), "func_unref()");
+ abort();
+ }
#else
EMSG2(_(e_intern2), "func_unref()");
+ abort();
#endif
- } else {
- user_func_unref(fp);
+ }
+ if (fp != NULL && --fp->uf_refcount <= 0) {
+ // Only delete it when it's not being used. Otherwise it's done
+ // when "uf_calls" becomes zero.
+ if (fp->uf_calls == 0) {
+ func_clear_free(fp, false);
}
}
}
-static void user_func_unref(ufunc_T *fp)
+/// Unreference a Function: decrement the reference count and free it when it
+/// becomes zero.
+void func_ptr_unref(ufunc_T *fp)
{
- if (--fp->uf_refcount <= 0) {
- // Only delete it when it's not being used. Otherwise it's done
+ if (fp != NULL && --fp->uf_refcount <= 0) {
+ // Only delete it when it's not being used. Otherwise it's done
// when "uf_calls" becomes zero.
if (fp->uf_calls == 0) {
- func_free(fp);
+ func_clear_free(fp, false);
}
}
}
-/*
- * Count a reference to a Function.
- */
+/// Count a reference to a Function.
void func_ref(char_u *name)
{
ufunc_T *fp;
- if (name != NULL && isdigit(*name)) {
- fp = find_func(name);
- if (fp == NULL)
- EMSG2(_(e_intern2), "func_ref()");
- else
- ++fp->uf_refcount;
+ if (name == NULL || !func_name_refcount(name)) {
+ return;
+ }
+ fp = find_func(name);
+ if (fp != NULL) {
+ (fp->uf_refcount)++;
+ } else if (isdigit(*name)) {
+ // Only give an error for a numbered function.
+ // Fail silently, when named or lambda function isn't found.
+ EMSG2(_(e_intern2), "func_ref()");
}
}
-/*
- * Call a user function.
- */
-static void
-call_user_func (
- ufunc_T *fp, /* pointer to function */
- int argcount, /* nr of args */
- typval_T *argvars, /* arguments */
- typval_T *rettv, /* return value */
- linenr_T firstline, /* first line of range */
- linenr_T lastline, /* last line of range */
- dict_T *selfdict /* Dictionary for "self" */
+/// Count a reference to a Function.
+void func_ptr_ref(ufunc_T *fp)
+{
+ if (fp != NULL) {
+ (fp->uf_refcount)++;
+ }
+}
+
+/// Call a user function.
+static void
+call_user_func(
+ ufunc_T *fp, // pointer to function
+ int argcount, // nr of args
+ typval_T *argvars, // arguments
+ typval_T *rettv, // return value
+ linenr_T firstline, // first line of range
+ linenr_T lastline, // last line of range
+ dict_T *selfdict // Dictionary for "self"
)
{
char_u *save_sourcing_name;
@@ -21906,6 +22523,7 @@ call_user_func (
dictitem_T *v;
int fixvar_idx = 0; /* index in fixvar[] */
int ai;
+ bool islambda = false;
char_u numbuf[NUMBUFLEN];
char_u *name;
proftime_T wait_start;
@@ -21943,14 +22561,21 @@ call_user_func (
fc->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0);
fc->dbg_tick = debug_tick;
- /*
- * Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables
- * with names up to VAR_SHORT_LEN long. This avoids having to alloc/free
- * each argument variable and saves a lot of time.
- */
- /*
- * Init l: variables.
- */
+ // Set up fields for closure.
+ fc->fc_refcount = 0;
+ fc->fc_copyID = 0;
+ ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1);
+ func_ptr_ref(fp);
+
+ if (STRNCMP(fp->uf_name, "<lambda>", 8) == 0) {
+ islambda = true;
+ }
+
+ // Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables
+ // with names up to VAR_SHORT_LEN long. This avoids having to alloc/free
+ // each argument variable and saves a lot of time.
+ //
+ // Init l: variables.
init_var_dict(&fc->l_vars, &fc->l_vars_var, VAR_DEF_SCOPE);
if (selfdict != NULL) {
/* Set l:self to "selfdict". Use "name" to avoid a warning from
@@ -21992,23 +22617,26 @@ call_user_func (
fc->l_varlist.lv_refcount = DO_NOT_FREE_CNT;
fc->l_varlist.lv_lock = VAR_FIXED;
- /*
- * Set a:firstline to "firstline" and a:lastline to "lastline".
- * Set a:name to named arguments.
- * Set a:N to the "..." arguments.
- */
+ // Set a:firstline to "firstline" and a:lastline to "lastline".
+ // Set a:name to named arguments.
+ // Set a:N to the "..." arguments.
add_nr_var(&fc->l_avars, &fc->fixvar[fixvar_idx++].var, "firstline",
- (varnumber_T)firstline);
+ (varnumber_T)firstline);
add_nr_var(&fc->l_avars, &fc->fixvar[fixvar_idx++].var, "lastline",
- (varnumber_T)lastline);
- for (int i = 0; i < argcount; ++i) {
+ (varnumber_T)lastline);
+ for (int i = 0; i < argcount; i++) {
+ bool addlocal = false;
+
ai = i - fp->uf_args.ga_len;
- if (ai < 0)
- /* named argument a:name */
+ if (ai < 0) {
+ // named argument a:name
name = FUNCARG(fp, i);
- else {
- /* "..." argument a:1, a:2, etc. */
- sprintf((char *)numbuf, "%d", ai + 1);
+ if (islambda) {
+ addlocal = true;
+ }
+ } else {
+ // "..." argument a:1, a:2, etc.
+ snprintf((char *)numbuf, sizeof(numbuf), "%d", ai + 1);
name = numbuf;
}
if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) {
@@ -22019,13 +22647,21 @@ call_user_func (
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC;
}
STRCPY(v->di_key, name);
- hash_add(&fc->l_avars.dv_hashtab, DI2HIKEY(v));
- /* Note: the values are copied directly to avoid alloc/free.
- * "argvars" must have VAR_FIXED for v_lock. */
+ // Note: the values are copied directly to avoid alloc/free.
+ // "argvars" must have VAR_FIXED for v_lock.
v->di_tv = argvars[i];
v->di_tv.v_lock = VAR_FIXED;
+ if (addlocal) {
+ // Named arguments can be accessed without the "a:" prefix in lambda
+ // expressions. Add to the l: dict.
+ copy_tv(&v->di_tv, &v->di_tv);
+ hash_add(&fc->l_vars.dv_hashtab, DI2HIKEY(v));
+ } else {
+ hash_add(&fc->l_avars.dv_hashtab, DI2HIKEY(v));
+ }
+
if (ai >= 0 && ai < MAX_FUNC_ARGS) {
list_append(&fc->l_varlist, &fc->l_listitems[ai]);
fc->l_listitems[ai].li_tv = argvars[i];
@@ -22211,20 +22847,21 @@ call_user_func (
current_funccal = fc->caller;
--depth;
- /* If the a:000 list and the l: and a: dicts are not referenced we can
- * free the funccall_T and what's in it. */
+ // If the a:000 list and the l: and a: dicts are not referenced and there
+ // is no closure using it, we can free the funccall_T and what's in it.
if (fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT
&& fc->l_vars.dv_refcount == DO_NOT_FREE_CNT
- && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT) {
- free_funccal(fc, FALSE);
+ && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT
+ && fc->fc_refcount <= 0) {
+ free_funccal(fc, false);
} else {
hashitem_T *hi;
listitem_T *li;
int todo;
- /* "fc" is still in use. This can happen when returning "a:000" or
- * assigning "l:" to a global variable.
- * Link "fc" in the list for garbage collection later. */
+ // "fc" is still in use. This can happen when returning "a:000",
+ // assigning "l:" to a global variable or defining a closure.
+ // Link "fc" in the list for garbage collection later.
fc->caller = previous_funccal;
previous_funccal = fc;
@@ -22243,9 +22880,9 @@ call_user_func (
copy_tv(&li->li_tv, &li->li_tv);
}
- if (--fp->uf_calls <= 0 && isdigit(*fp->uf_name) && fp->uf_refcount <= 0) {
+ if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) {
// Function was unreferenced while being used, free it now.
- func_free(fp);
+ func_clear_free(fp, false);
}
// restore search patterns and redo buffer
if (did_save_redo) {
@@ -22254,15 +22891,46 @@ call_user_func (
restore_search_patterns();
}
-/*
- * Return TRUE if items in "fc" do not have "copyID". That means they are not
- * referenced from anywhere that is in use.
- */
+/// Unreference "fc": decrement the reference count and free it when it
+/// becomes zero. "fp" is detached from "fc".
+///
+/// @param[in] force When true, we are exiting.
+static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force)
+{
+ funccall_T **pfc;
+ int i;
+
+ if (fc == NULL) {
+ return;
+ }
+
+ if (--fc->fc_refcount <= 0 && (force || (
+ fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT
+ && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT
+ && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT))) {
+ for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) {
+ if (fc == *pfc) {
+ *pfc = fc->caller;
+ free_funccal(fc, true);
+ return;
+ }
+ }
+ }
+ for (i = 0; i < fc->fc_funcs.ga_len; i++) {
+ if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) {
+ ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL;
+ }
+ }
+}
+
+/// @return true if items in "fc" do not have "copyID". That means they are not
+/// referenced from anywhere that is in use.
static int can_free_funccal(funccall_T *fc, int copyID)
{
return fc->l_varlist.lv_copyID != copyID
&& fc->l_vars.dv_copyID != copyID
- && fc->l_avars.dv_copyID != copyID;
+ && fc->l_avars.dv_copyID != copyID
+ && fc->fc_copyID != copyID;
}
/*
@@ -22276,18 +22944,34 @@ free_funccal (
{
listitem_T *li;
- /* The a: variables typevals may not have been allocated, only free the
- * allocated variables. */
+ for (int i = 0; i < fc->fc_funcs.ga_len; i++) {
+ ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i];
+
+ // When garbage collecting a funccall_T may be freed before the
+ // function that references it, clear its uf_scoped field.
+ // The function may have been redefined and point to another
+ // funccal_T, don't clear it then.
+ if (fp != NULL && fp->uf_scoped == fc) {
+ fp->uf_scoped = NULL;
+ }
+ }
+ ga_clear(&fc->fc_funcs);
+
+ // The a: variables typevals may not have been allocated, only free the
+ // allocated variables.
vars_clear_ext(&fc->l_avars.dv_hashtab, free_val);
/* free all l: variables */
vars_clear(&fc->l_vars.dv_hashtab);
- /* Free the a:000 variables if they were allocated. */
- if (free_val)
- for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next)
+ // Free the a:000 variables if they were allocated.
+ if (free_val) {
+ for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next) {
clear_tv(&li->li_tv);
+ }
+ }
+ func_ptr_unref(fc->func);
xfree(fc);
}
@@ -22606,6 +23290,72 @@ static var_flavour_T var_flavour(char_u *varname)
}
}
+/// Search hashitem in parent scope.
+hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht)
+{
+ if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) {
+ return NULL;
+ }
+
+ funccall_T *old_current_funccal = current_funccal;
+ hashitem_T *hi = NULL;
+ const size_t namelen = strlen(name);
+ const char *varname;
+
+ // Search in parent scope which is possible to reference from lambda
+ current_funccal = current_funccal->func->uf_scoped;
+ while (current_funccal != NULL) {
+ hashtab_T *ht = find_var_ht(name, namelen, &varname);
+ if (ht != NULL && *varname != NUL) {
+ hi = hash_find_len(ht, varname, namelen - (varname - name));
+ if (!HASHITEM_EMPTY(hi)) {
+ *pht = ht;
+ break;
+ }
+ }
+ if (current_funccal == current_funccal->func->uf_scoped) {
+ break;
+ }
+ current_funccal = current_funccal->func->uf_scoped;
+ }
+ current_funccal = old_current_funccal;
+
+ return hi;
+}
+
+/// Search variable in parent scope.
+dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen,
+ int no_autoload)
+{
+ if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) {
+ return NULL;
+ }
+
+ dictitem_T *v = NULL;
+ funccall_T *old_current_funccal = current_funccal;
+ const char *varname;
+
+ // Search in parent scope which is possible to reference from lambda
+ current_funccal = current_funccal->func->uf_scoped;
+ while (current_funccal) {
+ hashtab_T *ht = find_var_ht(name, namelen, &varname);
+ if (ht != NULL && *varname != NUL) {
+ v = find_var_in_ht(ht, *name, varname,
+ namelen - (size_t)(varname - name), no_autoload);
+ if (v != NULL) {
+ break;
+ }
+ }
+ if (current_funccal == current_funccal->func->uf_scoped) {
+ break;
+ }
+ current_funccal = current_funccal->func->uf_scoped;
+ }
+ current_funccal = old_current_funccal;
+
+ return v;
+}
+
/// Iterate over global variables
///
/// @warning No modifications to global variable dictionary must be performed
@@ -23005,7 +23755,7 @@ repeat:
sub = vim_strnsave(s, (int)(p - s));
str = vim_strnsave(*fnamep, *fnamelen);
*usedlen = (size_t)(p + 1 - src);
- s = do_string_sub(str, pat, sub, flags);
+ s = do_string_sub(str, pat, sub, NULL, flags);
*fnamep = s;
*fnamelen = STRLEN(s);
xfree(*bufp);
@@ -23041,12 +23791,12 @@ repeat:
return valid;
}
-/*
- * Perform a substitution on "str" with pattern "pat" and substitute "sub".
- * "flags" can be "g" to do a global substitute.
- * Returns an allocated string, NULL for error.
- */
-char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, char_u *flags)
+/// Perform a substitution on "str" with pattern "pat" and substitute "sub".
+/// When "sub" is NULL "expr" is used, must be a VAR_FUNC or VAR_PARTIAL.
+/// "flags" can be "g" to do a global substitute.
+/// Returns an allocated string, NULL for error.
+char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub,
+ typval_T *expr, char_u *flags)
{
int sublen;
regmatch_T regmatch;
@@ -23084,23 +23834,21 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, char_u *flags)
zero_width = regmatch.startp[0];
}
- /*
- * Get some space for a temporary buffer to do the substitution
- * into. It will contain:
- * - The text up to where the match is.
- * - The substituted text.
- * - The text after the match.
- */
- sublen = vim_regsub(&regmatch, sub, tail, FALSE, TRUE, FALSE);
+ // Get some space for a temporary buffer to do the substitution
+ // into. It will contain:
+ // - The text up to where the match is.
+ // - The substituted text.
+ // - The text after the match.
+ sublen = vim_regsub(&regmatch, sub, expr, tail, false, true, false);
ga_grow(&ga, (int)((end - tail) + sublen -
(regmatch.endp[0] - regmatch.startp[0])));
/* copy the text up to where the match is */
int i = (int)(regmatch.startp[0] - tail);
memmove((char_u *)ga.ga_data + ga.ga_len, tail, (size_t)i);
- /* add the substituted text */
- (void)vim_regsub(&regmatch, sub, (char_u *)ga.ga_data
- + ga.ga_len + i, TRUE, TRUE, FALSE);
+ // add the substituted text
+ (void)vim_regsub(&regmatch, sub, expr, (char_u *)ga.ga_data
+ + ga.ga_len + i, true, true, false);
ga.ga_len += i + sublen - 1;
tail = regmatch.endp[0];
if (*tail == NUL)
@@ -23117,11 +23865,12 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, char_u *flags)
char_u *ret = vim_strsave(ga.ga_data == NULL ? str : (char_u *)ga.ga_data);
ga_clear(&ga);
- if (p_cpo == empty_option)
+ if (p_cpo == empty_option) {
p_cpo = save_cpo;
- else
- /* Darn, evaluating {sub} expression changed the value. */
+ } else {
+ // Darn, evaluating {sub} expression or {expr} changed the value.
free_string_option(save_cpo);
+ }
return ret;
}
@@ -23487,6 +24236,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
&rettv,
2,
argvars,
+ NULL,
curwin->w_cursor.lnum,
curwin->w_cursor.lnum,
&dummy,
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index 630e309442..57fee5c5a2 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -12,39 +12,6 @@
// All user-defined functions are found in this hashtable.
extern hashtab_T func_hashtab;
-
-// Structure to hold info for a user function.
-typedef struct ufunc ufunc_T;
-
-struct ufunc {
- int uf_varargs; ///< variable nr of arguments
- int uf_flags;
- int uf_calls; ///< nr of active calls
- garray_T uf_args; ///< arguments
- garray_T uf_lines; ///< function lines
- int uf_profiling; ///< true when func is being profiled
- // Profiling the function as a whole.
- int uf_tm_count; ///< nr of calls
- proftime_T uf_tm_total; ///< time spent in function + children
- proftime_T uf_tm_self; ///< time spent in function itself
- proftime_T uf_tm_children; ///< time spent in children this call
- // Profiling the function per line.
- int *uf_tml_count; ///< nr of times line was executed
- proftime_T *uf_tml_total; ///< time spent in a line + children
- proftime_T *uf_tml_self; ///< time spent in a line itself
- proftime_T uf_tml_start; ///< start time for current line
- proftime_T uf_tml_children; ///< time spent in children for this line
- proftime_T uf_tml_wait; ///< start wait time for current line
- int uf_tml_idx; ///< index of line being timed; -1 if none
- int uf_tml_execed; ///< line being timed was executed
- scid_T uf_script_ID; ///< ID of script where function was defined,
- // used for s: variables
- int uf_refcount; ///< for numbered function: reference count
- char_u uf_name[1]; ///< name of function (actually longer); can
- // start with <SNR>123_ (<SNR> is K_SPECIAL
- // KS_EXTRA KE_SNR)
-};
-
// From user function to hashitem and back.
EXTERN ufunc_T dumuf;
#define UF2HIKEY(fp) ((fp)->uf_name)
@@ -127,6 +94,7 @@ typedef enum {
VV__NULL_LIST, // List with NULL value. For test purposes only.
VV__NULL_DICT, // Dictionary with NULL value. For test purposes only.
VV_VIM_DID_ENTER,
+ VV_TESTING,
VV_TYPE_NUMBER,
VV_TYPE_STRING,
VV_TYPE_FUNC,
@@ -156,8 +124,8 @@ extern const list_T *eval_msgpack_type_lists[LAST_MSGPACK_TYPE + 1];
#undef LAST_MSGPACK_TYPE
-/// Maximum number of function arguments
-#define MAX_FUNC_ARGS 20
+typedef int (*ArgvFunc)(int current_argcount, typval_T *argv,
+ int called_func_argcount);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval.h.generated.h"
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 964b061e95..b0bf417207 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -103,6 +103,7 @@ return {
foldtext={},
foldtextresult={args=1},
foreground={},
+ funcref={args={1, 3}},
['function']={args={1, 3}},
garbagecollect={args={0, 1}},
get={args={2, 3}},
@@ -303,6 +304,7 @@ return {
tanh={args=1, func="float_op_wrapper", data="&tanh"},
tempname={},
termopen={args={1, 2}},
+ test_garbagecollect_now={},
timer_start={args={2,3}},
timer_stop={args=1},
tolower={args=1},
diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h
index 34f88cbc98..4ff5589887 100644
--- a/src/nvim/eval/typval_encode.c.h
+++ b/src/nvim/eval/typval_encode.c.h
@@ -344,7 +344,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
case VAR_PARTIAL: {
partial_T *const pt = tv->vval.v_partial;
(void)pt;
- TYPVAL_ENCODE_CONV_FUNC_START(tv, (pt == NULL ? NULL : pt->pt_name));
+ TYPVAL_ENCODE_CONV_FUNC_START(tv, (pt == NULL ? NULL : partial_name(pt)));
_mp_push(*mpstack, ((MPConvStackVal) {
.type = kMPConvPartial,
.tv = tv,
diff --git a/src/nvim/eval_defs.h b/src/nvim/eval_defs.h
index 616c89671b..a61ddb7605 100644
--- a/src/nvim/eval_defs.h
+++ b/src/nvim/eval_defs.h
@@ -6,6 +6,9 @@
#include "nvim/hashtab.h"
#include "nvim/lib/queue.h"
+#include "nvim/garray.h" // for garray_T
+#include "nvim/profile.h" // for proftime_T
+#include "nvim/pos.h" // for linenr_T
typedef int varnumber_T;
typedef double float_T;
@@ -104,15 +107,19 @@ struct listvar_S {
list_T *lv_used_prev; /* previous list in used lists list */
};
-/*
- * Structure to hold an item of a Dictionary.
- * Also used for a variable.
- * The key is copied into "di_key" to avoid an extra alloc/free for it.
- */
+// Static list with 10 items. Use init_static_list() to initialize.
+typedef struct {
+ list_T sl_list; // must be first
+ listitem_T sl_items[10];
+} staticList10_T;
+
+// Structure to hold an item of a Dictionary.
+// Also used for a variable.
+// The key is copied into "di_key" to avoid an extra alloc/free for it.
struct dictitem_S {
- typval_T di_tv; /* type and value of the variable */
- char_u di_flags; /* flags (only used for variable) */
- char_u di_key[1]; /* key (actually longer!) */
+ typval_T di_tv; ///< type and value of the variable
+ char_u di_flags; ///< flags (only used for variable)
+ char_u di_key[1]; ///< key (actually longer!)
};
typedef struct dictitem_S dictitem_T;
@@ -147,9 +154,88 @@ struct dictvar_S {
QUEUE watchers; ///< Dictionary key watchers set by user code.
};
+typedef int scid_T; // script ID
+typedef struct funccall_S funccall_T;
+
+// Structure to hold info for a user function.
+typedef struct ufunc ufunc_T;
+
+struct ufunc {
+ int uf_varargs; ///< variable nr of arguments
+ int uf_flags;
+ int uf_calls; ///< nr of active calls
+ bool uf_cleared; ///< func_clear() was already called
+ garray_T uf_args; ///< arguments
+ garray_T uf_lines; ///< function lines
+ int uf_profiling; ///< true when func is being profiled
+ // Profiling the function as a whole.
+ int uf_tm_count; ///< nr of calls
+ proftime_T uf_tm_total; ///< time spent in function + children
+ proftime_T uf_tm_self; ///< time spent in function itself
+ proftime_T uf_tm_children; ///< time spent in children this call
+ // Profiling the function per line.
+ int *uf_tml_count; ///< nr of times line was executed
+ proftime_T *uf_tml_total; ///< time spent in a line + children
+ proftime_T *uf_tml_self; ///< time spent in a line itself
+ proftime_T uf_tml_start; ///< start time for current line
+ proftime_T uf_tml_children; ///< time spent in children for this line
+ proftime_T uf_tml_wait; ///< start wait time for current line
+ int uf_tml_idx; ///< index of line being timed; -1 if none
+ int uf_tml_execed; ///< line being timed was executed
+ scid_T uf_script_ID; ///< ID of script where function was defined,
+ // used for s: variables
+ int uf_refcount; ///< reference count, see func_name_refcount()
+ funccall_T *uf_scoped; ///< l: local variables for closure
+ char_u uf_name[1]; ///< name of function (actually longer); can
+ // start with <SNR>123_ (<SNR> is K_SPECIAL
+ // KS_EXTRA KE_SNR)
+};
+
+/// Maximum number of function arguments
+#define MAX_FUNC_ARGS 20
+#define VAR_SHORT_LEN 20 // short variable name length
+#define FIXVAR_CNT 12 // number of fixed variables
+
+// structure to hold info for a function that is currently being executed.
+struct funccall_S {
+ ufunc_T *func; ///< function being called
+ int linenr; ///< next line to be executed
+ int returned; ///< ":return" used
+ struct { ///< fixed variables for arguments
+ dictitem_T var; ///< variable (without room for name)
+ char_u room[VAR_SHORT_LEN]; ///< room for the name
+ } fixvar[FIXVAR_CNT];
+ dict_T l_vars; ///< l: local function variables
+ dictitem_T l_vars_var; ///< variable for l: scope
+ dict_T l_avars; ///< a: argument variables
+ dictitem_T l_avars_var; ///< variable for a: scope
+ list_T l_varlist; ///< list for a:000
+ listitem_T l_listitems[MAX_FUNC_ARGS]; ///< listitems for a:000
+ typval_T *rettv; ///< return value
+ 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
+ proftime_T prof_child; ///< time spent in a child
+ funccall_T *caller; ///< calling function or NULL
+ int fc_refcount; ///< number of user functions that reference
+ // this funccal
+ int fc_copyID; ///< for garbage collection
+ garray_T fc_funcs; ///< list of ufunc_T* which keep a reference
+ // to "func"
+};
+
+// structure used by trans_function_name()
+typedef struct {
+ dict_T *fd_dict; ///< Dictionary used.
+ char_u *fd_newkey; ///< New key in "dict" in allocated memory.
+ dictitem_T *fd_di; ///< Dictionary item used.
+} funcdict_T;
+
struct partial_S {
int pt_refcount; ///< Reference count.
- char_u *pt_name; ///< Function name.
+ char_u *pt_name; ///< Function name; when NULL use pt_func->name.
+ ufunc_T *pt_func; ///< Function pointer; when NULL lookup function
+ ///< with pt_name.
bool pt_auto; ///< when true the partial was created for using
///< dict.member in handle_subscript().
int pt_argc; ///< Number of arguments.
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index a471830c56..b7c6fd41f2 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -1327,8 +1327,9 @@ int using_script(void)
void before_blocking(void)
{
updatescript(0);
- if (may_garbage_collect)
- garbage_collect();
+ if (may_garbage_collect) {
+ garbage_collect(false);
+ }
}
/*
@@ -1366,10 +1367,11 @@ int vgetc(void)
char_u buf[MB_MAXBYTES + 1];
int i;
- /* Do garbage collection when garbagecollect() was called previously and
- * we are now at the toplevel. */
- if (may_garbage_collect && want_garbage_collect)
- garbage_collect();
+ // Do garbage collection when garbagecollect() was called previously and
+ // we are now at the toplevel.
+ if (may_garbage_collect && want_garbage_collect) {
+ garbage_collect(false);
+ }
/*
* If a character was put back with vungetc, it was already processed.
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 54551484ee..b141d40aa2 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -1236,6 +1236,9 @@ EXTERN char *ignoredp;
EXTERN bool in_free_unref_items INIT(= false);
+// Used for checking if local variables or arguments used in a lambda.
+EXTERN int *eval_lavars_used INIT(= NULL);
+
// If a msgpack-rpc channel should be started over stdin/stdout
EXTERN bool embedded_mode INIT(= false);
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 88c1990786..fab968836c 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -624,8 +624,9 @@ void getout(int exitval)
iconv_end();
#endif
cs_end();
- if (garbage_collect_at_exit)
- garbage_collect();
+ if (garbage_collect_at_exit) {
+ garbage_collect(false);
+ }
mch_exit(exitval);
}
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 641658532d..756053dc73 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -2460,11 +2460,11 @@ do_mouse (
};
typval_T rettv;
int doesrange;
- (void) call_func((char_u *) tab_page_click_defs[mouse_col].func,
- (int) strlen(tab_page_click_defs[mouse_col].func),
- &rettv, ARRAY_SIZE(argv), argv,
- curwin->w_cursor.lnum, curwin->w_cursor.lnum,
- &doesrange, true, NULL, NULL);
+ (void)call_func((char_u *)tab_page_click_defs[mouse_col].func,
+ (int)strlen(tab_page_click_defs[mouse_col].func),
+ &rettv, ARRAY_SIZE(argv), argv, NULL,
+ curwin->w_cursor.lnum, curwin->w_cursor.lnum,
+ &doesrange, true, NULL, NULL);
clear_tv(&rettv);
break;
}
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index 6613d284d7..1cd334abcd 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -6442,32 +6442,72 @@ static linenr_T submatch_firstlnum;
static linenr_T submatch_maxline;
static int submatch_line_lbr;
-/*
- * vim_regsub() - perform substitutions after a vim_regexec() or
- * vim_regexec_multi() match.
- *
- * If "copy" is TRUE really copy into "dest".
- * If "copy" is FALSE nothing is copied, this is just to find out the length
- * of the result.
- *
- * If "backslash" is TRUE, a backslash will be removed later, need to double
- * them to keep them, and insert a backslash before a CR to avoid it being
- * replaced with a line break later.
- *
- * Note: The matched text must not change between the call of
- * vim_regexec()/vim_regexec_multi() and vim_regsub()! It would make the back
- * references invalid!
- *
- * Returns the size of the replacement, including terminating NUL.
- */
-int vim_regsub(regmatch_T *rmp, char_u *source, char_u *dest, int copy, int magic, int backslash)
+/// Put the submatches in "argv[0]" which is a list passed into call_func() by
+/// vim_regsub_both().
+static int fill_submatch_list(int argc, typval_T *argv, int argcount)
+{
+ listitem_T *li;
+ int i;
+ char_u *s;
+
+ if (argcount == 0) {
+ // called function doesn't take an argument
+ return 0;
+ }
+
+ // Relies on sl_list to be the first item in staticList10_T.
+ init_static_list((staticList10_T *)(argv->vval.v_list));
+
+ // There are always 10 list items in staticList10_T.
+ li = argv->vval.v_list->lv_first;
+ for (i = 0; i < 10; i++) {
+ s = submatch_match->startp[i];
+ if (s == NULL || submatch_match->endp[i] == NULL) {
+ s = NULL;
+ } else {
+ s = vim_strnsave(s, (int)(submatch_match->endp[i] - s));
+ }
+ li->li_tv.v_type = VAR_STRING;
+ li->li_tv.vval.v_string = s;
+ li = li->li_next;
+ }
+ return 1;
+}
+
+static void clear_submatch_list(staticList10_T *sl)
+{
+ int i;
+
+ for (i = 0; i < 10; i++) {
+ xfree(sl->sl_items[i].li_tv.vval.v_string);
+ }
+}
+
+/// vim_regsub() - perform substitutions after a vim_regexec() or
+/// vim_regexec_multi() match.
+///
+/// If "copy" is TRUE really copy into "dest".
+/// If "copy" is FALSE nothing is copied, this is just to find out the length
+/// of the result.
+///
+/// If "backslash" is TRUE, a backslash will be removed later, need to double
+/// them to keep them, and insert a backslash before a CR to avoid it being
+/// replaced with a line break later.
+///
+/// Note: The matched text must not change between the call of
+/// vim_regexec()/vim_regexec_multi() and vim_regsub()! It would make the back
+/// references invalid!
+///
+/// Returns the size of the replacement, including terminating NUL.
+int vim_regsub(regmatch_T *rmp, char_u *source, typval_T *expr, char_u *dest,
+ int copy, int magic, int backslash)
{
reg_match = rmp;
reg_mmatch = NULL;
reg_maxline = 0;
reg_buf = curbuf;
- reg_line_lbr = TRUE;
- return vim_regsub_both(source, dest, copy, magic, backslash);
+ reg_line_lbr = true;
+ return vim_regsub_both(source, expr, dest, copy, magic, backslash);
}
int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char_u *source, char_u *dest, int copy, int magic, int backslash)
@@ -6477,11 +6517,12 @@ int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char_u *source, char_u *de
reg_buf = curbuf; /* always works on the current buffer! */
reg_firstlnum = lnum;
reg_maxline = curbuf->b_ml.ml_line_count - lnum;
- reg_line_lbr = FALSE;
- return vim_regsub_both(source, dest, copy, magic, backslash);
+ reg_line_lbr = false;
+ return vim_regsub_both(source, NULL, dest, copy, magic, backslash);
}
-static int vim_regsub_both(char_u *source, char_u *dest, int copy, int magic, int backslash)
+static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
+ int copy, int magic, int backslash)
{
char_u *src;
char_u *dst;
@@ -6495,8 +6536,8 @@ static int vim_regsub_both(char_u *source, char_u *dest, int copy, int magic, in
int len = 0; /* init for GCC */
static char_u *eval_result = NULL;
- /* Be paranoid... */
- if (source == NULL || dest == NULL) {
+ // Be paranoid...
+ if ((source == NULL && expr == NULL) || dest == NULL) {
EMSG(_(e_null));
return 0;
}
@@ -6505,16 +6546,13 @@ static int vim_regsub_both(char_u *source, char_u *dest, int copy, int magic, in
src = source;
dst = dest;
- /*
- * When the substitute part starts with "\=" evaluate it as an expression.
- */
- if (source[0] == '\\' && source[1] == '='
- && !can_f_submatch /* can't do this recursively */
- ) {
- /* To make sure that the length doesn't change between checking the
- * length and copying the string, and to speed up things, the
- * resulting string is saved from the call with "copy" == FALSE to the
- * call with "copy" == TRUE. */
+ // When the substitute part starts with "\=" evaluate it as an expression.
+ if (expr != NULL || (source[0] == '\\' && source[1] == '='
+ && !can_f_submatch)) { // can't do this recursively
+ // To make sure that the length doesn't change between checking the
+ // length and copying the string, and to speed up things, the
+ // resulting string is saved from the call with "copy" == FALSE to the
+ // call with "copy" == TRUE.
if (copy) {
if (eval_result != NULL) {
STRCPY(dest, eval_result);
@@ -6525,6 +6563,7 @@ static int vim_regsub_both(char_u *source, char_u *dest, int copy, int magic, in
} else {
win_T *save_reg_win;
int save_ireg_ic;
+ bool prev_can_f_submatch = can_f_submatch;
xfree(eval_result);
@@ -6539,9 +6578,50 @@ static int vim_regsub_both(char_u *source, char_u *dest, int copy, int magic, in
submatch_line_lbr = reg_line_lbr;
save_reg_win = reg_win;
save_ireg_ic = ireg_ic;
- can_f_submatch = TRUE;
+ can_f_submatch = true;
+
+ if (expr != NULL) {
+ typval_T argv[2];
+ int dummy;
+ char_u buf[NUMBUFLEN];
+ typval_T rettv;
+ staticList10_T matchList;
+
+ rettv.v_type = VAR_STRING;
+ rettv.vval.v_string = NULL;
+ if (prev_can_f_submatch) {
+ // can't do this recursively
+ } else {
+ argv[0].v_type = VAR_LIST;
+ argv[0].vval.v_list = &matchList.sl_list;
+ matchList.sl_list.lv_len = 0;
+ if (expr->v_type == VAR_FUNC) {
+ s = expr->vval.v_string;
+ call_func(s, (int)STRLEN(s), &rettv, 1, argv,
+ fill_submatch_list, 0L, 0L, &dummy,
+ true, NULL, NULL);
+ } else if (expr->v_type == VAR_PARTIAL) {
+ partial_T *partial = expr->vval.v_partial;
+
+ s = partial_name(partial);
+ call_func(s, (int)STRLEN(s), &rettv, 1, argv,
+ fill_submatch_list, 0L, 0L, &dummy,
+ true, partial, NULL);
+ }
+ if (matchList.sl_list.lv_len > 0) {
+ // fill_submatch_list() was called.
+ clear_submatch_list(&matchList);
+ }
+ }
+ eval_result = get_tv_string_buf_chk(&rettv, buf);
+ if (eval_result != NULL) {
+ eval_result = vim_strsave(eval_result);
+ }
+ clear_tv(&rettv);
+ } else {
+ eval_result = eval_to_string(source + 2, NULL, true);
+ }
- eval_result = eval_to_string(source + 2, NULL, TRUE);
if (eval_result != NULL) {
int had_backslash = FALSE;
diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile
index 721300c334..6f4e0fe49f 100644
--- a/src/nvim/testdir/Makefile
+++ b/src/nvim/testdir/Makefile
@@ -34,12 +34,14 @@ NEW_TESTS ?= \
test_cscope.res \
test_digraph.res \
test_diffmode.res \
+ test_filter_map.res \
test_gn.res \
test_hardcopy.res \
test_help_tagjump.res \
test_history.res \
test_increment.res \
test_increment_dbcs.res \
+ test_lambda.res \
test_langmap.res \
test_match.res \
test_matchadd_conceal.res \
diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim
index 316aba968d..5299fec7c2 100644
--- a/src/nvim/testdir/runtest.vim
+++ b/src/nvim/testdir/runtest.vim
@@ -62,6 +62,12 @@ lang mess C
" Always use forward slashes.
set shellslash
+" Make sure $HOME does not get read or written.
+let $HOME = '/does/not/exist'
+
+" Prepare for calling garbagecollect_for_testing().
+let v:testing = 1
+
" Align with vim defaults.
set directory^=.
set nohidden
diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim
index 60248bf430..375d8219e6 100644
--- a/src/nvim/testdir/test_alot.vim
+++ b/src/nvim/testdir/test_alot.vim
@@ -9,8 +9,10 @@ source test_ex_undo.vim
source test_expr.vim
source test_expr_utf8.vim
source test_feedkeys.vim
+source test_filter_map.vim
source test_goto.vim
source test_jumps.vim
+source test_lambda.vim
source test_match.vim
source test_matchadd_conceal_utf8.vim
source test_menu.vim
diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim
index 21bb057fe1..40db227d97 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/src/nvim/testdir/test_cmdline.vim
@@ -85,6 +85,11 @@ func Test_getcompletion()
let l = getcompletion('paint', 'function')
call assert_equal([], l)
+ let Flambda = {-> 'hello'}
+ let l = getcompletion('', 'function')
+ let l = filter(l, {i, v -> v =~ 'lambda'})
+ call assert_equal(0, len(l))
+
let l = getcompletion('run', 'file')
call assert_true(index(l, 'runtest.vim') >= 0)
let l = getcompletion('walk', 'file')
diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim
index 39dcacb55f..7ceef2834f 100644
--- a/src/nvim/testdir/test_expr.vim
+++ b/src/nvim/testdir/test_expr.vim
@@ -106,3 +106,68 @@ func Test_setmatches()
call setmatches(set)
call assert_equal(exp, getmatches())
endfunc
+
+func Test_substitute_expr()
+ let g:val = 'XXX'
+ call assert_equal('XXX', substitute('yyy', 'y*', '\=g:val', ''))
+ call assert_equal('XXX', substitute('yyy', 'y*', {-> g:val}, ''))
+ call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)',
+ \ '\=nr2char("0x" . submatch(1))', 'g'))
+ call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)',
+ \ {-> nr2char("0x" . submatch(1))}, 'g'))
+
+ call assert_equal('231', substitute('123', '\(.\)\(.\)\(.\)',
+ \ {-> submatch(2) . submatch(3) . submatch(1)}, ''))
+
+ func Recurse()
+ return substitute('yyy', 'y*', {-> g:val}, '')
+ endfunc
+ call assert_equal('--', substitute('xxx', 'x*', {-> '-' . Recurse() . '-'}, ''))
+endfunc
+
+func Test_invalid_submatch()
+ " This was causing invalid memory access in Vim-7.4.2232 and older
+ call assert_fails("call substitute('x', '.', {-> submatch(10)}, '')", 'E935:')
+endfunc
+
+func Test_substitute_expr_arg()
+ call assert_equal('123456789-123456789=', substitute('123456789',
+ \ '\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)',
+ \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, ''))
+
+ call assert_equal('123456-123456=789', substitute('123456789',
+ \ '\(.\)\(.\)\(.\)\(a*\)\(n*\)\(.\)\(.\)\(.\)\(x*\)',
+ \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, ''))
+
+ call assert_equal('123456789-123456789x=', substitute('123456789',
+ \ '\(.\)\(.\)\(.*\)',
+ \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . 'x' . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, ''))
+
+ call assert_fails("call substitute('xxx', '.', {m -> string(add(m, 'x'))}, '')", 'E742:')
+ call assert_fails("call substitute('xxx', '.', {m -> string(insert(m, 'x'))}, '')", 'E742:')
+ call assert_fails("call substitute('xxx', '.', {m -> string(extend(m, ['x']))}, '')", 'E742:')
+ call assert_fails("call substitute('xxx', '.', {m -> string(remove(m, 1))}, '')", 'E742:')
+endfunc
+
+func Test_function_with_funcref()
+ let s:f = function('type')
+ let s:fref = function(s:f)
+ call assert_equal(v:t_string, s:fref('x'))
+ call assert_fails("call function('s:f')", 'E700:')
+endfunc
+
+func Test_funcref()
+ func! One()
+ return 1
+ endfunc
+ let OneByName = function('One')
+ let OneByRef = funcref('One')
+ func! One()
+ return 2
+ endfunc
+ call assert_equal(2, OneByName())
+ call assert_equal(1, OneByRef())
+ let OneByRef = funcref('One')
+ call assert_equal(2, OneByRef())
+ call assert_fails('echo funcref("{")', 'E475:')
+endfunc
diff --git a/src/nvim/testdir/test_filter_map.vim b/src/nvim/testdir/test_filter_map.vim
new file mode 100644
index 0000000000..c8d64ce0a4
--- /dev/null
+++ b/src/nvim/testdir/test_filter_map.vim
@@ -0,0 +1,81 @@
+" Test filter() and map()
+
+" list with expression string
+func Test_filter_map_list_expr_string()
+ " filter()
+ call assert_equal([2, 3, 4], filter([1, 2, 3, 4], 'v:val > 1'))
+ call assert_equal([3, 4], filter([1, 2, 3, 4], 'v:key > 1'))
+ call assert_equal([], filter([1, 2, 3, 4], 0))
+
+ " map()
+ call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], 'v:val * 2'))
+ call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], 'v:key * 2'))
+ call assert_equal([9, 9, 9, 9], map([1, 2, 3, 4], 9))
+endfunc
+
+" dict with expression string
+func Test_filter_map_dict_expr_string()
+ let dict = {"foo": 1, "bar": 2, "baz": 3}
+
+ " filter()
+ call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), 'v:val > 1'))
+ call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), 'v:key > "bar"'))
+ call assert_equal({}, filter(copy(dict), 0))
+
+ " map()
+ call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), 'v:val * 2'))
+ call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), 'v:key[0]'))
+ call assert_equal({"foo": 9, "bar": 9, "baz": 9}, map(copy(dict), 9))
+endfunc
+
+" list with funcref
+func Test_filter_map_list_expr_funcref()
+ " filter()
+ func! s:filter1(index, val) abort
+ return a:val > 1
+ endfunc
+ call assert_equal([2, 3, 4], filter([1, 2, 3, 4], function('s:filter1')))
+
+ func! s:filter2(index, val) abort
+ return a:index > 1
+ endfunc
+ call assert_equal([3, 4], filter([1, 2, 3, 4], function('s:filter2')))
+
+ " map()
+ func! s:filter3(index, val) abort
+ return a:val * 2
+ endfunc
+ call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], function('s:filter3')))
+
+ func! s:filter4(index, val) abort
+ return a:index * 2
+ endfunc
+ call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], function('s:filter4')))
+endfunc
+
+" dict with funcref
+func Test_filter_map_dict_expr_funcref()
+ let dict = {"foo": 1, "bar": 2, "baz": 3}
+
+ " filter()
+ func! s:filter1(key, val) abort
+ return a:val > 1
+ endfunc
+ call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), function('s:filter1')))
+
+ func! s:filter2(key, val) abort
+ return a:key > "bar"
+ endfunc
+ call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), function('s:filter2')))
+
+ " map()
+ func! s:filter3(key, val) abort
+ return a:val * 2
+ endfunc
+ call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), function('s:filter3')))
+
+ func! s:filter4(key, val) abort
+ return a:key[0]
+ endfunc
+ call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), function('s:filter4')))
+endfunc
diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim
new file mode 100644
index 0000000000..311cc6e2cb
--- /dev/null
+++ b/src/nvim/testdir/test_lambda.vim
@@ -0,0 +1,287 @@
+" Test for lambda and closure
+
+function! Test_lambda_feature()
+ call assert_equal(1, has('lambda'))
+endfunction
+
+function! Test_lambda_with_filter()
+ let s:x = 2
+ call assert_equal([2, 3], filter([1, 2, 3], {i, v -> v >= s:x}))
+endfunction
+
+function! Test_lambda_with_map()
+ let s:x = 1
+ call assert_equal([2, 3, 4], map([1, 2, 3], {i, v -> v + s:x}))
+endfunction
+
+function! Test_lambda_with_sort()
+ call assert_equal([1, 2, 3, 4, 7], sort([3,7,2,1,4], {a, b -> a - b}))
+endfunction
+
+function! Test_lambda_with_timer()
+ if !has('timers')
+ return
+ endif
+
+ let s:n = 0
+ let s:timer_id = 0
+ function! s:Foo()
+ "let n = 0
+ let s:timer_id = timer_start(50, {-> execute("let s:n += 1 | echo s:n", "")}, {"repeat": -1})
+ endfunction
+
+ call s:Foo()
+ sleep 200ms
+ " do not collect lambda
+ call garbagecollect()
+ let m = s:n
+ sleep 200ms
+ call timer_stop(s:timer_id)
+ call assert_true(m > 1)
+ call assert_true(s:n > m + 1)
+ call assert_true(s:n < 9)
+endfunction
+
+function! Test_lambda_with_partial()
+ let l:Cb = function({... -> ['zero', a:1, a:2, a:3]}, ['one', 'two'])
+ call assert_equal(['zero', 'one', 'two', 'three'], l:Cb('three'))
+endfunction
+
+function Test_lambda_fails()
+ call assert_equal(3, {a, b -> a + b}(1, 2))
+ call assert_fails('echo {a, a -> a + a}(1, 2)', 'E15:')
+ call assert_fails('echo {a, b -> a + b)}(1, 2)', 'E15:')
+endfunc
+
+func Test_not_lambda()
+ let x = {'>' : 'foo'}
+ call assert_equal('foo', x['>'])
+endfunc
+
+function! Test_lambda_capture_by_reference()
+ let v = 1
+ let l:F = {x -> x + v}
+ let v = 2
+ call assert_equal(12, l:F(10))
+endfunction
+
+function! Test_lambda_side_effect()
+ function! s:update_and_return(arr)
+ let a:arr[1] = 5
+ return a:arr
+ endfunction
+
+ function! s:foo(arr)
+ return {-> s:update_and_return(a:arr)}
+ endfunction
+
+ let arr = [3,2,1]
+ call assert_equal([3, 5, 1], s:foo(arr)())
+endfunction
+
+function! Test_lambda_refer_local_variable_from_other_scope()
+ function! s:foo(X)
+ return a:X() " refer l:x in s:bar()
+ endfunction
+
+ function! s:bar()
+ let x = 123
+ return s:foo({-> x})
+ endfunction
+
+ call assert_equal(123, s:bar())
+endfunction
+
+function! Test_lambda_do_not_share_local_variable()
+ function! s:define_funcs()
+ let l:One = {-> split(execute("let a = 'abc' | echo a"))[0]}
+ let l:Two = {-> exists("a") ? a : "no"}
+ return [l:One, l:Two]
+ endfunction
+
+ let l:F = s:define_funcs()
+
+ call assert_equal('no', l:F[1]())
+ call assert_equal('abc', l:F[0]())
+ call assert_equal('no', l:F[1]())
+endfunction
+
+function! Test_lambda_closure_counter()
+ function! s:foo()
+ let x = 0
+ return {-> [execute("let x += 1"), x][-1]}
+ endfunction
+
+ let l:F = s:foo()
+ call garbagecollect()
+ call assert_equal(1, l:F())
+ call assert_equal(2, l:F())
+ call assert_equal(3, l:F())
+ call assert_equal(4, l:F())
+endfunction
+
+function! Test_lambda_with_a_var()
+ function! s:foo()
+ let x = 2
+ return {... -> a:000 + [x]}
+ endfunction
+ function! s:bar()
+ return s:foo()(1)
+ endfunction
+
+ call assert_equal([1, 2], s:bar())
+endfunction
+
+function! Test_lambda_call_lambda_from_lambda()
+ function! s:foo(x)
+ let l:F1 = {-> {-> a:x}}
+ return {-> l:F1()}
+ endfunction
+
+ let l:F = s:foo(1)
+ call assert_equal(1, l:F()())
+endfunction
+
+function! Test_lambda_delfunc()
+ function! s:gen()
+ let pl = l:
+ let l:Foo = {-> get(pl, "Foo", get(pl, "Bar", {-> 0}))}
+ let l:Bar = l:Foo
+ delfunction l:Foo
+ return l:Bar
+ endfunction
+
+ let l:F = s:gen()
+ call assert_fails(':call l:F()', 'E933:')
+endfunction
+
+function! Test_lambda_scope()
+ function! s:NewCounter()
+ let c = 0
+ return {-> [execute('let c += 1'), c][-1]}
+ endfunction
+
+ function! s:NewCounter2()
+ return {-> [execute('let c += 100'), c][-1]}
+ endfunction
+
+ let l:C = s:NewCounter()
+ let l:D = s:NewCounter2()
+
+ call assert_equal(1, l:C())
+ call assert_fails(':call l:D()', 'E15:') " E121: then E15:
+ call assert_equal(2, l:C())
+endfunction
+
+function! Test_lambda_share_scope()
+ function! s:New()
+ let c = 0
+ let l:Inc0 = {-> [execute('let c += 1'), c][-1]}
+ let l:Dec0 = {-> [execute('let c -= 1'), c][-1]}
+ return [l:Inc0, l:Dec0]
+ endfunction
+
+ let [l:Inc, l:Dec] = s:New()
+
+ call assert_equal(1, l:Inc())
+ call assert_equal(2, l:Inc())
+ call assert_equal(1, l:Dec())
+endfunction
+
+function! Test_lambda_circular_reference()
+ function! s:Foo()
+ let d = {}
+ let d.f = {-> d}
+ return d.f
+ endfunction
+
+ call s:Foo()
+ call garbagecollect()
+ let i = 0 | while i < 10000 | call s:Foo() | let i+= 1 | endwhile
+ call garbagecollect()
+endfunction
+
+function! Test_lambda_combination()
+ call assert_equal(2, {x -> {x -> x}}(1)(2))
+ call assert_equal(10, {y -> {x -> x(y)(10)}({y -> y})}({z -> z}))
+ call assert_equal(5.0, {x -> {y -> x / y}}(10)(2.0))
+ call assert_equal(6, {x -> {y -> {z -> x + y + z}}}(1)(2)(3))
+
+ call assert_equal(6, {x -> {f -> f(x)}}(3)({x -> x * 2}))
+ call assert_equal(6, {f -> {x -> f(x)}}({x -> x * 2})(3))
+
+ " Z combinator
+ let Z = {f -> {x -> f({y -> x(x)(y)})}({x -> f({y -> x(x)(y)})})}
+ let Fact = {f -> {x -> x == 0 ? 1 : x * f(x - 1)}}
+ call assert_equal(120, Z(Fact)(5))
+endfunction
+
+function! Test_closure_counter()
+ function! s:foo()
+ let x = 0
+ function! s:bar() closure
+ let x += 1
+ return x
+ endfunction
+ return function('s:bar')
+ endfunction
+
+ let l:F = s:foo()
+ call garbagecollect()
+ call assert_equal(1, l:F())
+ call assert_equal(2, l:F())
+ call assert_equal(3, l:F())
+ call assert_equal(4, l:F())
+endfunction
+
+function! Test_closure_unlet()
+ function! s:foo()
+ let x = 1
+ function! s:bar() closure
+ unlet x
+ endfunction
+ call s:bar()
+ return l:
+ endfunction
+
+ call assert_false(has_key(s:foo(), 'x'))
+ call garbagecollect()
+endfunction
+
+function! LambdaFoo()
+ let x = 0
+ function! LambdaBar() closure
+ let x += 1
+ return x
+ endfunction
+ return function('LambdaBar')
+endfunction
+
+func Test_closure_refcount()
+ let g:Count = LambdaFoo()
+ call test_garbagecollect_now()
+ call assert_equal(1, g:Count())
+ let g:Count2 = LambdaFoo()
+ call test_garbagecollect_now()
+ call assert_equal(1, g:Count2())
+ call assert_equal(2, g:Count())
+ call assert_equal(3, g:Count2())
+
+ delfunc LambdaFoo
+ delfunc LambdaBar
+endfunc
+
+" This test is causing a use-after-free on shutdown.
+func Test_named_function_closure()
+ func! Afoo()
+ let x = 14
+ func! s:Abar() closure
+ return x
+ endfunc
+ call assert_equal(14, s:Abar())
+ endfunc
+ call Afoo()
+ call assert_equal(14, s:Abar())
+ call garbagecollect()
+ call assert_equal(14, s:Abar())
+endfunc
diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim
index 3a6e162453..de5c26c2dd 100644
--- a/src/nvim/testdir/test_partial.vim
+++ b/src/nvim/testdir/test_partial.vim
@@ -14,6 +14,14 @@ func MySort(up, one, two)
return a:one < a:two ? 1 : -1
endfunc
+func MyMap(sub, index, val)
+ return a:val - a:sub
+endfunc
+
+func MyFilter(threshold, index, val)
+ return a:val > a:threshold
+endfunc
+
func Test_partial_args()
let Cb = function('MyFunc', ["foo", "bar"])
@@ -36,6 +44,16 @@ func Test_partial_args()
call assert_equal([1, 2, 3], sort([3, 1, 2], Sort))
let Sort = function('MySort', [0])
call assert_equal([3, 2, 1], sort([3, 1, 2], Sort))
+
+ let Map = function('MyMap', [2])
+ call assert_equal([-1, 0, 1], map([1, 2, 3], Map))
+ let Map = function('MyMap', [3])
+ call assert_equal([-2, -1, 0], map([1, 2, 3], Map))
+
+ let Filter = function('MyFilter', [1])
+ call assert_equal([2, 3], filter([1, 2, 3], Filter))
+ let Filter = function('MyFilter', [2])
+ call assert_equal([3], filter([1, 2, 3], Filter))
endfunc
func MyDictFunc(arg1, arg2) dict
@@ -59,6 +77,9 @@ func Test_partial_dict()
call assert_equal("hello/xxx/yyy", Cb("xxx", "yyy"))
call assert_fails('Cb("fff")', 'E492:')
+ let Cb = function('MyDictFunc', dict)
+ call assert_equal({"foo": "hello/foo/1", "bar": "hello/bar/2"}, map({"foo": 1, "bar": 2}, Cb))
+
let dict = {"tr": function('tr', ['hello', 'h', 'H'])}
call assert_equal("Hello", dict.tr())
endfunc
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 096af415ba..3787ba6908 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -205,9 +205,9 @@ static int included_patches[] = {
// 2238 NA
2237,
// 2236,
- // 2235,
+ 2235,
// 2234 NA
- // 2233,
+ 2233,
// 2232 NA
// 2231,
// 2230,
@@ -243,7 +243,7 @@ static int included_patches[] = {
// 2200,
// 2199 NA
// 2198 NA
- // 2197,
+ 2197,
// 2196,
// 2195 NA
2194,
@@ -297,16 +297,16 @@ static int included_patches[] = {
2146,
// 2145 NA
// 2144,
- // 2143,
- // 2142,
- // 2141,
+ 2143,
+ 2142,
+ 2141,
// 2140 NA
- // 2139,
+ 2139,
// 2138 NA
- // 2137,
- // 2136,
+ 2137,
+ 2136,
// 2135,
- // 2134,
+ 2134,
// 2133 NA
// 2132 NA
// 2131 NA
@@ -319,9 +319,9 @@ static int included_patches[] = {
2124,
2123,
// 2122 NA
- // 2121,
- // 2120,
- // 2119,
+ 2121,
+ 2120,
+ 2119,
// 2118 NA
2117,
// 2116 NA
@@ -344,13 +344,13 @@ static int included_patches[] = {
2099,
// 2098,
// 2097,
- // 2096,
+ 2096,
// 2095,
// 2094 NA
// 2093 NA
// 2092 NA
// 2091 NA
- // 2090,
+ 2090,
// 2089 NA
2088,
2087,
@@ -364,11 +364,11 @@ static int included_patches[] = {
// 2079 NA
// 2078 NA
2077,
- // 2076,
+ 2076,
2075,
2074,
// 2073 NA
- // 2072,
+ 2072,
2071,
// 2070 NA
// 2069,
@@ -396,7 +396,7 @@ static int included_patches[] = {
// 2047,
// 2046,
// 2045 NA
- // 2044,
+ 2044,
2043,
// 2042 NA
// 2041 NA
@@ -438,7 +438,7 @@ static int included_patches[] = {
2005,
// 2004 NA
// 2003 NA
- // 2002,
+ 2002,
// 2001 NA
2000,
1999,
@@ -451,7 +451,7 @@ static int included_patches[] = {
// 1992,
// 1991,
1990,
- // 1989,
+ 1989,
// 1988 NA
// 1987 NA
// 1986,
@@ -714,7 +714,7 @@ static int included_patches[] = {
1730,
// 1729 NA
1728,
- // 1727 NA
+ 1727,
// 1726 NA
// 1725 NA
// 1724 NA