• R/O
  • SSH

vim: Commit

Mirror of the Vim source from https://github.com/vim/vim


Commit MetaInfo

Revisionda670b1549b322056cd0715909b930f7ad55b6e9 (tree)
Time2024-01-13 20:00:06
AuthorChristian Brabandt <cb@256b...>
CommiterChristian Brabandt

Log Message

patch 9.1.0027: Vim is missing a foreach() func

Commit: https://github.com/vim/vim/commit/e79e2077607e8f829ba823308c91104a795736ba
Author: Ernie Rael <errael@raelity.com>
Date: Sat Jan 13 11:47:33 2024 +0100

patch 9.1.0027: Vim is missing a foreach() func
Problem: Vim is missing a foreach() func
Solution: Implement foreach({expr1}, {expr2}) function,
which applies {expr2} for each item in {expr1}
without changing it (Ernie Rael)
closes: #12166
Signed-off-by: Ernie Rael <errael@raelity.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>

Change Summary

Incremental Difference

diff -r 93171f4925c5 -r da670b1549b3 runtime/doc/builtin.txt
--- a/runtime/doc/builtin.txt Fri Jan 12 18:15:06 2024 +0100
+++ b/runtime/doc/builtin.txt Sat Jan 13 12:00:06 2024 +0100
@@ -1,4 +1,4 @@
1-*builtin.txt* For Vim version 9.1. Last change: 2024 Jan 05
1+*builtin.txt* For Vim version 9.1. Last change: 2024 Jan 13
22
33
44 VIM REFERENCE MANUAL by Bram Moolenaar
@@ -198,6 +198,8 @@
198198 foldlevel({lnum}) Number fold level at {lnum}
199199 foldtext() String line displayed for closed fold
200200 foldtextresult({lnum}) String text for closed fold at {lnum}
201+foreach({expr1}, {expr2}) List/Dict/Blob/String
202+ for each item in {expr1} call {expr2}
201203 foreground() Number bring the Vim window to the foreground
202204 fullcommand({name} [, {vim9}]) String get full command from {name}
203205 funcref({name} [, {arglist}] [, {dict}])
@@ -2995,6 +2997,45 @@
29952997
29962998 Can also be used as a |method|: >
29972999 GetLnum()->foldtextresult()
3000+
3001+foreach({expr1}, {expr2}) *foreach()*
3002+ {expr1} must be a |List|, |String|, |Blob| or |Dictionary|.
3003+ For each item in {expr1} execute {expr2}. {expr1} is not
3004+ modified; its values may be, as with |:lockvar| 1. *E741*
3005+ See |map()| and |filter()| to modify {expr1}.
3006+
3007+ {expr2} must be a |string| or |Funcref|.
3008+
3009+ If {expr2} is a |string|, inside {expr2} |v:val| has the value
3010+ of the current item. For a |Dictionary| |v:key| has the key
3011+ of the current item and for a |List| |v:key| has the index of
3012+ the current item. For a |Blob| |v:key| has the index of the
3013+ current byte. For a |String| |v:key| has the index of the
3014+ current character.
3015+ Examples: >
3016+ call foreach(mylist, 'used[v:val] = true')
3017+< This records the items that are in the {expr1} list.
3018+
3019+ Note that {expr2} is the result of expression and is then used
3020+ as a command. Often it is good to use a |literal-string| to
3021+ avoid having to double backslashes.
3022+
3023+ If {expr2} is a |Funcref| it must take two arguments:
3024+ 1. the key or the index of the current item.
3025+ 2. the value of the current item.
3026+ With a legacy script lambda you don't get an error if it only
3027+ accepts one argument, but with a Vim9 lambda you get "E1106:
3028+ One argument too many", the number of arguments must match.
3029+ If the function returns a value, it is ignored.
3030+
3031+ Returns {expr1} in all cases.
3032+ When an error is encountered while executing {expr2} no
3033+ further items in {expr1} are processed.
3034+ When {expr2} is a Funcref errors inside a function are ignored,
3035+ unless it was defined with the "abort" flag.
3036+
3037+ Can also be used as a |method|: >
3038+ mylist->foreach(expr2)
29983039 <
29993040 *foreground()*
30003041 foreground() Move the Vim window to the foreground. Useful when sent from
diff -r 93171f4925c5 -r da670b1549b3 runtime/doc/tags
--- a/runtime/doc/tags Fri Jan 12 18:15:06 2024 +0100
+++ b/runtime/doc/tags Sat Jan 13 12:00:06 2024 +0100
@@ -5167,6 +5167,7 @@
51675167 E739 builtin.txt /*E739*
51685168 E74 message.txt /*E74*
51695169 E740 userfunc.txt /*E740*
5170+E741 builtin.txt /*E741*
51705171 E741 eval.txt /*E741*
51715172 E742 userfunc.txt /*E742*
51725173 E743 eval.txt /*E743*
@@ -7148,6 +7149,7 @@
71487149 font-sizes gui_x11.txt /*font-sizes*
71497150 fontset mbyte.txt /*fontset*
71507151 forced-motion motion.txt /*forced-motion*
7152+foreach() builtin.txt /*foreach()*
71517153 foreground() builtin.txt /*foreground()*
71527154 fork os_unix.txt /*fork*
71537155 form.vim syntax.txt /*form.vim*
diff -r 93171f4925c5 -r da670b1549b3 runtime/doc/usr_41.txt
--- a/runtime/doc/usr_41.txt Fri Jan 12 18:15:06 2024 +0100
+++ b/runtime/doc/usr_41.txt Sat Jan 13 12:00:06 2024 +0100
@@ -1,4 +1,4 @@
1-*usr_41.txt* For Vim version 9.1. Last change: 2023 May 06
1+*usr_41.txt* For Vim version 9.1. Last change: 2024 Jan 13
22
33 VIM USER MANUAL - by Bram Moolenaar
44
@@ -798,6 +798,7 @@
798798 filter() remove selected items from a List
799799 map() change each List item
800800 mapnew() make a new List with changed items
801+ foreach() apply function to List items
801802 reduce() reduce a List to a value
802803 slice() take a slice of a List
803804 sort() sort a List
@@ -829,6 +830,7 @@
829830 filter() remove selected entries from a Dictionary
830831 map() change each Dictionary entry
831832 mapnew() make a new Dictionary with changed items
833+ foreach() apply function to Dictionary items
832834 keys() get List of Dictionary keys
833835 values() get List of Dictionary values
834836 items() get List of Dictionary key-value pairs
diff -r 93171f4925c5 -r da670b1549b3 src/blob.c
--- a/src/blob.c Fri Jan 12 18:15:06 2024 +0100
+++ b/src/blob.c Sat Jan 13 12:00:06 2024 +0100
@@ -641,25 +641,28 @@
641641 if (filter_map_one(&tv, expr, filtermap, fc, &newtv, &rem) == FAIL
642642 || did_emsg)
643643 break;
644- if (newtv.v_type != VAR_NUMBER && newtv.v_type != VAR_BOOL)
645- {
646- clear_tv(&newtv);
647- emsg(_(e_invalid_operation_for_blob));
648- break;
649- }
650- if (filtermap != FILTERMAP_FILTER)
644+ if (filtermap != FILTERMAP_FOREACH)
651645 {
652- if (newtv.vval.v_number != val)
653- blob_set(b_ret, i, newtv.vval.v_number);
654- }
655- else if (rem)
656- {
657- char_u *p = (char_u *)blob_arg->bv_ga.ga_data;
646+ if (newtv.v_type != VAR_NUMBER && newtv.v_type != VAR_BOOL)
647+ {
648+ clear_tv(&newtv);
649+ emsg(_(e_invalid_operation_for_blob));
650+ break;
651+ }
652+ if (filtermap != FILTERMAP_FILTER)
653+ {
654+ if (newtv.vval.v_number != val)
655+ blob_set(b_ret, i, newtv.vval.v_number);
656+ }
657+ else if (rem)
658+ {
659+ char_u *p = (char_u *)blob_arg->bv_ga.ga_data;
658660
659- mch_memmove(p + i, p + i + 1,
660- (size_t)b->bv_ga.ga_len - i - 1);
661- --b->bv_ga.ga_len;
662- --i;
661+ mch_memmove(p + i, p + i + 1,
662+ (size_t)b->bv_ga.ga_len - i - 1);
663+ --b->bv_ga.ga_len;
664+ --i;
665+ }
663666 }
664667 ++idx;
665668 }
diff -r 93171f4925c5 -r da670b1549b3 src/dict.c
--- a/src/dict.c Fri Jan 12 18:15:06 2024 +0100
+++ b/src/dict.c Sat Jan 13 12:00:06 2024 +0100
@@ -1329,8 +1329,8 @@
13291329 }
13301330
13311331 /*
1332- * Implementation of map() and filter() for a Dict. Apply "expr" to every
1333- * item in Dict "d" and return the result in "rettv".
1332+ * Implementation of map(), filter(), foreach() for a Dict. Apply "expr" to
1333+ * every item in Dict "d" and return the result in "rettv".
13341334 */
13351335 void
13361336 dict_filter_map(
@@ -1392,7 +1392,6 @@
13921392 arg_errmsg, TRUE)))
13931393 break;
13941394 set_vim_var_string(VV_KEY, di->di_key, -1);
1395- newtv.v_type = VAR_UNKNOWN;
13961395 r = filter_map_one(&di->di_tv, expr, filtermap, fc, &newtv, &rem);
13971396 clear_tv(get_vim_var_tv(VV_KEY));
13981397 if (r == FAIL || did_emsg)
diff -r 93171f4925c5 -r da670b1549b3 src/evalfunc.c
--- a/src/evalfunc.c Fri Jan 12 18:15:06 2024 +0100
+++ b/src/evalfunc.c Sat Jan 13 12:00:06 2024 +0100
@@ -607,10 +607,11 @@
607607 }
608608
609609 /*
610- * Check second argument of map() or filter().
610+ * Check second argument of map(), filter(), foreach().
611611 */
612612 static int
613-check_map_filter_arg2(type_T *type, argcontext_T *context, int is_map)
613+check_map_filter_arg2(type_T *type, argcontext_T *context,
614+ filtermap_T filtermap)
614615 {
615616 type_T *expected_member = NULL;
616617 type_T *(args[2]);
@@ -663,12 +664,14 @@
663664 {
664665 where_T where = WHERE_INIT;
665666
666- if (is_map)
667+ if (filtermap == FILTERMAP_MAP)
667668 t_func_exp.tt_member = expected_member == NULL
668669 || type_any_or_unknown(type->tt_member)
669670 ? &t_any : expected_member;
670- else
671+ else if (filtermap == FILTERMAP_FILTER)
671672 t_func_exp.tt_member = &t_bool;
673+ else // filtermap == FILTERMAP_FOREACH
674+ t_func_exp.tt_member = &t_unknown;
672675 if (args[0] == NULL)
673676 args[0] = &t_unknown;
674677 if (type->tt_argcount == -1)
@@ -693,7 +696,7 @@
693696 return OK;
694697
695698 if (type->tt_type == VAR_FUNC)
696- return check_map_filter_arg2(type, context, FALSE);
699+ return check_map_filter_arg2(type, context, FILTERMAP_FILTER);
697700 semsg(_(e_string_or_function_required_for_argument_nr), 2);
698701 return FAIL;
699702 }
@@ -710,7 +713,24 @@
710713 return OK;
711714
712715 if (type->tt_type == VAR_FUNC)
713- return check_map_filter_arg2(type, context, TRUE);
716+ return check_map_filter_arg2(type, context, FILTERMAP_MAP);
717+ semsg(_(e_string_or_function_required_for_argument_nr), 2);
718+ return FAIL;
719+}
720+
721+/*
722+ * Check second argument of foreach(), the function.
723+ */
724+ static int
725+arg_foreach_func(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
726+{
727+ if (type->tt_type == VAR_STRING
728+ || type->tt_type == VAR_PARTIAL
729+ || type_any_or_unknown(type))
730+ return OK;
731+
732+ if (type->tt_type == VAR_FUNC)
733+ return check_map_filter_arg2(type, context, FILTERMAP_FOREACH);
714734 semsg(_(e_string_or_function_required_for_argument_nr), 2);
715735 return FAIL;
716736 }
@@ -1173,6 +1193,7 @@
11731193 static argcheck_T arg3_libcall[] = {arg_string, arg_string, arg_string_or_nr};
11741194 static argcheck_T arg14_maparg[] = {arg_string, arg_string, arg_bool, arg_bool};
11751195 static argcheck_T arg2_filter[] = {arg_list_or_dict_or_blob_or_string_mod, arg_filter_func};
1196+static argcheck_T arg2_foreach[] = {arg_list_or_dict_or_blob_or_string, arg_foreach_func};
11761197 static argcheck_T arg2_instanceof[] = {arg_object, varargs_class, NULL };
11771198 static argcheck_T arg2_map[] = {arg_list_or_dict_or_blob_or_string_mod, arg_map_func};
11781199 static argcheck_T arg2_mapnew[] = {arg_list_or_dict_or_blob_or_string, arg_any};
@@ -2013,6 +2034,8 @@
20132034 ret_string, f_foldtext},
20142035 {"foldtextresult", 1, 1, FEARG_1, arg1_lnum,
20152036 ret_string, f_foldtextresult},
2037+ {"foreach", 2, 2, FEARG_1, arg2_foreach,
2038+ ret_first_arg, f_foreach},
20162039 {"foreground", 0, 0, 0, NULL,
20172040 ret_void, f_foreground},
20182041 {"fullcommand", 1, 2, FEARG_1, arg2_string_bool,
diff -r 93171f4925c5 -r da670b1549b3 src/list.c
--- a/src/list.c Fri Jan 12 18:15:06 2024 +0100
+++ b/src/list.c Sat Jan 13 12:00:06 2024 +0100
@@ -2325,7 +2325,7 @@
23252325 }
23262326
23272327 /*
2328- * Handle one item for map() and filter().
2328+ * Handle one item for map(), filter(), foreach().
23292329 * Sets v:val to "tv". Caller must set v:key.
23302330 */
23312331 int
@@ -2341,6 +2341,17 @@
23412341 int retval = FAIL;
23422342
23432343 copy_tv(tv, get_vim_var_tv(VV_VAL));
2344+
2345+ newtv->v_type = VAR_UNKNOWN;
2346+ if (filtermap == FILTERMAP_FOREACH && expr->v_type == VAR_STRING)
2347+ {
2348+ // foreach() is not limited to an expression
2349+ do_cmdline_cmd(expr->vval.v_string);
2350+ if (!did_emsg)
2351+ retval = OK;
2352+ goto theend;
2353+ }
2354+
23442355 argv[0] = *get_vim_var_tv(VV_KEY);
23452356 argv[1] = *get_vim_var_tv(VV_VAL);
23462357 if (eval_expr_typval(expr, FALSE, argv, 2, fc, newtv) == FAIL)
@@ -2360,6 +2371,8 @@
23602371 if (error)
23612372 goto theend;
23622373 }
2374+ else if (filtermap == FILTERMAP_FOREACH)
2375+ clear_tv(newtv);
23632376 retval = OK;
23642377 theend:
23652378 clear_tv(get_vim_var_tv(VV_VAL));
@@ -2367,8 +2380,8 @@
23672380 }
23682381
23692382 /*
2370- * Implementation of map() and filter() for a List. Apply "expr" to every item
2371- * in List "l" and return the result in "rettv".
2383+ * Implementation of map(), filter(), foreach() for a List. Apply "expr" to
2384+ * every item in List "l" and return the result in "rettv".
23722385 */
23732386 static void
23742387 list_filter_map(
@@ -2421,7 +2434,8 @@
24212434 int stride = l->lv_u.nonmat.lv_stride;
24222435
24232436 // List from range(): loop over the numbers
2424- if (filtermap != FILTERMAP_MAPNEW)
2437+ // NOTE: foreach() returns the range_list_item
2438+ if (filtermap != FILTERMAP_MAPNEW && filtermap != FILTERMAP_FOREACH)
24252439 {
24262440 l->lv_first = NULL;
24272441 l->lv_u.mat.lv_last = NULL;
@@ -2444,27 +2458,30 @@
24442458 clear_tv(&newtv);
24452459 break;
24462460 }
2447- if (filtermap != FILTERMAP_FILTER)
2461+ if (filtermap != FILTERMAP_FOREACH)
24482462 {
2449- if (filtermap == FILTERMAP_MAP && argtype != NULL
2463+ if (filtermap != FILTERMAP_FILTER)
2464+ {
2465+ if (filtermap == FILTERMAP_MAP && argtype != NULL
24502466 && check_typval_arg_type(
2451- argtype->tt_member, &newtv,
2452- func_name, 0) == FAIL)
2453- {
2454- clear_tv(&newtv);
2455- break;
2467+ argtype->tt_member, &newtv,
2468+ func_name, 0) == FAIL)
2469+ {
2470+ clear_tv(&newtv);
2471+ break;
2472+ }
2473+ // map(), mapnew(): always append the new value to the
2474+ // list
2475+ if (list_append_tv_move(filtermap == FILTERMAP_MAP
2476+ ? l : l_ret, &newtv) == FAIL)
2477+ break;
24562478 }
2457- // map(), mapnew(): always append the new value to the
2458- // list
2459- if (list_append_tv_move(filtermap == FILTERMAP_MAP
2460- ? l : l_ret, &newtv) == FAIL)
2461- break;
2462- }
2463- else if (!rem)
2464- {
2465- // filter(): append the list item value when not rem
2466- if (list_append_tv_move(l, &tv) == FAIL)
2467- break;
2479+ else if (!rem)
2480+ {
2481+ // filter(): append the list item value when not rem
2482+ if (list_append_tv_move(l, &tv) == FAIL)
2483+ break;
2484+ }
24682485 }
24692486
24702487 val += stride;
@@ -2508,7 +2525,7 @@
25082525 break;
25092526 }
25102527 else if (filtermap == FILTERMAP_FILTER && rem)
2511- listitem_remove(l, li);
2528+ listitem_remove(l, li);
25122529 ++idx;
25132530 }
25142531 }
@@ -2519,7 +2536,7 @@
25192536 }
25202537
25212538 /*
2522- * Implementation of map() and filter().
2539+ * Implementation of map(), filter() and foreach().
25232540 */
25242541 static void
25252542 filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
@@ -2527,16 +2544,19 @@
25272544 typval_T *expr;
25282545 char *func_name = filtermap == FILTERMAP_MAP ? "map()"
25292546 : filtermap == FILTERMAP_MAPNEW ? "mapnew()"
2530- : "filter()";
2547+ : filtermap == FILTERMAP_FILTER ? "filter()"
2548+ : "foreach()";
25312549 char_u *arg_errmsg = (char_u *)(filtermap == FILTERMAP_MAP
25322550 ? N_("map() argument")
25332551 : filtermap == FILTERMAP_MAPNEW
25342552 ? N_("mapnew() argument")
2535- : N_("filter() argument"));
2553+ : filtermap == FILTERMAP_FILTER
2554+ ? N_("filter() argument")
2555+ : N_("foreach() argument"));
25362556 int save_did_emsg;
25372557 type_T *type = NULL;
25382558
2539- // map() and filter() return the first argument, also on failure.
2559+ // map(), filter(), foreach() return the first argument, also on failure.
25402560 if (filtermap != FILTERMAP_MAPNEW && argvars[0].v_type != VAR_STRING)
25412561 copy_tv(&argvars[0], rettv);
25422562
@@ -2630,6 +2650,15 @@
26302650 }
26312651
26322652 /*
2653+ * "foreach()" function
2654+ */
2655+ void
2656+f_foreach(typval_T *argvars, typval_T *rettv)
2657+{
2658+ filter_map(argvars, rettv, FILTERMAP_FOREACH);
2659+}
2660+
2661+/*
26332662 * "add(list, item)" function
26342663 */
26352664 static void
diff -r 93171f4925c5 -r da670b1549b3 src/proto/list.pro
--- a/src/proto/list.pro Fri Jan 12 18:15:06 2024 +0100
+++ b/src/proto/list.pro Sat Jan 13 12:00:06 2024 +0100
@@ -56,6 +56,7 @@
5656 void f_filter(typval_T *argvars, typval_T *rettv);
5757 void f_map(typval_T *argvars, typval_T *rettv);
5858 void f_mapnew(typval_T *argvars, typval_T *rettv);
59+void f_foreach(typval_T *argvars, typval_T *rettv);
5960 void f_add(typval_T *argvars, typval_T *rettv);
6061 void f_count(typval_T *argvars, typval_T *rettv);
6162 void f_extend(typval_T *argvars, typval_T *rettv);
diff -r 93171f4925c5 -r da670b1549b3 src/strings.c
--- a/src/strings.c Fri Jan 12 18:15:06 2024 +0100
+++ b/src/strings.c Sat Jan 13 12:00:06 2024 +0100
@@ -942,7 +942,6 @@
942942 break;
943943 len = (int)STRLEN(tv.vval.v_string);
944944
945- newtv.v_type = VAR_UNKNOWN;
946945 set_vim_var_nr(VV_KEY, idx);
947946 if (filter_map_one(&tv, expr, filtermap, fc, &newtv, &rem) == FAIL
948947 || did_emsg)
@@ -951,7 +950,7 @@
951950 clear_tv(&tv);
952951 break;
953952 }
954- else if (filtermap != FILTERMAP_FILTER)
953+ if (filtermap == FILTERMAP_MAP || filtermap == FILTERMAP_MAPNEW)
955954 {
956955 if (newtv.v_type != VAR_STRING)
957956 {
@@ -963,7 +962,7 @@
963962 else
964963 ga_concat(&ga, newtv.vval.v_string);
965964 }
966- else if (!rem)
965+ else if (filtermap == FILTERMAP_FOREACH || !rem)
967966 ga_concat(&ga, tv.vval.v_string);
968967
969968 clear_tv(&newtv);
diff -r 93171f4925c5 -r da670b1549b3 src/structs.h
--- a/src/structs.h Fri Jan 12 18:15:06 2024 +0100
+++ b/src/structs.h Sat Jan 13 12:00:06 2024 +0100
@@ -4879,11 +4879,12 @@
48794879 hashtab_T sve_hashtab;
48804880 } save_v_event_T;
48814881
4882-// Enum used by filter(), map() and mapnew()
4882+// Enum used by filter(), map(), mapnew() and foreach()
48834883 typedef enum {
48844884 FILTERMAP_FILTER,
48854885 FILTERMAP_MAP,
4886- FILTERMAP_MAPNEW
4886+ FILTERMAP_MAPNEW,
4887+ FILTERMAP_FOREACH
48874888 } filtermap_T;
48884889
48894890 // Structure used by switch_win() to pass values to restore_win()
diff -r 93171f4925c5 -r da670b1549b3 src/testdir/test_filter_map.vim
--- a/src/testdir/test_filter_map.vim Fri Jan 12 18:15:06 2024 +0100
+++ b/src/testdir/test_filter_map.vim Sat Jan 13 12:00:06 2024 +0100
@@ -14,6 +14,18 @@
1414 call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], 'v:key * 2'))
1515 call assert_equal([9, 9, 9, 9], map([1, 2, 3, 4], 9))
1616 call assert_equal([7, 7, 7], map([1, 2, 3], ' 7 '))
17+
18+ " foreach()
19+ let list01 = [1, 2, 3, 4]
20+ let list02 = []
21+ call assert_equal([1, 2, 3, 4], foreach(list01, 'call add(list02, v:val * 2)'))
22+ call assert_equal([2, 4, 6, 8], list02)
23+ let list02 = []
24+ call assert_equal([1, 2, 3, 4], foreach(list01, 'call add(list02, v:key * 2)'))
25+ call assert_equal([0, 2, 4, 6], list02)
26+ let list02 = []
27+ call assert_equal([1, 2, 3, 4], foreach(list01, 'call add(list02, 9)'))
28+ call assert_equal([9, 9, 9, 9], list02)
1729 endfunc
1830
1931 " dict with expression string
@@ -29,6 +41,14 @@
2941 call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), 'v:val * 2'))
3042 call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), 'v:key[0]'))
3143 call assert_equal({"foo": 9, "bar": 9, "baz": 9}, map(copy(dict), 9))
44+
45+ " foreach()
46+ let dict01 = {}
47+ call assert_equal(dict, foreach(copy(dict), 'let dict01[v:key] = v:val * 2'))
48+ call assert_equal({"foo": 2, "bar": 4, "baz": 6}, dict01)
49+ let dict01 = {}
50+ call assert_equal(dict, foreach(copy(dict), 'let dict01[v:key] = v:key[0]'))
51+ call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, dict01)
3252 endfunc
3353
3454 " list with funcref
@@ -54,6 +74,16 @@
5474 return a:index * 2
5575 endfunc
5676 call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], function('s:filter4')))
77+
78+ " foreach()
79+ func! s:foreach1(index, val) abort
80+ call add(g:test_variable, a:val + 1)
81+ return [ 11, 12, 13, 14 ]
82+ endfunc
83+ let g:test_variable = []
84+ call assert_equal([0, 1, 2, 3, 4], foreach(range(5), function('s:foreach1')))
85+ call assert_equal([1, 2, 3, 4, 5], g:test_variable)
86+ call remove(g:, 'test_variable')
5787 endfunc
5888
5989 func Test_filter_map_nested()
@@ -90,11 +120,46 @@
90120 return a:key[0]
91121 endfunc
92122 call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), function('s:filter4')))
123+
124+ " foreach()
125+ func! s:foreach1(key, val) abort
126+ call extend(g:test_variable, {a:key: a:val * 2})
127+ return [ 11, 12, 13, 14 ]
128+ endfunc
129+ let g:test_variable = {}
130+ call assert_equal(dict, foreach(copy(dict), function('s:foreach1')))
131+ call assert_equal({"foo": 2, "bar": 4, "baz": 6}, g:test_variable)
132+ call remove(g:, 'test_variable')
133+endfunc
134+
135+func Test_map_filter_locked()
136+ let list01 = [1, 2, 3, 4]
137+ lockvar 1 list01
138+ call assert_fails('call filter(list01, "v:val > 1")', 'E741:')
139+ call assert_equal([2, 4, 6, 8], map(list01, 'v:val * 2'))
140+ call assert_equal([1, 2, 3, 4], map(list01, 'v:val / 2'))
141+ call assert_equal([2, 4, 6, 8], mapnew(list01, 'v:val * 2'))
142+ let g:test_variable = []
143+ call assert_equal([1, 2, 3, 4], foreach(list01, 'call add(g:test_variable, v:val * 2)'))
144+ call remove(g:, 'test_variable')
145+ call assert_fails('call filter(list01, "v:val > 1")', 'E741:')
146+ unlockvar 1 list01
147+ lockvar! list01
148+ call assert_fails('call filter(list01, "v:val > 1")', 'E741:')
149+ call assert_fails('call map(list01, "v:val * 2")', 'E741:')
150+ call assert_equal([2, 4, 6, 8], mapnew(list01, 'v:val * 2'))
151+ let g:test_variable = []
152+ call assert_equal([1, 2, 3, 4], foreach(list01, 'call add(g:test_variable, v:val * 2)'))
153+ call assert_fails('call foreach(list01, "let list01[0] = -1")', 'E741:')
154+ call assert_fails('call filter(list01, "v:val > 1")', 'E741:')
155+ call remove(g:, 'test_variable')
156+ unlockvar! list01
93157 endfunc
94158
95159 func Test_map_filter_fails()
96160 call assert_fails('call map([1], "42 +")', 'E15:')
97161 call assert_fails('call filter([1], "42 +")', 'E15:')
162+ call assert_fails('call foreach([1], "let a = }")', 'E15:')
98163 call assert_fails("let l = filter([1, 2, 3], '{}')", 'E728:')
99164 call assert_fails("let l = filter({'k' : 10}, '{}')", 'E728:')
100165 call assert_fails("let l = filter([1, 2], {})", 'E731:')
@@ -106,6 +171,8 @@
106171 call assert_fails("let l = filter([1, 2], function('min'))", 'E118:')
107172 call assert_equal([1, 2, 3], filter([1, 2, 3], test_null_partial()))
108173 call assert_fails("let l = filter([1, 2], {a, b, c -> 1})", 'E119:')
174+ call assert_fails('call foreach([1], "xyzzy")', 'E492:')
175+ call assert_fails('call foreach([1], "let a = foo")', 'E121:')
109176 endfunc
110177
111178 func Test_map_and_modify()
@@ -123,7 +190,7 @@
123190
124191 func Test_filter_and_modify()
125192 let l = [0]
126- " cannot change the list halfway a map()
193+ " cannot change the list halfway thru filter()
127194 call assert_fails('call filter(l, "remove(l, 0)")', 'E741:')
128195
129196 let d = #{a: 0, b: 0, c: 0}
@@ -133,6 +200,18 @@
133200 call assert_fails('call filter(b, "remove(b, 0)")', 'E741:')
134201 endfunc
135202
203+func Test_foreach_and_modify()
204+ let l = [0]
205+ " cannot change the list halfway thru foreach()
206+ call assert_fails('call foreach(l, "let a = remove(l, 0)")', 'E741:')
207+
208+ let d = #{a: 0, b: 0, c: 0}
209+ call assert_fails('call foreach(d, "let a = remove(d, v:key)")', 'E741:')
210+
211+ let b = 0z1234
212+ call assert_fails('call foreach(b, "let a = remove(b, 0)")', 'E741:')
213+endfunc
214+
136215 func Test_mapnew_dict()
137216 let din = #{one: 1, two: 2}
138217 let dout = mapnew(din, {k, v -> string(v)})
@@ -160,6 +239,36 @@
160239 call assert_equal(0z129956, bout)
161240 endfunc
162241
242+func Test_foreach_blob()
243+ let lines =<< trim END
244+ LET g:test_variable = []
245+ call assert_equal(0z0001020304, foreach(0z0001020304, 'call add(g:test_variable, v:val)'))
246+ call assert_equal([0, 1, 2, 3, 4], g:test_variable)
247+ END
248+ call v9.CheckLegacyAndVim9Success(lines)
249+
250+ func! s:foreach1(index, val) abort
251+ call add(g:test_variable, a:val)
252+ return [ 11, 12, 13, 14 ]
253+ endfunc
254+ let g:test_variable = []
255+ call assert_equal(0z0001020304, foreach(0z0001020304, function('s:foreach1')))
256+ call assert_equal([0, 1, 2, 3, 4], g:test_variable)
257+
258+ let lines =<< trim END
259+ def Foreach1(_, val: any): list<number>
260+ add(g:test_variable, val)
261+ return [ 11, 12, 13, 14 ]
262+ enddef
263+ g:test_variable = []
264+ assert_equal(0z0001020304, foreach(0z0001020304, Foreach1))
265+ assert_equal([0, 1, 2, 3, 4], g:test_variable)
266+ END
267+ call v9.CheckDefSuccess(lines)
268+
269+ call remove(g:, 'test_variable')
270+endfunc
271+
163272 " Test for using map(), filter() and mapnew() with a string
164273 func Test_filter_map_string()
165274 " filter()
@@ -219,6 +328,37 @@
219328 END
220329 call v9.CheckLegacyAndVim9Success(lines)
221330
331+ " foreach()
332+ let lines =<< trim END
333+ VAR s = "abc"
334+ LET g:test_variable = []
335+ call assert_equal(s, foreach(s, 'call add(g:test_variable, v:val)'))
336+ call assert_equal(['a', 'b', 'c'], g:test_variable)
337+ LET g:test_variable = []
338+ LET s = 'あiうえお'
339+ call assert_equal(s, foreach(s, 'call add(g:test_variable, v:val)'))
340+ call assert_equal(['あ', 'i', 'う', 'え', 'お'], g:test_variable)
341+ END
342+ call v9.CheckLegacyAndVim9Success(lines)
343+ func! s:foreach1(index, val) abort
344+ call add(g:test_variable, a:val)
345+ return [ 11, 12, 13, 14 ]
346+ endfunc
347+ let g:test_variable = []
348+ call assert_equal('abcd', foreach('abcd', function('s:foreach1')))
349+ call assert_equal(['a', 'b', 'c', 'd'], g:test_variable)
350+ let lines =<< trim END
351+ def Foreach1(_, val: string): list<number>
352+ add(g:test_variable, val)
353+ return [ 11, 12, 13, 14 ]
354+ enddef
355+ g:test_variable = []
356+ assert_equal('abcd', foreach('abcd', Foreach1))
357+ assert_equal(['a', 'b', 'c', 'd'], g:test_variable)
358+ END
359+ call v9.CheckDefSuccess(lines)
360+ call remove(g:, 'test_variable')
361+
222362 let lines =<< trim END
223363 #" map() and filter()
224364 call assert_equal('[あ][⁈][a][😊][⁉][💕][💕][b][💕]', map(filter('あx⁈ax😊x⁉💕💕b💕x', '"x" != v:val'), '"[" .. v:val .. "]"'))
diff -r 93171f4925c5 -r da670b1549b3 src/version.c
--- a/src/version.c Fri Jan 12 18:15:06 2024 +0100
+++ b/src/version.c Sat Jan 13 12:00:06 2024 +0100
@@ -705,6 +705,8 @@
705705 static int included_patches[] =
706706 { /* Add new patch number below this line */
707707 /**/
708+ 27,
709+/**/
708710 26,
709711 /**/
710712 25,
Show on old repository browser