• R/O
• SSH

vim: Commit

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

Commit MetaInfo

Revision da670b1549b322056cd0715909b930f7ad55b6e9 (tree) 2024-01-13 20:00:06 Christian Brabandt Christian 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>

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 2 2 3 3 4 4 VIM REFERENCE MANUAL by Bram Moolenaar
 @@ -198,6 +198,8 @@ 198 198 foldlevel({lnum}) Number fold level at {lnum} 199 199 foldtext() String line displayed for closed fold 200 200 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} 201 203 foreground() Number bring the Vim window to the foreground 202 204 fullcommand({name} [, {vim9}]) String get full command from {name} 203 205 funcref({name} [, {arglist}] [, {dict}])
 @@ -2995,6 +2997,45 @@ 2995 2997 2996 2998 Can also be used as a |method|: > 2997 2999 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) 2998 3039 < 2999 3040 *foreground()* 3000 3041 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 @@ 5167 5167 E739 builtin.txt /*E739* 5168 5168 E74 message.txt /*E74* 5169 5169 E740 userfunc.txt /*E740* 5170 +E741 builtin.txt /*E741* 5170 5171 E741 eval.txt /*E741* 5171 5172 E742 userfunc.txt /*E742* 5172 5173 E743 eval.txt /*E743*
 @@ -7148,6 +7149,7 @@ 7148 7149 font-sizes gui_x11.txt /*font-sizes* 7149 7150 fontset mbyte.txt /*fontset* 7150 7151 forced-motion motion.txt /*forced-motion* 7152 +foreach() builtin.txt /*foreach()* 7151 7153 foreground() builtin.txt /*foreground()* 7152 7154 fork os_unix.txt /*fork* 7153 7155 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 2 2 3 3 VIM USER MANUAL - by Bram Moolenaar 4 4
 @@ -798,6 +798,7 @@ 798 798 filter() remove selected items from a List 799 799 map() change each List item 800 800 mapnew() make a new List with changed items 801 + foreach() apply function to List items 801 802 reduce() reduce a List to a value 802 803 slice() take a slice of a List 803 804 sort() sort a List
 @@ -829,6 +830,7 @@ 829 830 filter() remove selected entries from a Dictionary 830 831 map() change each Dictionary entry 831 832 mapnew() make a new Dictionary with changed items 833 + foreach() apply function to Dictionary items 832 834 keys() get List of Dictionary keys 833 835 values() get List of Dictionary values 834 836 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 @@ 641 641 if (filter_map_one(&tv, expr, filtermap, fc, &newtv, &rem) == FAIL 642 642 || did_emsg) 643 643 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) 651 645 { 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; 658 660 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 + } 663 666 } 664 667 ++idx; 665 668 }
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 @@ 1329 1329 } 1330 1330 1331 1331 /* 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". 1334 1334 */ 1335 1335 void 1336 1336 dict_filter_map(
 @@ -1392,7 +1392,6 @@ 1392 1392 arg_errmsg, TRUE))) 1393 1393 break; 1394 1394 set_vim_var_string(VV_KEY, di->di_key, -1); 1395 - newtv.v_type = VAR_UNKNOWN; 1396 1395 r = filter_map_one(&di->di_tv, expr, filtermap, fc, &newtv, &rem); 1397 1396 clear_tv(get_vim_var_tv(VV_KEY)); 1398 1397 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 @@ 607 607 } 608 608 609 609 /* 610 - * Check second argument of map() or filter(). 610 + * Check second argument of map(), filter(), foreach(). 611 611 */ 612 612 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) 614 615 { 615 616 type_T *expected_member = NULL; 616 617 type_T *(args[2]);
 @@ -663,12 +664,14 @@ 663 664 { 664 665 where_T where = WHERE_INIT; 665 666 666 - if (is_map) 667 + if (filtermap == FILTERMAP_MAP) 667 668 t_func_exp.tt_member = expected_member == NULL 668 669 || type_any_or_unknown(type->tt_member) 669 670 ? &t_any : expected_member; 670 - else 671 + else if (filtermap == FILTERMAP_FILTER) 671 672 t_func_exp.tt_member = &t_bool; 673 + else // filtermap == FILTERMAP_FOREACH 674 + t_func_exp.tt_member = &t_unknown; 672 675 if (args[0] == NULL) 673 676 args[0] = &t_unknown; 674 677 if (type->tt_argcount == -1)
 @@ -693,7 +696,7 @@ 693 696 return OK; 694 697 695 698 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); 697 700 semsg(_(e_string_or_function_required_for_argument_nr), 2); 698 701 return FAIL; 699 702 }
 @@ -710,7 +713,24 @@ 710 713 return OK; 711 714 712 715 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); 714 734 semsg(_(e_string_or_function_required_for_argument_nr), 2); 715 735 return FAIL; 716 736 }
 @@ -1173,6 +1193,7 @@ 1173 1193 static argcheck_T arg3_libcall[] = {arg_string, arg_string, arg_string_or_nr}; 1174 1194 static argcheck_T arg14_maparg[] = {arg_string, arg_string, arg_bool, arg_bool}; 1175 1195 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}; 1176 1197 static argcheck_T arg2_instanceof[] = {arg_object, varargs_class, NULL }; 1177 1198 static argcheck_T arg2_map[] = {arg_list_or_dict_or_blob_or_string_mod, arg_map_func}; 1178 1199 static argcheck_T arg2_mapnew[] = {arg_list_or_dict_or_blob_or_string, arg_any};
 @@ -2013,6 +2034,8 @@ 2013 2034 ret_string, f_foldtext}, 2014 2035 {"foldtextresult", 1, 1, FEARG_1, arg1_lnum, 2015 2036 ret_string, f_foldtextresult}, 2037 + {"foreach", 2, 2, FEARG_1, arg2_foreach, 2038 + ret_first_arg, f_foreach}, 2016 2039 {"foreground", 0, 0, 0, NULL, 2017 2040 ret_void, f_foreground}, 2018 2041 {"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 @@ 2325 2325 } 2326 2326 2327 2327 /* 2328 - * Handle one item for map() and filter(). 2328 + * Handle one item for map(), filter(), foreach(). 2329 2329 * Sets v:val to "tv". Caller must set v:key. 2330 2330 */ 2331 2331 int
 @@ -2341,6 +2341,17 @@ 2341 2341 int retval = FAIL; 2342 2342 2343 2343 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 + 2344 2355 argv[0] = *get_vim_var_tv(VV_KEY); 2345 2356 argv[1] = *get_vim_var_tv(VV_VAL); 2346 2357 if (eval_expr_typval(expr, FALSE, argv, 2, fc, newtv) == FAIL)
 @@ -2360,6 +2371,8 @@ 2360 2371 if (error) 2361 2372 goto theend; 2362 2373 } 2374 + else if (filtermap == FILTERMAP_FOREACH) 2375 + clear_tv(newtv); 2363 2376 retval = OK; 2364 2377 theend: 2365 2378 clear_tv(get_vim_var_tv(VV_VAL));
 @@ -2367,8 +2380,8 @@ 2367 2380 } 2368 2381 2369 2382 /* 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". 2372 2385 */ 2373 2386 static void 2374 2387 list_filter_map(
 @@ -2421,7 +2434,8 @@ 2421 2434 int stride = l->lv_u.nonmat.lv_stride; 2422 2435 2423 2436 // 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) 2425 2439 { 2426 2440 l->lv_first = NULL; 2427 2441 l->lv_u.mat.lv_last = NULL;
 @@ -2444,27 +2458,30 @@ 2444 2458 clear_tv(&newtv); 2445 2459 break; 2446 2460 } 2447 - if (filtermap != FILTERMAP_FILTER) 2461 + if (filtermap != FILTERMAP_FOREACH) 2448 2462 { 2449 - if (filtermap == FILTERMAP_MAP && argtype != NULL 2463 + if (filtermap != FILTERMAP_FILTER) 2464 + { 2465 + if (filtermap == FILTERMAP_MAP && argtype != NULL 2450 2466 && 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; 2456 2478 } 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 + } 2468 2485 } 2469 2486 2470 2487 val += stride;
 @@ -2508,7 +2525,7 @@ 2508 2525 break; 2509 2526 } 2510 2527 else if (filtermap == FILTERMAP_FILTER && rem) 2511 - listitem_remove(l, li); 2528 + listitem_remove(l, li); 2512 2529 ++idx; 2513 2530 } 2514 2531 }
 @@ -2519,7 +2536,7 @@ 2519 2536 } 2520 2537 2521 2538 /* 2522 - * Implementation of map() and filter(). 2539 + * Implementation of map(), filter() and foreach(). 2523 2540 */ 2524 2541 static void 2525 2542 filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
 @@ -2527,16 +2544,19 @@ 2527 2544 typval_T *expr; 2528 2545 char *func_name = filtermap == FILTERMAP_MAP ? "map()" 2529 2546 : filtermap == FILTERMAP_MAPNEW ? "mapnew()" 2530 - : "filter()"; 2547 + : filtermap == FILTERMAP_FILTER ? "filter()" 2548 + : "foreach()"; 2531 2549 char_u *arg_errmsg = (char_u *)(filtermap == FILTERMAP_MAP 2532 2550 ? N_("map() argument") 2533 2551 : filtermap == FILTERMAP_MAPNEW 2534 2552 ? N_("mapnew() argument") 2535 - : N_("filter() argument")); 2553 + : filtermap == FILTERMAP_FILTER 2554 + ? N_("filter() argument") 2555 + : N_("foreach() argument")); 2536 2556 int save_did_emsg; 2537 2557 type_T *type = NULL; 2538 2558 2539 - // map() and filter() return the first argument, also on failure. 2559 + // map(), filter(), foreach() return the first argument, also on failure. 2540 2560 if (filtermap != FILTERMAP_MAPNEW && argvars[0].v_type != VAR_STRING) 2541 2561 copy_tv(&argvars[0], rettv); 2542 2562
 @@ -2630,6 +2650,15 @@ 2630 2650 } 2631 2651 2632 2652 /* 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 +/* 2633 2662 * "add(list, item)" function 2634 2663 */ 2635 2664 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 @@ 56 56 void f_filter(typval_T *argvars, typval_T *rettv); 57 57 void f_map(typval_T *argvars, typval_T *rettv); 58 58 void f_mapnew(typval_T *argvars, typval_T *rettv); 59 +void f_foreach(typval_T *argvars, typval_T *rettv); 59 60 void f_add(typval_T *argvars, typval_T *rettv); 60 61 void f_count(typval_T *argvars, typval_T *rettv); 61 62 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 @@ 942 942 break; 943 943 len = (int)STRLEN(tv.vval.v_string); 944 944 945 - newtv.v_type = VAR_UNKNOWN; 946 945 set_vim_var_nr(VV_KEY, idx); 947 946 if (filter_map_one(&tv, expr, filtermap, fc, &newtv, &rem) == FAIL 948 947 || did_emsg)
 @@ -951,7 +950,7 @@ 951 950 clear_tv(&tv); 952 951 break; 953 952 } 954 - else if (filtermap != FILTERMAP_FILTER) 953 + if (filtermap == FILTERMAP_MAP || filtermap == FILTERMAP_MAPNEW) 955 954 { 956 955 if (newtv.v_type != VAR_STRING) 957 956 {
 @@ -963,7 +962,7 @@ 963 962 else 964 963 ga_concat(&ga, newtv.vval.v_string); 965 964 } 966 - else if (!rem) 965 + else if (filtermap == FILTERMAP_FOREACH || !rem) 967 966 ga_concat(&ga, tv.vval.v_string); 968 967 969 968 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 @@ 4879 4879 hashtab_T sve_hashtab; 4880 4880 } save_v_event_T; 4881 4881 4882 -// Enum used by filter(), map() and mapnew() 4882 +// Enum used by filter(), map(), mapnew() and foreach() 4883 4883 typedef enum { 4884 4884 FILTERMAP_FILTER, 4885 4885 FILTERMAP_MAP, 4886 - FILTERMAP_MAPNEW 4886 + FILTERMAP_MAPNEW, 4887 + FILTERMAP_FOREACH 4887 4888 } filtermap_T; 4888 4889 4889 4890 // 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 @@ 14 14 call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], 'v:key * 2')) 15 15 call assert_equal([9, 9, 9, 9], map([1, 2, 3, 4], 9)) 16 16 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) 17 29 endfunc 18 30 19 31 " dict with expression string
 @@ -29,6 +41,14 @@ 29 41 call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), 'v:val * 2')) 30 42 call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), 'v:key[0]')) 31 43 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) 32 52 endfunc 33 53 34 54 " list with funcref
 @@ -54,6 +74,16 @@ 54 74 return a:index * 2 55 75 endfunc 56 76 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') 57 87 endfunc 58 88 59 89 func Test_filter_map_nested()
 @@ -90,11 +120,46 @@ 90 120 return a:key[0] 91 121 endfunc 92 122 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 93 157 endfunc 94 158 95 159 func Test_map_filter_fails() 96 160 call assert_fails('call map([1], "42 +")', 'E15:') 97 161 call assert_fails('call filter([1], "42 +")', 'E15:') 162 + call assert_fails('call foreach([1], "let a = }")', 'E15:') 98 163 call assert_fails("let l = filter([1, 2, 3], '{}')", 'E728:') 99 164 call assert_fails("let l = filter({'k' : 10}, '{}')", 'E728:') 100 165 call assert_fails("let l = filter([1, 2], {})", 'E731:')
 @@ -106,6 +171,8 @@ 106 171 call assert_fails("let l = filter([1, 2], function('min'))", 'E118:') 107 172 call assert_equal([1, 2, 3], filter([1, 2, 3], test_null_partial())) 108 173 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:') 109 176 endfunc 110 177 111 178 func Test_map_and_modify()
 @@ -123,7 +190,7 @@ 123 190 124 191 func Test_filter_and_modify() 125 192 let l = [0] 126 - " cannot change the list halfway a map() 193 + " cannot change the list halfway thru filter() 127 194 call assert_fails('call filter(l, "remove(l, 0)")', 'E741:') 128 195 129 196 let d = #{a: 0, b: 0, c: 0}
 @@ -133,6 +200,18 @@ 133 200 call assert_fails('call filter(b, "remove(b, 0)")', 'E741:') 134 201 endfunc 135 202 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 + 136 215 func Test_mapnew_dict() 137 216 let din = #{one: 1, two: 2} 138 217 let dout = mapnew(din, {k, v -> string(v)})
 @@ -160,6 +239,36 @@ 160 239 call assert_equal(0z129956, bout) 161 240 endfunc 162 241 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 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 + 163 272 " Test for using map(), filter() and mapnew() with a string 164 273 func Test_filter_map_string() 165 274 " filter()
 @@ -219,6 +328,37 @@ 219 328 END 220 329 call v9.CheckLegacyAndVim9Success(lines) 221 330 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 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 + 222 362 let lines =<< trim END 223 363 #" map() and filter() 224 364 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 @@ 705 705 static int included_patches[] = 706 706 { /* Add new patch number below this line */ 707 707 /**/ 708 + 27, 709 +/**/ 708 710 26, 709 711 /**/ 710 712 25,