aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/builtin.txt35
-rw-r--r--runtime/doc/usr_41.txt2
-rw-r--r--runtime/lua/vim/_meta/vimfn.lua39
-rw-r--r--src/nvim/eval.c79
-rw-r--r--src/nvim/eval.lua42
-rw-r--r--test/old/testdir/test_filter_map.vim142
-rw-r--r--test/old/testdir/vim9.vim4
7 files changed, 317 insertions, 26 deletions
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 416a10460a..cf82c478b6 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -1866,6 +1866,41 @@ foldtextresult({lnum}) *foldtextresult()*
line, "'m" mark m, etc.
Useful when exporting folded text, e.g., to HTML.
+foreach({expr1}, {expr2}) *foreach()*
+ {expr1} must be a |List|, |String|, |Blob| or |Dictionary|.
+ For each item in {expr1} execute {expr2}. {expr1} is not
+ modified; its values may be, as with |:lockvar| 1. |E741|
+ See |map()| and |filter()| to modify {expr1}.
+
+ {expr2} must be a |string| or |Funcref|.
+
+ If {expr2} is a |string|, inside {expr2} |v:val| has the value
+ of the current item. For a |Dictionary| |v:key| has the key
+ of the current item and for a |List| |v:key| has the index of
+ the current item. For a |Blob| |v:key| has the index of the
+ current byte. For a |String| |v:key| has the index of the
+ current character.
+ Examples: >
+ call foreach(mylist, 'let used[v:val] = v:true')
+< This records the items that are in the {expr1} list.
+
+ Note that {expr2} is the result of expression and is then used
+ as a command. Often it is good to use a |literal-string| to
+ avoid having to double backslashes.
+
+ If {expr2} is a |Funcref| it must take two arguments:
+ 1. the key or the index of the current item.
+ 2. the value of the current item.
+ With a lambda you don't get an error if it only accepts one
+ argument.
+ If the function returns a value, it is ignored.
+
+ Returns {expr1} in all cases.
+ When an error is encountered while executing {expr2} no
+ further items in {expr1} are processed.
+ When {expr2} is a Funcref errors inside a function are ignored,
+ unless it was defined with the "abort" flag.
+
fullcommand({name}) *fullcommand()*
Get the full command name from a short abbreviated command
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index acb957d0c1..2dae9333b6 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -665,6 +665,7 @@ List manipulation: *list-functions*
filter() remove selected items from a List
map() change each List item
mapnew() make a new List with changed items
+ foreach() apply function to List items
reduce() reduce a List to a value
slice() take a slice of a List
sort() sort a List
@@ -696,6 +697,7 @@ Dictionary manipulation: *dict-functions*
filter() remove selected entries from a Dictionary
map() change each Dictionary entry
mapnew() make a new Dictionary with changed items
+ foreach() apply function to Dictionary items
keys() get List of Dictionary keys
values() get List of Dictionary values
items() get List of Dictionary key-value pairs
diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
index 01b4ef920b..59497f96e9 100644
--- a/runtime/lua/vim/_meta/vimfn.lua
+++ b/runtime/lua/vim/_meta/vimfn.lua
@@ -2308,6 +2308,45 @@ function vim.fn.foldtext() end
--- @return string
function vim.fn.foldtextresult(lnum) end
+--- {expr1} must be a |List|, |String|, |Blob| or |Dictionary|.
+--- For each item in {expr1} execute {expr2}. {expr1} is not
+--- modified; its values may be, as with |:lockvar| 1. |E741|
+--- See |map()| and |filter()| to modify {expr1}.
+---
+--- {expr2} must be a |string| or |Funcref|.
+---
+--- If {expr2} is a |string|, inside {expr2} |v:val| has the value
+--- of the current item. For a |Dictionary| |v:key| has the key
+--- of the current item and for a |List| |v:key| has the index of
+--- the current item. For a |Blob| |v:key| has the index of the
+--- current byte. For a |String| |v:key| has the index of the
+--- current character.
+--- Examples: >
+--- call foreach(mylist, 'let used[v:val] = v:true')
+--- <This records the items that are in the {expr1} list.
+---
+--- Note that {expr2} is the result of expression and is then used
+--- as a command. Often it is good to use a |literal-string| to
+--- avoid having to double backslashes.
+---
+--- If {expr2} is a |Funcref| it must take two arguments:
+--- 1. the key or the index of the current item.
+--- 2. the value of the current item.
+--- With a lambda you don't get an error if it only accepts one
+--- argument.
+--- If the function returns a value, it is ignored.
+---
+--- Returns {expr1} in all cases.
+--- When an error is encountered while executing {expr2} no
+--- further items in {expr1} are processed.
+--- When {expr2} is a Funcref errors inside a function are ignored,
+--- unless it was defined with the "abort" flag.
+---
+--- @param expr1 any
+--- @param expr2 any
+--- @return any
+function vim.fn.foreach(expr1, expr2) end
+
--- Get the full command name from a short abbreviated command
--- name; see |20.2| for details on command abbreviations.
---
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index ad2ff89f7e..faa652f80a 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -308,11 +308,12 @@ static partial_T *vvlua_partial;
/// v: hashtab
#define vimvarht vimvardict.dv_hashtab
-/// Enum used by filter(), map() and mapnew()
+/// Enum used by filter(), map(), mapnew() and foreach()
typedef enum {
FILTERMAP_FILTER,
FILTERMAP_MAP,
FILTERMAP_MAPNEW,
+ FILTERMAP_FOREACH,
} filtermap_T;
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -5098,7 +5099,8 @@ void assert_error(garray_T *gap)
tv_list_append_string(vimvars[VV_ERRORS].vv_list, gap->ga_data, (ptrdiff_t)gap->ga_len);
}
-/// Implementation of map() and filter() for a Dict.
+/// Implementation of map(), filter(), foreach() for a Dict. Apply "expr" to
+/// every item in Dict "d" and return the result in "rettv".
static void filter_map_dict(dict_T *d, filtermap_T filtermap, const char *func_name,
const char *arg_errmsg, typval_T *expr, typval_T *rettv)
{
@@ -5166,7 +5168,7 @@ static void filter_map_dict(dict_T *d, filtermap_T filtermap, const char *func_n
d->dv_lock = prev_lock;
}
-/// Implementation of map() and filter() for a Blob.
+/// Implementation of map(), filter(), foreach() for a Blob.
static void filter_map_blob(blob_T *blob_arg, filtermap_T filtermap, typval_T *expr,
const char *arg_errmsg, typval_T *rettv)
{
@@ -5209,20 +5211,22 @@ static void filter_map_blob(blob_T *blob_arg, filtermap_T filtermap, typval_T *e
|| did_emsg) {
break;
}
- if (newtv.v_type != VAR_NUMBER && newtv.v_type != VAR_BOOL) {
- tv_clear(&newtv);
- emsg(_(e_invalblob));
- break;
- }
- if (filtermap != FILTERMAP_FILTER) {
- if (newtv.vval.v_number != val) {
- tv_blob_set(b_ret, i, (uint8_t)newtv.vval.v_number);
+ if (filtermap != FILTERMAP_FOREACH) {
+ if (newtv.v_type != VAR_NUMBER && newtv.v_type != VAR_BOOL) {
+ tv_clear(&newtv);
+ emsg(_(e_invalblob));
+ break;
+ }
+ if (filtermap != FILTERMAP_FILTER) {
+ if (newtv.vval.v_number != val) {
+ tv_blob_set(b_ret, i, (uint8_t)newtv.vval.v_number);
+ }
+ } else if (rem) {
+ char *const p = (char *)blob_arg->bv_ga.ga_data;
+ memmove(p + i, p + i + 1, (size_t)(b->bv_ga.ga_len - i - 1));
+ b->bv_ga.ga_len--;
+ i--;
}
- } else if (rem) {
- char *const p = (char *)blob_arg->bv_ga.ga_data;
- memmove(p + i, p + i + 1, (size_t)(b->bv_ga.ga_len - i - 1));
- b->bv_ga.ga_len--;
- i--;
}
idx++;
}
@@ -5230,7 +5234,7 @@ static void filter_map_blob(blob_T *blob_arg, filtermap_T filtermap, typval_T *e
b->bv_lock = prev_lock;
}
-/// Implementation of map() and filter() for a String.
+/// Implementation of map(), filter(), foreach() for a String.
static void filter_map_string(const char *str, filtermap_T filtermap, typval_T *expr,
typval_T *rettv)
{
@@ -5259,7 +5263,8 @@ static void filter_map_string(const char *str, filtermap_T filtermap, typval_T *
tv_clear(&newtv);
tv_clear(&tv);
break;
- } else if (filtermap != FILTERMAP_FILTER) {
+ }
+ if (filtermap == FILTERMAP_MAP || filtermap == FILTERMAP_MAPNEW) {
if (newtv.v_type != VAR_STRING) {
tv_clear(&newtv);
tv_clear(&tv);
@@ -5268,7 +5273,7 @@ static void filter_map_string(const char *str, filtermap_T filtermap, typval_T *
} else {
ga_concat(&ga, newtv.vval.v_string);
}
- } else if (!rem) {
+ } else if (filtermap == FILTERMAP_FOREACH || !rem) {
ga_concat(&ga, tv.vval.v_string);
}
@@ -5281,7 +5286,8 @@ static void filter_map_string(const char *str, filtermap_T filtermap, typval_T *
rettv->vval.v_string = ga.ga_data;
}
-/// Implementation of map() and filter() for a List.
+/// Implementation of map(), filter(), foreach() for a List. Apply "expr" to
+/// every item in List "l" and return the result in "rettv".
static void filter_map_list(list_T *l, filtermap_T filtermap, const char *func_name,
const char *arg_errmsg, typval_T *expr, typval_T *rettv)
{
@@ -5345,21 +5351,25 @@ static void filter_map_list(list_T *l, filtermap_T filtermap, const char *func_n
tv_list_set_lock(l, prev_lock);
}
-/// Implementation of map() and filter().
+/// Implementation of map(), filter() and foreach().
static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
{
const char *const func_name = (filtermap == FILTERMAP_MAP
? "map()"
: (filtermap == FILTERMAP_MAPNEW
? "mapnew()"
- : "filter()"));
+ : (filtermap == FILTERMAP_FILTER
+ ? "filter()"
+ : "foreach()")));
const char *const arg_errmsg = (filtermap == FILTERMAP_MAP
? N_("map() argument")
: (filtermap == FILTERMAP_MAPNEW
? N_("mapnew() argument")
- : N_("filter() argument")));
+ : (filtermap == FILTERMAP_FILTER
+ ? N_("filter() argument")
+ : N_("foreach() argument"))));
- // map() and filter() return the first argument, also on failure.
+ // map(), filter(), foreach() return the first argument, also on failure.
if (filtermap != FILTERMAP_MAPNEW && argvars[0].v_type != VAR_STRING) {
tv_copy(&argvars[0], rettv);
}
@@ -5407,7 +5417,7 @@ static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap
}
}
-/// Handle one item for map() and filter().
+/// Handle one item for map(), filter(), foreach().
/// Sets v:val to "tv". Caller must set v:key.
///
/// @param tv original value
@@ -5422,6 +5432,17 @@ static int filter_map_one(typval_T *tv, typval_T *expr, const filtermap_T filter
int retval = FAIL;
tv_copy(tv, &vimvars[VV_VAL].vv_tv);
+
+ newtv->v_type = VAR_UNKNOWN;
+ if (filtermap == FILTERMAP_FOREACH && expr->v_type == VAR_STRING) {
+ // foreach() is not limited to an expression
+ do_cmdline_cmd(expr->vval.v_string);
+ if (!did_emsg) {
+ retval = OK;
+ }
+ goto theend;
+ }
+
argv[0] = vimvars[VV_KEY].vv_tv;
argv[1] = vimvars[VV_VAL].vv_tv;
if (eval_expr_typval(expr, false, argv, 2, newtv) == FAIL) {
@@ -5438,6 +5459,8 @@ static int filter_map_one(typval_T *tv, typval_T *expr, const filtermap_T filter
if (error) {
goto theend;
}
+ } else if (filtermap == FILTERMAP_FOREACH) {
+ tv_clear(newtv);
}
retval = OK;
theend:
@@ -5463,6 +5486,12 @@ void f_mapnew(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
filter_map(argvars, rettv, FILTERMAP_MAPNEW);
}
+/// "foreach()" function
+void f_foreach(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ filter_map(argvars, rettv, FILTERMAP_FOREACH);
+}
+
/// "function()" function
/// "funcref()" function
void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref)
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 9545cb9823..9a66d20dbb 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -2923,6 +2923,48 @@ M.funcs = {
returns = 'string',
signature = 'foldtextresult({lnum})',
},
+ foreach = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ {expr1} must be a |List|, |String|, |Blob| or |Dictionary|.
+ For each item in {expr1} execute {expr2}. {expr1} is not
+ modified; its values may be, as with |:lockvar| 1. |E741|
+ See |map()| and |filter()| to modify {expr1}.
+
+ {expr2} must be a |string| or |Funcref|.
+
+ If {expr2} is a |string|, inside {expr2} |v:val| has the value
+ of the current item. For a |Dictionary| |v:key| has the key
+ of the current item and for a |List| |v:key| has the index of
+ the current item. For a |Blob| |v:key| has the index of the
+ current byte. For a |String| |v:key| has the index of the
+ current character.
+ Examples: >
+ call foreach(mylist, 'let used[v:val] = v:true')
+ <This records the items that are in the {expr1} list.
+
+ Note that {expr2} is the result of expression and is then used
+ as a command. Often it is good to use a |literal-string| to
+ avoid having to double backslashes.
+
+ If {expr2} is a |Funcref| it must take two arguments:
+ 1. the key or the index of the current item.
+ 2. the value of the current item.
+ With a lambda you don't get an error if it only accepts one
+ argument.
+ If the function returns a value, it is ignored.
+
+ Returns {expr1} in all cases.
+ When an error is encountered while executing {expr2} no
+ further items in {expr1} are processed.
+ When {expr2} is a Funcref errors inside a function are ignored,
+ unless it was defined with the "abort" flag.
+ ]=],
+ name = 'foreach',
+ params = { { 'expr1', 'any' }, { 'expr2', 'any' } },
+ signature = 'foreach({expr1}, {expr2})',
+ },
foreground = {
args = 0,
params = {},
diff --git a/test/old/testdir/test_filter_map.vim b/test/old/testdir/test_filter_map.vim
index fb435f4291..5ef56c5623 100644
--- a/test/old/testdir/test_filter_map.vim
+++ b/test/old/testdir/test_filter_map.vim
@@ -14,6 +14,18 @@ func Test_filter_map_list_expr_string()
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))
call assert_equal([7, 7, 7], map([1, 2, 3], ' 7 '))
+
+ " foreach()
+ let list01 = [1, 2, 3, 4]
+ let list02 = []
+ call assert_equal([1, 2, 3, 4], foreach(list01, 'call add(list02, v:val * 2)'))
+ call assert_equal([2, 4, 6, 8], list02)
+ let list02 = []
+ call assert_equal([1, 2, 3, 4], foreach(list01, 'call add(list02, v:key * 2)'))
+ call assert_equal([0, 2, 4, 6], list02)
+ let list02 = []
+ call assert_equal([1, 2, 3, 4], foreach(list01, 'call add(list02, 9)'))
+ call assert_equal([9, 9, 9, 9], list02)
endfunc
" dict with expression string
@@ -29,6 +41,14 @@ func Test_filter_map_dict_expr_string()
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))
+
+ " foreach()
+ let dict01 = {}
+ call assert_equal(dict, foreach(copy(dict), 'let dict01[v:key] = v:val * 2'))
+ call assert_equal({"foo": 2, "bar": 4, "baz": 6}, dict01)
+ let dict01 = {}
+ call assert_equal(dict, foreach(copy(dict), 'let dict01[v:key] = v:key[0]'))
+ call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, dict01)
endfunc
" list with funcref
@@ -54,6 +74,16 @@ func Test_filter_map_list_expr_funcref()
return a:index * 2
endfunc
call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], function('s:filter4')))
+
+ " foreach()
+ func! s:foreach1(index, val) abort
+ call add(g:test_variable, a:val + 1)
+ return [ 11, 12, 13, 14 ]
+ endfunc
+ let g:test_variable = []
+ call assert_equal([0, 1, 2, 3, 4], foreach(range(5), function('s:foreach1')))
+ call assert_equal([1, 2, 3, 4, 5], g:test_variable)
+ call remove(g:, 'test_variable')
endfunc
func Test_filter_map_nested()
@@ -90,11 +120,46 @@ func Test_filter_map_dict_expr_funcref()
return a:key[0]
endfunc
call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), function('s:filter4')))
+
+ " foreach()
+ func! s:foreach1(key, val) abort
+ call extend(g:test_variable, {a:key: a:val * 2})
+ return [ 11, 12, 13, 14 ]
+ endfunc
+ let g:test_variable = {}
+ call assert_equal(dict, foreach(copy(dict), function('s:foreach1')))
+ call assert_equal({"foo": 2, "bar": 4, "baz": 6}, g:test_variable)
+ call remove(g:, 'test_variable')
+endfunc
+
+func Test_map_filter_locked()
+ let list01 = [1, 2, 3, 4]
+ lockvar 1 list01
+ call assert_fails('call filter(list01, "v:val > 1")', 'E741:')
+ call assert_equal([2, 4, 6, 8], map(list01, 'v:val * 2'))
+ call assert_equal([1, 2, 3, 4], map(list01, 'v:val / 2'))
+ call assert_equal([2, 4, 6, 8], mapnew(list01, 'v:val * 2'))
+ let g:test_variable = []
+ call assert_equal([1, 2, 3, 4], foreach(list01, 'call add(g:test_variable, v:val * 2)'))
+ call remove(g:, 'test_variable')
+ call assert_fails('call filter(list01, "v:val > 1")', 'E741:')
+ unlockvar 1 list01
+ lockvar! list01
+ call assert_fails('call filter(list01, "v:val > 1")', 'E741:')
+ call assert_fails('call map(list01, "v:val * 2")', 'E741:')
+ call assert_equal([2, 4, 6, 8], mapnew(list01, 'v:val * 2'))
+ let g:test_variable = []
+ call assert_equal([1, 2, 3, 4], foreach(list01, 'call add(g:test_variable, v:val * 2)'))
+ call assert_fails('call foreach(list01, "let list01[0] = -1")', 'E741:')
+ call assert_fails('call filter(list01, "v:val > 1")', 'E741:')
+ call remove(g:, 'test_variable')
+ unlockvar! list01
endfunc
func Test_map_filter_fails()
call assert_fails('call map([1], "42 +")', 'E15:')
call assert_fails('call filter([1], "42 +")', 'E15:')
+ call assert_fails('call foreach([1], "let a = }")', 'E15:')
call assert_fails("let l = filter([1, 2, 3], '{}')", 'E728:')
call assert_fails("let l = filter({'k' : 10}, '{}')", 'E728:')
call assert_fails("let l = filter([1, 2], {})", 'E731:')
@@ -108,6 +173,8 @@ func Test_map_filter_fails()
" Nvim doesn't have null partials
" call assert_equal([1, 2, 3], filter([1, 2, 3], test_null_partial()))
call assert_fails("let l = filter([1, 2], {a, b, c -> 1})", 'E119:')
+ call assert_fails('call foreach([1], "xyzzy")', 'E492:')
+ call assert_fails('call foreach([1], "let a = foo")', 'E121:')
endfunc
func Test_map_and_modify()
@@ -125,7 +192,7 @@ endfunc
func Test_filter_and_modify()
let l = [0]
- " cannot change the list halfway a map()
+ " cannot change the list halfway thru filter()
call assert_fails('call filter(l, "remove(l, 0)")', 'E741:')
let d = #{a: 0, b: 0, c: 0}
@@ -135,6 +202,18 @@ func Test_filter_and_modify()
call assert_fails('call filter(b, "remove(b, 0)")', 'E741:')
endfunc
+func Test_foreach_and_modify()
+ let l = [0]
+ " cannot change the list halfway thru foreach()
+ call assert_fails('call foreach(l, "let a = remove(l, 0)")', 'E741:')
+
+ let d = #{a: 0, b: 0, c: 0}
+ call assert_fails('call foreach(d, "let a = remove(d, v:key)")', 'E741:')
+
+ let b = 0z1234
+ call assert_fails('call foreach(b, "let a = remove(b, 0)")', 'E741:')
+endfunc
+
func Test_mapnew_dict()
let din = #{one: 1, two: 2}
let dout = mapnew(din, {k, v -> string(v)})
@@ -162,6 +241,36 @@ func Test_mapnew_blob()
call assert_equal(0z129956, bout)
endfunc
+func Test_foreach_blob()
+ let lines =<< trim END
+ LET g:test_variable = []
+ call assert_equal(0z0001020304, foreach(0z0001020304, 'call add(g:test_variable, v:val)'))
+ call assert_equal([0, 1, 2, 3, 4], g:test_variable)
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ func! s:foreach1(index, val) abort
+ call add(g:test_variable, a:val)
+ return [ 11, 12, 13, 14 ]
+ endfunc
+ let g:test_variable = []
+ call assert_equal(0z0001020304, foreach(0z0001020304, function('s:foreach1')))
+ call assert_equal([0, 1, 2, 3, 4], g:test_variable)
+
+ let lines =<< trim END
+ def Foreach1(_, val: any): list<number>
+ add(g:test_variable, val)
+ return [ 11, 12, 13, 14 ]
+ enddef
+ g:test_variable = []
+ assert_equal(0z0001020304, foreach(0z0001020304, Foreach1))
+ assert_equal([0, 1, 2, 3, 4], g:test_variable)
+ END
+ call CheckDefSuccess(lines)
+
+ call remove(g:, 'test_variable')
+endfunc
+
" Test for using map(), filter() and mapnew() with a string
func Test_filter_map_string()
" filter()
@@ -221,6 +330,37 @@ func Test_filter_map_string()
END
call CheckLegacyAndVim9Success(lines)
+ " foreach()
+ let lines =<< trim END
+ VAR s = "abc"
+ LET g:test_variable = []
+ call assert_equal(s, foreach(s, 'call add(g:test_variable, v:val)'))
+ call assert_equal(['a', 'b', 'c'], g:test_variable)
+ LET g:test_variable = []
+ LET s = 'あiうえお'
+ call assert_equal(s, foreach(s, 'call add(g:test_variable, v:val)'))
+ call assert_equal(['あ', 'i', 'う', 'え', 'お'], g:test_variable)
+ END
+ call CheckLegacyAndVim9Success(lines)
+ func! s:foreach1(index, val) abort
+ call add(g:test_variable, a:val)
+ return [ 11, 12, 13, 14 ]
+ endfunc
+ let g:test_variable = []
+ call assert_equal('abcd', foreach('abcd', function('s:foreach1')))
+ call assert_equal(['a', 'b', 'c', 'd'], g:test_variable)
+ let lines =<< trim END
+ def Foreach1(_, val: string): list<number>
+ add(g:test_variable, val)
+ return [ 11, 12, 13, 14 ]
+ enddef
+ g:test_variable = []
+ assert_equal('abcd', foreach('abcd', Foreach1))
+ assert_equal(['a', 'b', 'c', 'd'], g:test_variable)
+ END
+ call CheckDefSuccess(lines)
+ call remove(g:, 'test_variable')
+
let lines =<< trim END
#" map() and filter()
call assert_equal('[あ][⁈][a][😊][⁉][💕][💕][b][💕]', map(filter('あx⁈ax😊x⁉💕💕b💕x', '"x" != v:val'), '"[" .. v:val .. "]"'))
diff --git a/test/old/testdir/vim9.vim b/test/old/testdir/vim9.vim
index f8a90c1e97..218fab6c5e 100644
--- a/test/old/testdir/vim9.vim
+++ b/test/old/testdir/vim9.vim
@@ -2,6 +2,10 @@
" Use a different file name for each run.
let s:sequence = 1
+func CheckDefSuccess(lines)
+ return
+endfunc
+
func CheckDefFailure(lines, error, lnum = -3)
return
endfunc