Patch 8.2.3871
Problem:    List.c contains code for dict and blob.
Solution:   Refactor to put code where it belongs. (Yegappan Lakshmanan,
            closes #9386)
Files:      src/blob.c, src/dict.c, src/list.c, src/proto/blob.pro,
            src/proto/dict.pro, src/proto/list.pro, src/proto/strings.pro,
            src/strings.c, src/structs.h, src/testdir/test_filter_map.vim,
            src/testdir/test_listdict.vim, src/testdir/test_sort.vim


*** ../vim-8.2.3870/src/blob.c  2021-12-19 19:19:27.818424761 +0000
--- src/blob.c  2021-12-22 18:04:56.843931664 +0000
***************
*** 410,487 ****
  }
  
  /*
!  * "remove({blob})" function
   */
      void
  blob_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg)
  {
      blob_T    *b = argvars[0].vval.v_blob;
      int               error = FALSE;
      long      idx;
      long      end;
  
      if (b != NULL && value_check_lock(b->bv_lock, arg_errmsg, TRUE))
        return;
  
      idx = (long)tv_get_number_chk(&argvars[1], &error);
!     if (!error)
      {
!       int     len = blob_len(b);
!       char_u  *p;
  
!       if (idx < 0)
!           // count from the end
!           idx = len + idx;
!       if (idx < 0 || idx >= len)
!       {
!           semsg(_(e_blobidx), idx);
            return;
        }
!       if (argvars[2].v_type == VAR_UNKNOWN)
        {
!           // Remove one item, return its value.
!           p = (char_u *)b->bv_ga.ga_data;
!           rettv->vval.v_number = (varnumber_T) *(p + idx);
!           mch_memmove(p + idx, p + idx + 1, (size_t)len - idx - 1);
            --b->bv_ga.ga_len;
        }
!       else
        {
!           blob_T  *blob;
  
!           // Remove range of items, return blob with values.
!           end = (long)tv_get_number_chk(&argvars[2], &error);
!           if (error)
!               return;
!           if (end < 0)
!               // count from the end
!               end = len + end;
!           if (end >= len || idx > end)
!           {
!               semsg(_(e_blobidx), end);
!               return;
!           }
!           blob = blob_alloc();
!           if (blob == NULL)
!               return;
!           blob->bv_ga.ga_len = end - idx + 1;
!           if (ga_grow(&blob->bv_ga, end - idx + 1) == FAIL)
!           {
!               vim_free(blob);
!               return;
!           }
!           p = (char_u *)b->bv_ga.ga_data;
!           mch_memmove((char_u *)blob->bv_ga.ga_data, p + idx,
!                                                 (size_t)(end - idx + 1));
!           ++blob->bv_refcount;
!           rettv->v_type = VAR_BLOB;
!           rettv->vval.v_blob = blob;
! 
!           if (len - end - 1 > 0)
!               mch_memmove(p + idx, p + end + 1, (size_t)(len - end - 1));
!           b->bv_ga.ga_len -= end - idx + 1;
        }
      }
  }
  
  /*
--- 410,715 ----
  }
  
  /*
!  * "add(blob, item)" function
!  */
!     void
! blob_add(typval_T *argvars, typval_T *rettv)
! {
!     blob_T    *b = argvars[0].vval.v_blob;
!     int               error = FALSE;
!     varnumber_T n;
! 
!     if (b == NULL)
!     {
!       if (in_vim9script())
!           emsg(_(e_cannot_add_to_null_blob));
!       return;
!     }
! 
!     if (value_check_lock(b->bv_lock, (char_u *)N_("add() argument"), TRUE))
!       return;
! 
!     n = tv_get_number_chk(&argvars[1], &error);
!     if (error)
!       return;
! 
!     ga_append(&b->bv_ga, (int)n);
!     copy_tv(&argvars[0], rettv);
! }
! 
! /*
!  * "remove({blob}, {idx} [, {end}])" function
   */
      void
  blob_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg)
  {
      blob_T    *b = argvars[0].vval.v_blob;
+     blob_T    *newblob;
      int               error = FALSE;
      long      idx;
      long      end;
+     int               len;
+     char_u    *p;
  
      if (b != NULL && value_check_lock(b->bv_lock, arg_errmsg, TRUE))
        return;
  
      idx = (long)tv_get_number_chk(&argvars[1], &error);
!     if (error)
!       return;
! 
!     len = blob_len(b);
! 
!     if (idx < 0)
!       // count from the end
!       idx = len + idx;
!     if (idx < 0 || idx >= len)
!     {
!       semsg(_(e_blobidx), idx);
!       return;
!     }
!     if (argvars[2].v_type == VAR_UNKNOWN)
      {
!       // Remove one item, return its value.
!       p = (char_u *)b->bv_ga.ga_data;
!       rettv->vval.v_number = (varnumber_T) *(p + idx);
!       mch_memmove(p + idx, p + idx + 1, (size_t)len - idx - 1);
!       --b->bv_ga.ga_len;
!       return;
!     }
  
!     // Remove range of items, return blob with values.
!     end = (long)tv_get_number_chk(&argvars[2], &error);
!     if (error)
!       return;
!     if (end < 0)
!       // count from the end
!       end = len + end;
!     if (end >= len || idx > end)
!     {
!       semsg(_(e_blobidx), end);
!       return;
!     }
!     newblob = blob_alloc();
!     if (newblob == NULL)
!       return;
!     newblob->bv_ga.ga_len = end - idx + 1;
!     if (ga_grow(&newblob->bv_ga, end - idx + 1) == FAIL)
!     {
!       vim_free(newblob);
!       return;
!     }
!     p = (char_u *)b->bv_ga.ga_data;
!     mch_memmove((char_u *)newblob->bv_ga.ga_data, p + idx,
!           (size_t)(end - idx + 1));
!     ++newblob->bv_refcount;
!     rettv->v_type = VAR_BLOB;
!     rettv->vval.v_blob = newblob;
! 
!     if (len - end - 1 > 0)
!       mch_memmove(p + idx, p + end + 1, (size_t)(len - end - 1));
!     b->bv_ga.ga_len -= end - idx + 1;
! }
! 
! /*
!  * Implementation of map() and filter() for a Blob.  Apply "expr" to every
!  * number in Blob "blob_arg" and return the result in "rettv".
!  */
!     void
! blob_filter_map(
!       blob_T          *blob_arg,
!       filtermap_T     filtermap,
!       typval_T        *expr,
!       typval_T        *rettv)
! {
!     blob_T    *b;
!     int               i;
!     typval_T  tv;
!     varnumber_T       val;
!     blob_T    *b_ret;
!     int               idx = 0;
!     int               rem;
! 
!     if (filtermap == FILTERMAP_MAPNEW)
!     {
!       rettv->v_type = VAR_BLOB;
!       rettv->vval.v_blob = NULL;
!     }
!     if ((b = blob_arg) == NULL)
!       return;
! 
!     b_ret = b;
!     if (filtermap == FILTERMAP_MAPNEW)
!     {
!       if (blob_copy(b, rettv) == FAIL)
            return;
+       b_ret = rettv->vval.v_blob;
+     }
+ 
+     // set_vim_var_nr() doesn't set the type
+     set_vim_var_type(VV_KEY, VAR_NUMBER);
+ 
+     for (i = 0; i < b->bv_ga.ga_len; i++)
+     {
+       typval_T newtv;
+ 
+       tv.v_type = VAR_NUMBER;
+       val = blob_get(b, i);
+       tv.vval.v_number = val;
+       set_vim_var_nr(VV_KEY, idx);
+       if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
+               || did_emsg)
+           break;
+       if (newtv.v_type != VAR_NUMBER && newtv.v_type != VAR_BOOL)
+       {
+           clear_tv(&newtv);
+           emsg(_(e_invalblob));
+           break;
        }
!       if (filtermap != FILTERMAP_FILTER)
        {
!           if (newtv.vval.v_number != val)
!               blob_set(b_ret, i, newtv.vval.v_number);
!       }
!       else if (rem)
!       {
!           char_u *p = (char_u *)blob_arg->bv_ga.ga_data;
! 
!           mch_memmove(p + i, p + i + 1,
!                   (size_t)b->bv_ga.ga_len - i - 1);
            --b->bv_ga.ga_len;
+           --i;
        }
!       ++idx;
!     }
! }
! 
! /*
!  * "insert(blob, {item} [, {idx}])" function
!  */
!     void
! blob_insert_func(typval_T *argvars, typval_T *rettv)
! {
!     blob_T    *b = argvars[0].vval.v_blob;
!     long      before = 0;
!     int               error = FALSE;
!     int               val, len;
!     char_u    *p;
! 
!     if (b == NULL)
!     {
!       if (in_vim9script())
!           emsg(_(e_cannot_add_to_null_blob));
!       return;
!     }
! 
!     if (value_check_lock(b->bv_lock, (char_u *)N_("insert() argument"), TRUE))
!       return;
! 
!     len = blob_len(b);
!     if (argvars[2].v_type != VAR_UNKNOWN)
!     {
!       before = (long)tv_get_number_chk(&argvars[2], &error);
!       if (error)
!           return;             // type error; errmsg already given
!       if (before < 0 || before > len)
        {
!           semsg(_(e_invarg2), tv_get_string(&argvars[2]));
!           return;
!       }
!     }
!     val = tv_get_number_chk(&argvars[1], &error);
!     if (error)
!       return;
!     if (val < 0 || val > 255)
!     {
!       semsg(_(e_invarg2), tv_get_string(&argvars[1]));
!       return;
!     }
! 
!     if (ga_grow(&b->bv_ga, 1) == FAIL)
!       return;
!     p = (char_u *)b->bv_ga.ga_data;
!     mch_memmove(p + before + 1, p + before, (size_t)len - before);
!     *(p + before) = val;
!     ++b->bv_ga.ga_len;
  
!     copy_tv(&argvars[0], rettv);
! }
! 
! /*
!  * reduce() Blob argvars[0] using the function 'funcname' with arguments in
!  * 'funcexe' starting with the initial value argvars[2] and return the result
!  * in 'rettv'.
!  */
!     void
! blob_reduce(
!       typval_T        *argvars,
!       char_u          *func_name,
!       funcexe_T       *funcexe,
!       typval_T        *rettv)
! {
!     blob_T    *b = argvars[0].vval.v_blob;
!     int               called_emsg_start = called_emsg;
!     int               r;
!     typval_T  initial;
!     typval_T  argv[3];
!     int       i;
! 
!     if (argvars[2].v_type == VAR_UNKNOWN)
!     {
!       if (b == NULL || b->bv_ga.ga_len == 0)
!       {
!           semsg(_(e_reduceempty), "Blob");
!           return;
        }
+       initial.v_type = VAR_NUMBER;
+       initial.vval.v_number = blob_get(b, 0);
+       i = 1;
+     }
+     else if (argvars[2].v_type != VAR_NUMBER)
+     {
+       emsg(_(e_number_expected));
+       return;
+     }
+     else
+     {
+       initial = argvars[2];
+       i = 0;
+     }
+ 
+     copy_tv(&initial, rettv);
+     if (b == NULL)
+       return;
+ 
+     for ( ; i < b->bv_ga.ga_len; i++)
+     {
+       argv[0] = *rettv;
+       argv[1].v_type = VAR_NUMBER;
+       argv[1].vval.v_number = blob_get(b, i);
+       r = call_func(func_name, -1, rettv, 2, argv, funcexe);
+       clear_tv(&argv[0]);
+       if (r == FAIL || called_emsg != called_emsg_start)
+           return;
+     }
+ }
+ 
+ /*
+  * "reverse({blob})" function
+  */
+     void
+ blob_reverse(blob_T *b, typval_T *rettv)
+ {
+     int       i, len = blob_len(b);
+ 
+     for (i = 0; i < len / 2; i++)
+     {
+       int tmp = blob_get(b, i);
+ 
+       blob_set(b, i, blob_get(b, len - i - 1));
+       blob_set(b, len - i - 1, tmp);
      }
+     rettv_blob_set(rettv, b);
  }
  
  /*
*** ../vim-8.2.3870/src/dict.c  2021-12-16 20:56:52.948098558 +0000
--- src/dict.c  2021-12-22 18:04:56.843931664 +0000
***************
*** 896,908 ****
      int               vim9script = in_vim9script();
      int               had_comma;
  
!     /*
!      * First check if it's not a curly-braces thing: {expr}.
!      * Must do this without evaluating, otherwise a function may be called
!      * twice.  Unfortunately this means we need to call eval1() twice for the
!      * first item.
!      * But {} is an empty Dictionary.
!      */
      if (!vim9script
            && *curly_expr != '}'
            && eval1(&curly_expr, &tv, NULL) == OK
--- 896,906 ----
      int               vim9script = in_vim9script();
      int               had_comma;
  
!     // First check if it's not a curly-braces thing: {expr}.
!     // Must do this without evaluating, otherwise a function may be called
!     // twice.  Unfortunately this means we need to call eval1() twice for the
!     // first item.
!     // But {} is an empty Dictionary.
      if (!vim9script
            && *curly_expr != '}'
            && eval1(&curly_expr, &tv, NULL) == OK
***************
*** 1184,1189 ****
--- 1182,1432 ----
  }
  
  /*
+  * Count the number of times item "needle" occurs in Dict "d". Case is ignored
+  * if "ic" is TRUE.
+  */
+     long
+ dict_count(dict_T *d, typval_T *needle, int ic)
+ {
+     int               todo;
+     hashitem_T        *hi;
+     long      n = 0;
+ 
+     if (d == NULL)
+       return 0;
+ 
+     todo = (int)d->dv_hashtab.ht_used;
+     for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi)
+     {
+       if (!HASHITEM_EMPTY(hi))
+       {
+           --todo;
+           if (tv_equal(&HI2DI(hi)->di_tv, needle, ic, FALSE))
+               ++n;
+       }
+     }
+ 
+     return n;
+ }
+ 
+ /*
+  * extend() a Dict. Append Dict argvars[1] to Dict argvars[0] and return the
+  * resulting Dict in "rettv".  "is_new" is TRUE for extendnew().
+  */
+     void
+ dict_extend_func(
+       typval_T        *argvars,
+       type_T          *type,
+       char            *func_name,
+       char_u          *arg_errmsg,
+       int             is_new,
+       typval_T        *rettv)
+ {
+     dict_T    *d1, *d2;
+     char_u    *action;
+     int       i;
+ 
+     d1 = argvars[0].vval.v_dict;
+     if (d1 == NULL)
+     {
+       emsg(_(e_cannot_extend_null_dict));
+       return;
+     }
+     d2 = argvars[1].vval.v_dict;
+     if ((is_new || !value_check_lock(d1->dv_lock, arg_errmsg, TRUE))
+           && d2 != NULL)
+     {
+       if (is_new)
+       {
+           d1 = dict_copy(d1, FALSE, get_copyID());
+           if (d1 == NULL)
+               return;
+       }
+ 
+       // Check the third argument.
+       if (argvars[2].v_type != VAR_UNKNOWN)
+       {
+           static char *(av[]) = {"keep", "force", "error"};
+ 
+           action = tv_get_string_chk(&argvars[2]);
+           if (action == NULL)
+               return;
+           for (i = 0; i < 3; ++i)
+               if (STRCMP(action, av[i]) == 0)
+                   break;
+           if (i == 3)
+           {
+               semsg(_(e_invarg2), action);
+               return;
+           }
+       }
+       else
+           action = (char_u *)"force";
+ 
+       if (type != NULL && check_typval_arg_type(type, &argvars[1],
+                   func_name, 2) == FAIL)
+           return;
+       dict_extend(d1, d2, action, func_name);
+ 
+       if (is_new)
+       {
+           rettv->v_type = VAR_DICT;
+           rettv->vval.v_dict = d1;
+           rettv->v_lock = FALSE;
+       }
+       else
+           copy_tv(&argvars[0], rettv);
+     }
+ }
+ 
+ /*
+  * Implementation of map() and filter() for a Dict.  Apply "expr" to every
+  * item in Dict "d" and return the result in "rettv".
+  */
+     void
+ dict_filter_map(
+       dict_T          *d,
+       filtermap_T     filtermap,
+       type_T          *argtype,
+       char            *func_name,
+       char_u          *arg_errmsg,
+       typval_T        *expr,
+       typval_T        *rettv)
+ {
+     int               prev_lock;
+     dict_T    *d_ret = NULL;
+     hashtab_T *ht;
+     hashitem_T        *hi;
+     dictitem_T        *di;
+     int               todo;
+     int               rem;
+ 
+     if (filtermap == FILTERMAP_MAPNEW)
+     {
+       rettv->v_type = VAR_DICT;
+       rettv->vval.v_dict = NULL;
+     }
+     if (d == NULL
+         || (filtermap == FILTERMAP_FILTER
+                       && value_check_lock(d->dv_lock, arg_errmsg, TRUE)))
+       return;
+ 
+     prev_lock = d->dv_lock;
+ 
+     if (filtermap == FILTERMAP_MAPNEW)
+     {
+       if (rettv_dict_alloc(rettv) == FAIL)
+           return;
+       d_ret = rettv->vval.v_dict;
+     }
+ 
+     if (filtermap != FILTERMAP_FILTER && d->dv_lock == 0)
+       d->dv_lock = VAR_LOCKED;
+     ht = &d->dv_hashtab;
+     hash_lock(ht);
+     todo = (int)ht->ht_used;
+     for (hi = ht->ht_array; todo > 0; ++hi)
+     {
+       if (!HASHITEM_EMPTY(hi))
+       {
+           int         r;
+           typval_T    newtv;
+ 
+           --todo;
+           di = HI2DI(hi);
+           if (filtermap == FILTERMAP_MAP
+                   && (value_check_lock(di->di_tv.v_lock,
+                           arg_errmsg, TRUE)
+                       || var_check_ro(di->di_flags,
+                           arg_errmsg, TRUE)))
+               break;
+           set_vim_var_string(VV_KEY, di->di_key, -1);
+           newtv.v_type = VAR_UNKNOWN;
+           r = filter_map_one(&di->di_tv, expr, filtermap,
+                   &newtv, &rem);
+           clear_tv(get_vim_var_tv(VV_KEY));
+           if (r == FAIL || did_emsg)
+           {
+               clear_tv(&newtv);
+               break;
+           }
+           if (filtermap == FILTERMAP_MAP)
+           {
+               if (argtype != NULL && check_typval_arg_type(
+                           argtype->tt_member, &newtv,
+                           func_name, 0) == FAIL)
+               {
+                   clear_tv(&newtv);
+                   break;
+               }
+               // map(): replace the dict item value
+               clear_tv(&di->di_tv);
+               newtv.v_lock = 0;
+               di->di_tv = newtv;
+           }
+           else if (filtermap == FILTERMAP_MAPNEW)
+           {
+               // mapnew(): add the item value to the new dict
+               r = dict_add_tv(d_ret, (char *)di->di_key, &newtv);
+               clear_tv(&newtv);
+               if (r == FAIL)
+                   break;
+           }
+           else if (filtermap == FILTERMAP_FILTER && rem)
+           {
+               // filter(false): remove the item from the dict
+               if (var_check_fixed(di->di_flags, arg_errmsg, TRUE)
+                       || var_check_ro(di->di_flags, arg_errmsg, TRUE))
+                   break;
+               dictitem_remove(d, di);
+           }
+       }
+     }
+     hash_unlock(ht);
+     d->dv_lock = prev_lock;
+ }
+ 
+ /*
+  * "remove({dict})" function
+  */
+     void
+ dict_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg)
+ {
+     dict_T    *d;
+     char_u    *key;
+     dictitem_T        *di;
+ 
+     if (argvars[2].v_type != VAR_UNKNOWN)
+     {
+       semsg(_(e_too_many_arguments_for_function_str), "remove()");
+       return;
+     }
+ 
+     d = argvars[0].vval.v_dict;
+     if (d == NULL || value_check_lock(d->dv_lock, arg_errmsg, TRUE))
+       return;
+ 
+     key = tv_get_string_chk(&argvars[1]);
+     if (key == NULL)
+       return;
+ 
+     di = dict_find(d, key, -1);
+     if (di == NULL)
+     {
+       semsg(_(e_dictkey), key);
+       return;
+     }
+ 
+     if (var_check_fixed(di->di_flags, arg_errmsg, TRUE)
+           || var_check_ro(di->di_flags, arg_errmsg, TRUE))
+       return;
+ 
+     *rettv = di->di_tv;
+     init_tv(&di->di_tv);
+     dictitem_remove(d, di);
+ }
+ 
+ /*
   * Turn a dict into a list:
   * "what" == 0: list of keys
   * "what" == 1: list of values
***************
*** 1338,1373 ****
                                      tv_get_string(&argvars[1]), -1) != NULL;
  }
  
- /*
-  * "remove({dict})" function
-  */
-     void
- dict_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg)
- {
-     dict_T    *d;
-     char_u    *key;
-     dictitem_T        *di;
- 
-     if (argvars[2].v_type != VAR_UNKNOWN)
-       semsg(_(e_too_many_arguments_for_function_str), "remove()");
-     else if ((d = argvars[0].vval.v_dict) != NULL
-           && !value_check_lock(d->dv_lock, arg_errmsg, TRUE))
-     {
-       key = tv_get_string_chk(&argvars[1]);
-       if (key != NULL)
-       {
-           di = dict_find(d, key, -1);
-           if (di == NULL)
-               semsg(_(e_dictkey), key);
-           else if (!var_check_fixed(di->di_flags, arg_errmsg, TRUE)
-                       && !var_check_ro(di->di_flags, arg_errmsg, TRUE))
-           {
-               *rettv = di->di_tv;
-               init_tv(&di->di_tv);
-               dictitem_remove(d, di);
-           }
-       }
-     }
- }
- 
  #endif // defined(FEAT_EVAL)
--- 1581,1584 ----
*** ../vim-8.2.3870/src/list.c  2021-12-21 13:19:38.745205784 +0000
--- src/list.c  2021-12-22 18:04:56.843931664 +0000
***************
*** 314,341 ****
  }
  
  /*
-  * Make a typval_T of the first character of "input" and store it in "output".
-  * Return OK or FAIL.
-  */
-     static int
- tv_get_first_char(char_u *input, typval_T *output)
- {
-     char_u    buf[MB_MAXBYTES + 1];
-     int               len;
- 
-     if (input == NULL || output == NULL)
-       return FAIL;
- 
-     len = has_mbyte ? mb_ptr2len(input) : 1;
-     STRNCPY(buf, input, len);
-     buf[len] = NUL;
-     output->v_type = VAR_STRING;
-     output->vval.v_string = vim_strsave(buf);
- 
-     return output->vval.v_string == NULL ? FAIL : OK;
- }
- 
- /*
   * Free a list item, unless it was allocated together with the list itself.
   * Does not clear the value.  Does not notify watchers.
   */
--- 314,319 ----
***************
*** 876,884 ****
      long      idx;
      type_T    *member_type = NULL;
  
!     /*
!      * Check whether any of the list items is locked before making any 
changes.
!      */
      idx = idx1;
      dest_li = first_li;
      for (src_li = src->lv_first; src_li != NULL && dest_li != NULL; )
--- 854,860 ----
      long      idx;
      type_T    *member_type = NULL;
  
!     // Check whether any of the list items is locked before making any 
changes.
      idx = idx1;
      dest_li = first_li;
      for (src_li = src->lv_first; src_li != NULL && dest_li != NULL; )
***************
*** 896,904 ****
                                           && dest->lv_type->tt_member != NULL)
        member_type = dest->lv_type->tt_member;
  
!     /*
!      * Assign the List values to the list items.
!      */
      idx = idx1;
      dest_li = first_li;
      for (src_li = src->lv_first; src_li != NULL; )
--- 872,878 ----
                                           && dest->lv_type->tt_member != NULL)
        member_type = dest->lv_type->tt_member;
  
!     // Assign the List values to the list items.
      idx = idx1;
      dest_li = first_li;
      for (src_li = src->lv_first; src_li != NULL; )
***************
*** 1725,1730 ****
--- 1699,1708 ----
      rettv->vval.v_string = ga.ga_data;
  }
  
+ /*
+  * Remove item argvars[1] from List argvars[0]. If argvars[2] is supplied, 
then
+  * remove the range of items from argvars[1] to argvars[2] (inclusive).
+  */
      static void
  list_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg)
  {
***************
*** 1733,1738 ****
--- 1711,1719 ----
      listitem_T        *li;
      int               error = FALSE;
      long      idx;
+     long      end;
+     int               cnt = 0;
+     list_T    *rl;
  
      if ((l = argvars[0].vval.v_list) == NULL
                             || value_check_lock(l->lv_lock, arg_errmsg, TRUE))
***************
*** 1740,1814 ****
  
      idx = (long)tv_get_number_chk(&argvars[1], &error);
      if (error)
!       ;               // type error: do nothing, errmsg already given
!     else if ((item = list_find(l, idx)) == NULL)
        semsg(_(e_listidx), idx);
!     else
      {
!       if (argvars[2].v_type == VAR_UNKNOWN)
!       {
!           // Remove one item, return its value.
!           vimlist_remove(l, item, item);
!           *rettv = item->li_tv;
!           list_free_item(l, item);
!       }
!       else
!       {
!           // Remove range of items, return list with values.
!           long end = (long)tv_get_number_chk(&argvars[2], &error);
  
!           if (error)
!               ;               // type error: do nothing
!           else if ((item2 = list_find(l, end)) == NULL)
!               semsg(_(e_listidx), end);
!           else
!           {
!               int         cnt = 0;
  
!               for (li = item; li != NULL; li = li->li_next)
!               {
!                   ++cnt;
!                   if (li == item2)
!                       break;
!               }
!               if (li == NULL)  // didn't find "item2" after "item"
!                   emsg(_(e_invalid_range));
!               else
!               {
!                   vimlist_remove(l, item, item2);
!                   if (rettv_list_alloc(rettv) == OK)
!                   {
!                       list_T *rl = rettv->vval.v_list;
! 
!                       if (l->lv_with_items > 0)
!                       {
!                           // need to copy the list items and move the value
!                           while (item != NULL)
!                           {
!                               li = listitem_alloc();
!                               if (li == NULL)
!                                   return;
!                               li->li_tv = item->li_tv;
!                               init_tv(&item->li_tv);
!                               list_append(rl, li);
!                               if (item == item2)
!                                   break;
!                               item = item->li_next;
!                           }
!                       }
!                       else
!                       {
!                           rl->lv_first = item;
!                           rl->lv_u.mat.lv_last = item2;
!                           item->li_prev = NULL;
!                           item2->li_next = NULL;
!                           rl->lv_len = cnt;
!                       }
!                   }
!               }
!           }
        }
      }
  }
  
  static int item_compare(const void *s1, const void *s2);
--- 1721,1796 ----
  
      idx = (long)tv_get_number_chk(&argvars[1], &error);
      if (error)
!       return;         // type error: do nothing, errmsg already given
! 
!     if ((item = list_find(l, idx)) == NULL)
!     {
        semsg(_(e_listidx), idx);
!       return;
!     }
! 
!     if (argvars[2].v_type == VAR_UNKNOWN)
      {
!       // Remove one item, return its value.
!       vimlist_remove(l, item, item);
!       *rettv = item->li_tv;
!       list_free_item(l, item);
!       return;
!     }
  
!     // Remove range of items, return list with values.
!     end = (long)tv_get_number_chk(&argvars[2], &error);
!     if (error)
!       return;         // type error: do nothing
  
!     if ((item2 = list_find(l, end)) == NULL)
!     {
!       semsg(_(e_listidx), end);
!       return;
!     }
! 
!     for (li = item; li != NULL; li = li->li_next)
!     {
!       ++cnt;
!       if (li == item2)
!           break;
!     }
!     if (li == NULL)  // didn't find "item2" after "item"
!     {
!       emsg(_(e_invalid_range));
!       return;
!     }
! 
!     vimlist_remove(l, item, item2);
!     if (rettv_list_alloc(rettv) != OK)
!       return;
! 
!     rl = rettv->vval.v_list;
! 
!     if (l->lv_with_items > 0)
!     {
!       // need to copy the list items and move the value
!       while (item != NULL)
!       {
!           li = listitem_alloc();
!           if (li == NULL)
!               return;
!           li->li_tv = item->li_tv;
!           init_tv(&item->li_tv);
!           list_append(rl, li);
!           if (item == item2)
!               break;
!           item = item->li_next;
        }
      }
+     else
+     {
+       rl->lv_first = item;
+       rl->lv_u.mat.lv_last = item2;
+       item->li_prev = NULL;
+       item2->li_next = NULL;
+       rl->lv_len = cnt;
+     }
  }
  
  static int item_compare(const void *s1, const void *s2);
***************
*** 2101,2108 ****
  }
  
  /*
!  * Parse the optional arguments to sort() and uniq() and return the values in
!  * 'info'.
   */
      static int
  parse_sort_uniq_args(typval_T *argvars, sortinfo_T *info)
--- 2083,2090 ----
  }
  
  /*
!  * Parse the optional arguments supplied to the sort() or uniq() function and
!  * return the values in "info".
   */
      static int
  parse_sort_uniq_args(typval_T *argvars, sortinfo_T *info)
***************
*** 2272,2288 ****
      do_sort_uniq(argvars, rettv, FALSE);
  }
  
- typedef enum {
-     FILTERMAP_FILTER,
-     FILTERMAP_MAP,
-     FILTERMAP_MAPNEW
- } filtermap_T;
- 
  /*
   * Handle one item for map() and filter().
   * Sets v:val to "tv".  Caller must set v:key.
   */
!     static int
  filter_map_one(
        typval_T        *tv,        // original value
        typval_T        *expr,      // callback
--- 2254,2264 ----
      do_sort_uniq(argvars, rettv, FALSE);
  }
  
  /*
   * Handle one item for map() and filter().
   * Sets v:val to "tv".  Caller must set v:key.
   */
!     int
  filter_map_one(
        typval_T        *tv,        // original value
        typval_T        *expr,      // callback
***************
*** 2320,2573 ****
  }
  
  /*
!  * Implementation of map() and filter() for a Dict.
!  */
!     static void
! filter_map_dict(
!       dict_T          *d,
!       filtermap_T     filtermap,
!       type_T          *argtype,
!       char            *func_name,
!       char_u          *arg_errmsg,
!       typval_T        *expr,
!       typval_T        *rettv)
! {
!     int               prev_lock;
!     dict_T    *d_ret = NULL;
!     hashtab_T *ht;
!     hashitem_T        *hi;
!     dictitem_T        *di;
!     int               todo;
!     int               rem;
! 
!     if (filtermap == FILTERMAP_MAPNEW)
!     {
!       rettv->v_type = VAR_DICT;
!       rettv->vval.v_dict = NULL;
!     }
!     if (d == NULL
!         || (filtermap == FILTERMAP_FILTER
!                       && value_check_lock(d->dv_lock, arg_errmsg, TRUE)))
!       return;
! 
!     prev_lock = d->dv_lock;
! 
!     if (filtermap == FILTERMAP_MAPNEW)
!     {
!       if (rettv_dict_alloc(rettv) == FAIL)
!           return;
!       d_ret = rettv->vval.v_dict;
!     }
! 
!     if (filtermap != FILTERMAP_FILTER && d->dv_lock == 0)
!       d->dv_lock = VAR_LOCKED;
!     ht = &d->dv_hashtab;
!     hash_lock(ht);
!     todo = (int)ht->ht_used;
!     for (hi = ht->ht_array; todo > 0; ++hi)
!     {
!       if (!HASHITEM_EMPTY(hi))
!       {
!           int         r;
!           typval_T    newtv;
! 
!           --todo;
!           di = HI2DI(hi);
!           if (filtermap == FILTERMAP_MAP
!                   && (value_check_lock(di->di_tv.v_lock,
!                           arg_errmsg, TRUE)
!                       || var_check_ro(di->di_flags,
!                           arg_errmsg, TRUE)))
!               break;
!           set_vim_var_string(VV_KEY, di->di_key, -1);
!           newtv.v_type = VAR_UNKNOWN;
!           r = filter_map_one(&di->di_tv, expr, filtermap,
!                   &newtv, &rem);
!           clear_tv(get_vim_var_tv(VV_KEY));
!           if (r == FAIL || did_emsg)
!           {
!               clear_tv(&newtv);
!               break;
!           }
!           if (filtermap == FILTERMAP_MAP)
!           {
!               if (argtype != NULL && check_typval_arg_type(
!                           argtype->tt_member, &newtv,
!                           func_name, 0) == FAIL)
!               {
!                   clear_tv(&newtv);
!                   break;
!               }
!               // map(): replace the dict item value
!               clear_tv(&di->di_tv);
!               newtv.v_lock = 0;
!               di->di_tv = newtv;
!           }
!           else if (filtermap == FILTERMAP_MAPNEW)
!           {
!               // mapnew(): add the item value to the new dict
!               r = dict_add_tv(d_ret, (char *)di->di_key, &newtv);
!               clear_tv(&newtv);
!               if (r == FAIL)
!                   break;
!           }
!           else if (filtermap == FILTERMAP_FILTER && rem)
!           {
!               // filter(false): remove the item from the dict
!               if (var_check_fixed(di->di_flags, arg_errmsg, TRUE)
!                       || var_check_ro(di->di_flags, arg_errmsg, TRUE))
!                   break;
!               dictitem_remove(d, di);
!           }
!       }
!     }
!     hash_unlock(ht);
!     d->dv_lock = prev_lock;
! }
! 
! /*
!  * Implementation of map() and filter() for a Blob.
   */
      static void
! filter_map_blob(
!       blob_T          *blob_arg,
!       filtermap_T     filtermap,
!       typval_T        *expr,
!       typval_T        *rettv)
! {
!     blob_T    *b;
!     int               i;
!     typval_T  tv;
!     varnumber_T       val;
!     blob_T    *b_ret;
!     int               idx = 0;
!     int               rem;
! 
!     if (filtermap == FILTERMAP_MAPNEW)
!     {
!       rettv->v_type = VAR_BLOB;
!       rettv->vval.v_blob = NULL;
!     }
!     if ((b = blob_arg) == NULL)
!       return;
! 
!     b_ret = b;
!     if (filtermap == FILTERMAP_MAPNEW)
!     {
!       if (blob_copy(b, rettv) == FAIL)
!           return;
!       b_ret = rettv->vval.v_blob;
!     }
! 
!     // set_vim_var_nr() doesn't set the type
!     set_vim_var_type(VV_KEY, VAR_NUMBER);
! 
!     for (i = 0; i < b->bv_ga.ga_len; i++)
!     {
!       typval_T newtv;
! 
!       tv.v_type = VAR_NUMBER;
!       val = blob_get(b, i);
!       tv.vval.v_number = val;
!       set_vim_var_nr(VV_KEY, idx);
!       if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
!               || did_emsg)
!           break;
!       if (newtv.v_type != VAR_NUMBER && newtv.v_type != VAR_BOOL)
!       {
!           clear_tv(&newtv);
!           emsg(_(e_invalblob));
!           break;
!       }
!       if (filtermap != FILTERMAP_FILTER)
!       {
!           if (newtv.vval.v_number != val)
!               blob_set(b_ret, i, newtv.vval.v_number);
!       }
!       else if (rem)
!       {
!           char_u *p = (char_u *)blob_arg->bv_ga.ga_data;
! 
!           mch_memmove(p + i, p + i + 1,
!                   (size_t)b->bv_ga.ga_len - i - 1);
!           --b->bv_ga.ga_len;
!           --i;
!       }
!       ++idx;
!     }
! }
! 
! /*
!  * Implementation of map() and filter() for a String.
!  */
!     static void
! filter_map_string(
!       char_u          *str,
!       filtermap_T     filtermap,
!       typval_T        *expr,
!       typval_T        *rettv)
! {
!     char_u    *p;
!     typval_T  tv;
!     garray_T  ga;
!     int               len = 0;
!     int               idx = 0;
!     int               rem;
! 
!     rettv->v_type = VAR_STRING;
!     rettv->vval.v_string = NULL;
! 
!     // set_vim_var_nr() doesn't set the type
!     set_vim_var_type(VV_KEY, VAR_NUMBER);
! 
!     ga_init2(&ga, (int)sizeof(char), 80);
!     for (p = str; *p != NUL; p += len)
!     {
!       typval_T newtv;
! 
!       if (tv_get_first_char(p, &tv) == FAIL)
!           break;
!       len = (int)STRLEN(tv.vval.v_string);
! 
!       set_vim_var_nr(VV_KEY, idx);
!       if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
!               || did_emsg)
!           break;
!       if (did_emsg)
!       {
!           clear_tv(&newtv);
!           clear_tv(&tv);
!           break;
!       }
!       else if (filtermap != FILTERMAP_FILTER)
!       {
!           if (newtv.v_type != VAR_STRING)
!           {
!               clear_tv(&newtv);
!               clear_tv(&tv);
!               emsg(_(e_stringreq));
!               break;
!           }
!           else
!               ga_concat(&ga, newtv.vval.v_string);
!       }
!       else if (!rem)
!           ga_concat(&ga, tv.vval.v_string);
! 
!       clear_tv(&newtv);
!       clear_tv(&tv);
! 
!       ++idx;
!     }
!     ga_append(&ga, NUL);
!     rettv->vval.v_string = ga.ga_data;
! }
! 
! /*
!  * Implementation of map() and filter() for a List.
!  */
!     static void
! filter_map_list(
        list_T          *l,
        filtermap_T     filtermap,
        type_T          *argtype,
--- 2296,2306 ----
  }
  
  /*
!  * Implementation of map() and filter() for a List.  Apply "expr" to every 
item
!  * in List "l" and return the result in "rettv".
   */
      static void
! list_filter_map(
        list_T          *l,
        filtermap_T     filtermap,
        type_T          *argtype,
***************
*** 2775,2789 ****
        did_emsg = FALSE;
  
        if (argvars[0].v_type == VAR_DICT)
!           filter_map_dict(argvars[0].vval.v_dict, filtermap, type, func_name,
                    arg_errmsg, expr, rettv);
        else if (argvars[0].v_type == VAR_BLOB)
!           filter_map_blob(argvars[0].vval.v_blob, filtermap, expr, rettv);
        else if (argvars[0].v_type == VAR_STRING)
!           filter_map_string(tv_get_string(&argvars[0]), filtermap, expr,
                    rettv);
        else // argvars[0].v_type == VAR_LIST
!           filter_map_list(argvars[0].vval.v_list, filtermap, type, func_name,
                    arg_errmsg, expr, rettv);
  
        restore_vimvar(VV_KEY, &save_key);
--- 2508,2522 ----
        did_emsg = FALSE;
  
        if (argvars[0].v_type == VAR_DICT)
!           dict_filter_map(argvars[0].vval.v_dict, filtermap, type, func_name,
                    arg_errmsg, expr, rettv);
        else if (argvars[0].v_type == VAR_BLOB)
!           blob_filter_map(argvars[0].vval.v_blob, filtermap, expr, rettv);
        else if (argvars[0].v_type == VAR_STRING)
!           string_filter_map(tv_get_string(&argvars[0]), filtermap, expr,
                    rettv);
        else // argvars[0].v_type == VAR_LIST
!           list_filter_map(argvars[0].vval.v_list, filtermap, type, func_name,
                    arg_errmsg, expr, rettv);
  
        restore_vimvar(VV_KEY, &save_key);
***************
*** 2827,2832 ****
--- 2560,2586 ----
  /*
   * "add(list, item)" function
   */
+     static void
+ list_add(typval_T *argvars, typval_T *rettv)
+ {
+     list_T    *l = argvars[0].vval.v_list;
+ 
+     if (l == NULL)
+     {
+       if (in_vim9script())
+           emsg(_(e_cannot_add_to_null_list));
+     }
+     else if (!value_check_lock(l->lv_lock,
+               (char_u *)N_("add() argument"), TRUE)
+           && list_append_tv(l, &argvars[1]) == OK)
+     {
+       copy_tv(&argvars[0], rettv);
+     }
+ }
+ 
+ /*
+  * "add(object, item)" function
+  */
      void
  f_add(typval_T *argvars, typval_T *rettv)
  {
***************
*** 2839,2930 ****
        return;
  
      if (argvars[0].v_type == VAR_LIST)
!     {
!       list_T  *l = argvars[0].vval.v_list;
! 
!       if (l == NULL)
!       {
!           if (in_vim9script())
!               emsg(_(e_cannot_add_to_null_list));
!       }
!       else if (!value_check_lock(l->lv_lock,
!                                         (char_u *)N_("add() argument"), TRUE)
!               && list_append_tv(l, &argvars[1]) == OK)
!       {
!           copy_tv(&argvars[0], rettv);
!       }
!     }
      else if (argvars[0].v_type == VAR_BLOB)
!     {
!       blob_T  *b = argvars[0].vval.v_blob;
! 
!       if (b == NULL)
!       {
!           if (in_vim9script())
!               emsg(_(e_cannot_add_to_null_blob));
!       }
!       else if (!value_check_lock(b->bv_lock,
!                                        (char_u *)N_("add() argument"), TRUE))
!       {
!           int         error = FALSE;
!           varnumber_T n = tv_get_number_chk(&argvars[1], &error);
! 
!           if (!error)
!           {
!               ga_append(&b->bv_ga, (int)n);
!               copy_tv(&argvars[0], rettv);
!           }
!       }
!     }
      else
        emsg(_(e_listblobreq));
  }
  
  /*
-  * Count the number of times "needle" occurs in string "haystack". Case is
-  * ignored if "ic" is TRUE.
-  */
-     static long
- count_string(char_u *haystack, char_u *needle, int ic)
- {
-     long      n = 0;
-     char_u    *p = haystack;
-     char_u    *next;
- 
-     if (p == NULL || needle == NULL || *needle == NUL)
-       return 0;
- 
-     if (ic)
-     {
-       size_t len = STRLEN(needle);
- 
-       while (*p != NUL)
-       {
-           if (MB_STRNICMP(p, needle, len) == 0)
-           {
-               ++n;
-               p += len;
-           }
-           else
-               MB_PTR_ADV(p);
-       }
-     }
-     else
-       while ((next = (char_u *)strstr((char *)p, (char *)needle)) != NULL)
-       {
-           ++n;
-           p = next + STRLEN(needle);
-       }
- 
-     return n;
- }
- 
- /*
   * Count the number of times item "needle" occurs in List "l" starting at 
index
   * "idx". Case is ignored if "ic" is TRUE.
   */
      static long
! count_list(list_T *l, typval_T *needle, long idx, int ic)
  {
      long      n = 0;
      listitem_T        *li;
--- 2593,2611 ----
        return;
  
      if (argvars[0].v_type == VAR_LIST)
!       list_add(argvars, rettv);
      else if (argvars[0].v_type == VAR_BLOB)
!       blob_add(argvars, rettv);
      else
        emsg(_(e_listblobreq));
  }
  
  /*
   * Count the number of times item "needle" occurs in List "l" starting at 
index
   * "idx". Case is ignored if "ic" is TRUE.
   */
      static long
! list_count(list_T *l, typval_T *needle, long idx, int ic)
  {
      long      n = 0;
      listitem_T        *li;
***************
*** 2952,2985 ****
  }
  
  /*
-  * Count the number of times item "needle" occurs in Dict "d". Case is ignored
-  * if "ic" is TRUE.
-  */
-     static long
- count_dict(dict_T *d, typval_T *needle, int ic)
- {
-     int               todo;
-     hashitem_T        *hi;
-     long      n = 0;
- 
-     if (d == NULL)
-       return 0;
- 
-     todo = (int)d->dv_hashtab.ht_used;
-     for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi)
-     {
-       if (!HASHITEM_EMPTY(hi))
-       {
-           --todo;
-           if (tv_equal(&HI2DI(hi)->di_tv, needle, ic, FALSE))
-               ++n;
-       }
-     }
- 
-     return n;
- }
- 
- /*
   * "count()" function
   */
      void
--- 2633,2638 ----
***************
*** 3000,3006 ****
        ic = (int)tv_get_bool_chk(&argvars[2], &error);
  
      if (!error && argvars[0].v_type == VAR_STRING)
!       n = count_string(argvars[0].vval.v_string,
                                        tv_get_string_chk(&argvars[1]), ic);
      else if (!error && argvars[0].v_type == VAR_LIST)
      {
--- 2653,2659 ----
        ic = (int)tv_get_bool_chk(&argvars[2], &error);
  
      if (!error && argvars[0].v_type == VAR_STRING)
!       n = string_count(argvars[0].vval.v_string,
                                        tv_get_string_chk(&argvars[1]), ic);
      else if (!error && argvars[0].v_type == VAR_LIST)
      {
***************
*** 3010,3016 ****
                && argvars[3].v_type != VAR_UNKNOWN)
            idx = (long)tv_get_number_chk(&argvars[3], &error);
        if (!error)
!           n = count_list(argvars[0].vval.v_list, &argvars[1], idx, ic);
      }
      else if (!error && argvars[0].v_type == VAR_DICT)
      {
--- 2663,2669 ----
                && argvars[3].v_type != VAR_UNKNOWN)
            idx = (long)tv_get_number_chk(&argvars[3], &error);
        if (!error)
!           n = list_count(argvars[0].vval.v_list, &argvars[1], idx, ic);
      }
      else if (!error && argvars[0].v_type == VAR_DICT)
      {
***************
*** 3018,3024 ****
                && argvars[3].v_type != VAR_UNKNOWN)
            emsg(_(e_invarg));
        else
!           n = count_dict(argvars[0].vval.v_dict, &argvars[1], ic);
      }
      else
        semsg(_(e_listdictarg), "count()");
--- 2671,2677 ----
                && argvars[3].v_type != VAR_UNKNOWN)
            emsg(_(e_invarg));
        else
!           n = dict_count(argvars[0].vval.v_dict, &argvars[1], ic);
      }
      else
        semsg(_(e_listdictarg), "count()");
***************
*** 3031,3037 ****
   * extendnew().
   */
      static void
! extend_list(
        typval_T        *argvars,
        type_T          *type,
        char            *func_name,
--- 2684,2690 ----
   * extendnew().
   */
      static void
! list_extend_func(
        typval_T        *argvars,
        type_T          *type,
        char            *func_name,
***************
*** 3098,3173 ****
  }
  
  /*
-  * extend() a Dict. Append Dict argvars[1] to Dict argvars[0] and return the
-  * resulting Dict in "rettv".  "is_new" is TRUE for extendnew().
-  */
-     static void
- extend_dict(
-       typval_T        *argvars,
-       type_T          *type,
-       char            *func_name,
-       char_u          *arg_errmsg,
-       int             is_new,
-       typval_T        *rettv)
- {
-     dict_T    *d1, *d2;
-     char_u    *action;
-     int       i;
- 
-     d1 = argvars[0].vval.v_dict;
-     if (d1 == NULL)
-     {
-       emsg(_(e_cannot_extend_null_dict));
-       return;
-     }
-     d2 = argvars[1].vval.v_dict;
-     if ((is_new || !value_check_lock(d1->dv_lock, arg_errmsg, TRUE))
-           && d2 != NULL)
-     {
-       if (is_new)
-       {
-           d1 = dict_copy(d1, FALSE, get_copyID());
-           if (d1 == NULL)
-               return;
-       }
- 
-       // Check the third argument.
-       if (argvars[2].v_type != VAR_UNKNOWN)
-       {
-           static char *(av[]) = {"keep", "force", "error"};
- 
-           action = tv_get_string_chk(&argvars[2]);
-           if (action == NULL)
-               return;
-           for (i = 0; i < 3; ++i)
-               if (STRCMP(action, av[i]) == 0)
-                   break;
-           if (i == 3)
-           {
-               semsg(_(e_invarg2), action);
-               return;
-           }
-       }
-       else
-           action = (char_u *)"force";
- 
-       if (type != NULL && check_typval_arg_type(type, &argvars[1],
-                   func_name, 2) == FAIL)
-           return;
-       dict_extend(d1, d2, action, func_name);
- 
-       if (is_new)
-       {
-           rettv->v_type = VAR_DICT;
-           rettv->vval.v_dict = d1;
-           rettv->v_lock = FALSE;
-       }
-       else
-           copy_tv(&argvars[0], rettv);
-     }
- }
- 
- /*
   * "extend()" or "extendnew()" function.  "is_new" is TRUE for extendnew().
   */
      static void
--- 2751,2756 ----
***************
*** 3185,3193 ****
      }
  
      if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST)
!       extend_list(argvars, type, func_name, arg_errmsg, is_new, rettv);
      else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type == VAR_DICT)
!       extend_dict(argvars, type, func_name, arg_errmsg, is_new, rettv);
      else
        semsg(_(e_listdictarg), func_name);
  
--- 2768,2776 ----
      }
  
      if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST)
!       list_extend_func(argvars, type, func_name, arg_errmsg, is_new, rettv);
      else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type == VAR_DICT)
!       dict_extend_func(argvars, type, func_name, arg_errmsg, is_new, rettv);
      else
        semsg(_(e_listdictarg), func_name);
  
***************
*** 3219,3234 ****
      extend(argvars, rettv, errmsg, TRUE);
  }
  
  /*
   * "insert()" function
   */
      void
  f_insert(typval_T *argvars, typval_T *rettv)
  {
-     long      before = 0;
-     listitem_T        *item;
-     int               error = FALSE;
- 
      if (in_vim9script()
            && (check_for_list_or_blob_arg(argvars, 0) == FAIL
                || (argvars[0].v_type == VAR_BLOB
--- 2802,2854 ----
      extend(argvars, rettv, errmsg, TRUE);
  }
  
+     static void
+ list_insert_func(typval_T *argvars, typval_T *rettv)
+ {
+     list_T    *l = argvars[0].vval.v_list;
+     long      before = 0;
+     listitem_T        *item;
+     int               error = FALSE;
+ 
+     if (l == NULL)
+     {
+       if (in_vim9script())
+           emsg(_(e_cannot_add_to_null_list));
+       return;
+     }
+ 
+     if (value_check_lock(l->lv_lock, (char_u *)N_("insert() argument"), TRUE))
+       return;
+ 
+     if (argvars[2].v_type != VAR_UNKNOWN)
+       before = (long)tv_get_number_chk(&argvars[2], &error);
+     if (error)
+       return;         // type error; errmsg already given
+ 
+     if (before == l->lv_len)
+       item = NULL;
+     else
+     {
+       item = list_find(l, before);
+       if (item == NULL)
+       {
+           semsg(_(e_listidx), before);
+           l = NULL;
+       }
+     }
+     if (l != NULL)
+     {
+       (void)list_insert_tv(l, &argvars[1], item);
+       copy_tv(&argvars[0], rettv);
+     }
+ }
+ 
  /*
   * "insert()" function
   */
      void
  f_insert(typval_T *argvars, typval_T *rettv)
  {
      if (in_vim9script()
            && (check_for_list_or_blob_arg(argvars, 0) == FAIL
                || (argvars[0].v_type == VAR_BLOB
***************
*** 3237,3324 ****
        return;
  
      if (argvars[0].v_type == VAR_BLOB)
!     {
!       blob_T  *b = argvars[0].vval.v_blob;
! 
!       if (b == NULL)
!       {
!           if (in_vim9script())
!               emsg(_(e_cannot_add_to_null_blob));
!       }
!       else if (!value_check_lock(b->bv_lock,
!                                    (char_u *)N_("insert() argument"), TRUE))
!       {
!           int         val, len;
!           char_u      *p;
! 
!           len = blob_len(b);
!           if (argvars[2].v_type != VAR_UNKNOWN)
!           {
!               before = (long)tv_get_number_chk(&argvars[2], &error);
!               if (error)
!                   return;             // type error; errmsg already given
!               if (before < 0 || before > len)
!               {
!                   semsg(_(e_invarg2), tv_get_string(&argvars[2]));
!                   return;
!               }
!           }
!           val = tv_get_number_chk(&argvars[1], &error);
!           if (error)
!               return;
!           if (val < 0 || val > 255)
!           {
!               semsg(_(e_invarg2), tv_get_string(&argvars[1]));
!               return;
!           }
! 
!           if (ga_grow(&b->bv_ga, 1) == FAIL)
!               return;
!           p = (char_u *)b->bv_ga.ga_data;
!           mch_memmove(p + before + 1, p + before, (size_t)len - before);
!           *(p + before) = val;
!           ++b->bv_ga.ga_len;
! 
!           copy_tv(&argvars[0], rettv);
!       }
!     }
      else if (argvars[0].v_type != VAR_LIST)
        semsg(_(e_listblobarg), "insert()");
      else
!     {
!       list_T  *l = argvars[0].vval.v_list;
! 
!       if (l == NULL)
!       {
!           if (in_vim9script())
!               emsg(_(e_cannot_add_to_null_list));
!       }
!       else if (!value_check_lock(l->lv_lock,
!                                    (char_u *)N_("insert() argument"), TRUE))
!       {
!           if (argvars[2].v_type != VAR_UNKNOWN)
!               before = (long)tv_get_number_chk(&argvars[2], &error);
!           if (error)
!               return;         // type error; errmsg already given
! 
!           if (before == l->lv_len)
!               item = NULL;
!           else
!           {
!               item = list_find(l, before);
!               if (item == NULL)
!               {
!                   semsg(_(e_listidx), before);
!                   l = NULL;
!               }
!           }
!           if (l != NULL)
!           {
!               (void)list_insert_tv(l, &argvars[1], item);
!               copy_tv(&argvars[0], rettv);
!           }
!       }
!     }
  }
  
  /*
--- 2857,2867 ----
        return;
  
      if (argvars[0].v_type == VAR_BLOB)
!       blob_insert_func(argvars, rettv);
      else if (argvars[0].v_type != VAR_LIST)
        semsg(_(e_listblobarg), "insert()");
      else
!       list_insert_func(argvars, rettv);
  }
  
  /*
***************
*** 3349,3414 ****
        semsg(_(e_listdictblobarg), "remove()");
  }
  
  /*
   * "reverse({list})" function
   */
      void
  f_reverse(typval_T *argvars, typval_T *rettv)
  {
-     list_T    *l;
-     listitem_T        *li, *ni;
- 
      if (in_vim9script() && check_for_list_or_blob_arg(argvars, 0) == FAIL)
        return;
  
      if (argvars[0].v_type == VAR_BLOB)
!     {
!       blob_T  *b = argvars[0].vval.v_blob;
!       int     i, len = blob_len(b);
! 
!       for (i = 0; i < len / 2; i++)
!       {
!           int tmp = blob_get(b, i);
! 
!           blob_set(b, i, blob_get(b, len - i - 1));
!           blob_set(b, len - i - 1, tmp);
!       }
!       rettv_blob_set(rettv, b);
!       return;
!     }
! 
!     if (argvars[0].v_type != VAR_LIST)
        semsg(_(e_listblobarg), "reverse()");
      else
!     {
!       l = argvars[0].vval.v_list;
!       rettv_list_set(rettv, l);
!       if (l != NULL
!           && !value_check_lock(l->lv_lock,
!                                   (char_u *)N_("reverse() argument"), TRUE))
!       {
!           if (l->lv_first == &range_list_item)
!           {
!               varnumber_T new_start = l->lv_u.nonmat.lv_start
!                                 + (l->lv_len - 1) * l->lv_u.nonmat.lv_stride;
!               l->lv_u.nonmat.lv_end = new_start
!                          - (l->lv_u.nonmat.lv_end - l->lv_u.nonmat.lv_start);
!               l->lv_u.nonmat.lv_start = new_start;
!               l->lv_u.nonmat.lv_stride = -l->lv_u.nonmat.lv_stride;
!               return;
!           }
!           li = l->lv_u.mat.lv_last;
!           l->lv_first = l->lv_u.mat.lv_last = NULL;
!           l->lv_len = 0;
!           while (li != NULL)
!           {
!               ni = li->li_prev;
!               list_append(l, li);
!               li = ni;
!           }
!           l->lv_u.mat.lv_idx = l->lv_len - l->lv_u.mat.lv_idx - 1;
!       }
!     }
  }
  
  /*
--- 2892,2945 ----
        semsg(_(e_listdictblobarg), "remove()");
  }
  
+     static void
+ list_reverse(list_T *l, typval_T *rettv)
+ {
+     listitem_T        *li, *ni;
+ 
+     rettv_list_set(rettv, l);
+     if (l != NULL
+           && !value_check_lock(l->lv_lock,
+               (char_u *)N_("reverse() argument"), TRUE))
+     {
+       if (l->lv_first == &range_list_item)
+       {
+           varnumber_T new_start = l->lv_u.nonmat.lv_start
+               + (l->lv_len - 1) * l->lv_u.nonmat.lv_stride;
+           l->lv_u.nonmat.lv_end = new_start
+               - (l->lv_u.nonmat.lv_end - l->lv_u.nonmat.lv_start);
+           l->lv_u.nonmat.lv_start = new_start;
+           l->lv_u.nonmat.lv_stride = -l->lv_u.nonmat.lv_stride;
+           return;
+       }
+       li = l->lv_u.mat.lv_last;
+       l->lv_first = l->lv_u.mat.lv_last = NULL;
+       l->lv_len = 0;
+       while (li != NULL)
+       {
+           ni = li->li_prev;
+           list_append(l, li);
+           li = ni;
+       }
+       l->lv_u.mat.lv_idx = l->lv_len - l->lv_u.mat.lv_idx - 1;
+     }
+ }
+ 
  /*
   * "reverse({list})" function
   */
      void
  f_reverse(typval_T *argvars, typval_T *rettv)
  {
      if (in_vim9script() && check_for_list_or_blob_arg(argvars, 0) == FAIL)
        return;
  
      if (argvars[0].v_type == VAR_BLOB)
!       blob_reverse(argvars[0].vval.v_blob, rettv);
!     else if (argvars[0].v_type != VAR_LIST)
        semsg(_(e_listblobarg), "reverse()");
      else
!       list_reverse(argvars[0].vval.v_list, rettv);
  }
  
  /*
***************
*** 3417,3423 ****
   * in 'rettv'.
   */
      static void
! reduce_list(
        typval_T        *argvars,
        char_u          *func_name,
        funcexe_T       *funcexe,
--- 2948,2954 ----
   * in 'rettv'.
   */
      static void
! list_reduce(
        typval_T        *argvars,
        char_u          *func_name,
        funcexe_T       *funcexe,
***************
*** 3471,3584 ****
  }
  
  /*
-  * reduce() String argvars[0] using the function 'funcname' with arguments in
-  * 'funcexe' starting with the initial value argvars[2] and return the result
-  * in 'rettv'.
-  */
-     static void
- reduce_string(
-       typval_T        *argvars,
-       char_u          *func_name,
-       funcexe_T       *funcexe,
-       typval_T        *rettv)
- {
-     char_u    *p = tv_get_string(&argvars[0]);
-     int               len;
-     typval_T  argv[3];
-     int               r;
-     int               called_emsg_start = called_emsg;
- 
-     if (argvars[2].v_type == VAR_UNKNOWN)
-     {
-       if (*p == NUL)
-       {
-           semsg(_(e_reduceempty), "String");
-           return;
-       }
-       if (tv_get_first_char(p, rettv) == FAIL)
-           return;
-       p += STRLEN(rettv->vval.v_string);
-     }
-     else if (argvars[2].v_type != VAR_STRING)
-     {
-       semsg(_(e_string_expected_for_argument_nr), 3);
-       return;
-     }
-     else
-       copy_tv(&argvars[2], rettv);
- 
-     for ( ; *p != NUL; p += len)
-     {
-       argv[0] = *rettv;
-       if (tv_get_first_char(p, &argv[1]) == FAIL)
-           break;
-       len = (int)STRLEN(argv[1].vval.v_string);
-       r = call_func(func_name, -1, rettv, 2, argv, funcexe);
-       clear_tv(&argv[0]);
-       clear_tv(&argv[1]);
-       if (r == FAIL || called_emsg != called_emsg_start)
-           return;
-     }
- }
- 
- /*
-  * reduce() Blob argvars[0] using the function 'funcname' with arguments in
-  * 'funcexe' starting with the initial value argvars[2] and return the result
-  * in 'rettv'.
-  */
-     static void
- reduce_blob(
-       typval_T        *argvars,
-       char_u          *func_name,
-       funcexe_T       *funcexe,
-       typval_T        *rettv)
- {
-     blob_T    *b = argvars[0].vval.v_blob;
-     int               called_emsg_start = called_emsg;
-     int               r;
-     typval_T  initial;
-     typval_T  argv[3];
-     int       i;
- 
-     if (argvars[2].v_type == VAR_UNKNOWN)
-     {
-       if (b == NULL || b->bv_ga.ga_len == 0)
-       {
-           semsg(_(e_reduceempty), "Blob");
-           return;
-       }
-       initial.v_type = VAR_NUMBER;
-       initial.vval.v_number = blob_get(b, 0);
-       i = 1;
-     }
-     else if (argvars[2].v_type != VAR_NUMBER)
-     {
-       emsg(_(e_number_expected));
-       return;
-     }
-     else
-     {
-       initial = argvars[2];
-       i = 0;
-     }
- 
-     copy_tv(&initial, rettv);
-     if (b == NULL)
-       return;
- 
-     for ( ; i < b->bv_ga.ga_len; i++)
-     {
-       argv[0] = *rettv;
-       argv[1].v_type = VAR_NUMBER;
-       argv[1].vval.v_number = blob_get(b, i);
-       r = call_func(func_name, -1, rettv, 2, argv, funcexe);
-       clear_tv(&argv[0]);
-       if (r == FAIL || called_emsg != called_emsg_start)
-           return;
-     }
- }
- 
- /*
   * "reduce(list, { accumulator, element -> value } [, initial])" function
   * "reduce(blob, { accumulator, element -> value } [, initial])"
   * "reduce(string, { accumulator, element -> value } [, initial])"
--- 3002,3007 ----
***************
*** 3622,3632 ****
      funcexe.fe_partial = partial;
  
      if (argvars[0].v_type == VAR_LIST)
!       reduce_list(argvars, func_name, &funcexe, rettv);
      else if (argvars[0].v_type == VAR_STRING)
!       reduce_string(argvars, func_name, &funcexe, rettv);
      else
!       reduce_blob(argvars, func_name, &funcexe, rettv);
  }
  
  #endif // defined(FEAT_EVAL)
--- 3045,3055 ----
      funcexe.fe_partial = partial;
  
      if (argvars[0].v_type == VAR_LIST)
!       list_reduce(argvars, func_name, &funcexe, rettv);
      else if (argvars[0].v_type == VAR_STRING)
!       string_reduce(argvars, func_name, &funcexe, rettv);
      else
!       blob_reduce(argvars, func_name, &funcexe, rettv);
  }
  
  #endif // defined(FEAT_EVAL)
*** ../vim-8.2.3870/src/proto/blob.pro  2021-09-14 16:53:39.316540671 +0100
--- src/proto/blob.pro  2021-12-22 18:04:56.843931664 +0000
***************
*** 18,24 ****
--- 18,29 ----
  int check_blob_index(long bloblen, varnumber_T n1, int quiet);
  int check_blob_range(long bloblen, varnumber_T n1, varnumber_T n2, int quiet);
  int blob_set_range(blob_T *dest, long n1, long n2, typval_T *src);
+ void blob_add(typval_T *argvars, typval_T *rettv);
  void blob_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg);
+ void blob_filter_map(blob_T *blob_arg, filtermap_T filtermap, typval_T *expr, 
typval_T *rettv);
+ void blob_insert_func(typval_T *argvars, typval_T *rettv);
+ void blob_reduce(typval_T *argvars, char_u *func_name, funcexe_T *funcexe, 
typval_T *rettv);
+ void blob_reverse(blob_T *b, typval_T *rettv);
  void f_blob2list(typval_T *argvars, typval_T *rettv);
  void f_list2blob(typval_T *argvars, typval_T *rettv);
  /* vim: set ft=c : */
*** ../vim-8.2.3870/src/proto/dict.pro  2021-08-09 18:59:01.442811242 +0100
--- src/proto/dict.pro  2021-12-22 18:04:56.843931664 +0000
***************
*** 39,48 ****
  void dict_extend(dict_T *d1, dict_T *d2, char_u *action, char *func_name);
  dictitem_T *dict_lookup(hashitem_T *hi);
  int dict_equal(dict_T *d1, dict_T *d2, int ic, int recursive);
  void f_items(typval_T *argvars, typval_T *rettv);
  void f_keys(typval_T *argvars, typval_T *rettv);
  void f_values(typval_T *argvars, typval_T *rettv);
  void dict_set_items_ro(dict_T *di);
  void f_has_key(typval_T *argvars, typval_T *rettv);
- void dict_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg);
  /* vim: set ft=c : */
--- 39,51 ----
  void dict_extend(dict_T *d1, dict_T *d2, char_u *action, char *func_name);
  dictitem_T *dict_lookup(hashitem_T *hi);
  int dict_equal(dict_T *d1, dict_T *d2, int ic, int recursive);
+ long dict_count(dict_T *d, typval_T *needle, int ic);
+ void dict_extend_func(typval_T *argvars, type_T *type, char *func_name, 
char_u *arg_errmsg, int is_new, typval_T *rettv);
+ void dict_filter_map(dict_T *d, filtermap_T filtermap, type_T *argtype, char 
*func_name, char_u *arg_errmsg, typval_T *expr, typval_T *rettv);
+ void dict_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg);
  void f_items(typval_T *argvars, typval_T *rettv);
  void f_keys(typval_T *argvars, typval_T *rettv);
  void f_values(typval_T *argvars, typval_T *rettv);
  void dict_set_items_ro(dict_T *di);
  void f_has_key(typval_T *argvars, typval_T *rettv);
  /* vim: set ft=c : */
*** ../vim-8.2.3870/src/proto/list.pro  2021-08-11 20:49:19.626869328 +0100
--- src/proto/list.pro  2021-12-22 18:04:56.843931664 +0000
***************
*** 50,55 ****
--- 50,56 ----
  void f_list2str(typval_T *argvars, typval_T *rettv);
  void f_sort(typval_T *argvars, typval_T *rettv);
  void f_uniq(typval_T *argvars, typval_T *rettv);
+ int filter_map_one(typval_T *tv, typval_T *expr, filtermap_T filtermap, 
typval_T *newtv, int *remp);
  void f_filter(typval_T *argvars, typval_T *rettv);
  void f_map(typval_T *argvars, typval_T *rettv);
  void f_mapnew(typval_T *argvars, typval_T *rettv);
*** ../vim-8.2.3870/src/proto/strings.pro       2021-07-10 20:28:55.327050110 
+0100
--- src/proto/strings.pro       2021-12-22 18:04:56.847931657 +0000
***************
*** 21,26 ****
--- 21,29 ----
  int has_non_ascii(char_u *s);
  char_u *concat_str(char_u *str1, char_u *str2);
  char_u *string_quote(char_u *str, int function);
+ long string_count(char_u *haystack, char_u *needle, int ic);
+ void string_filter_map(char_u *str, filtermap_T filtermap, typval_T *expr, 
typval_T *rettv);
+ void string_reduce(typval_T *argvars, char_u *func_name, funcexe_T *funcexe, 
typval_T *rettv);
  void f_byteidx(typval_T *argvars, typval_T *rettv);
  void f_byteidxcomp(typval_T *argvars, typval_T *rettv);
  void f_charidx(typval_T *argvars, typval_T *rettv);
*** ../vim-8.2.3870/src/strings.c       2021-11-24 15:32:53.723778915 +0000
--- src/strings.c       2021-12-22 18:04:56.847931657 +0000
***************
*** 764,770 ****
  }
  
  #if defined(FEAT_EVAL) || defined(PROTO)
- 
  /*
   * Return string "str" in ' quotes, doubling ' characters.
   * If "str" is NULL an empty string is assumed.
--- 764,769 ----
***************
*** 809,814 ****
--- 808,992 ----
      return s;
  }
  
+ /*
+  * Count the number of times "needle" occurs in string "haystack". Case is
+  * ignored if "ic" is TRUE.
+  */
+     long
+ string_count(char_u *haystack, char_u *needle, int ic)
+ {
+     long      n = 0;
+     char_u    *p = haystack;
+     char_u    *next;
+ 
+     if (p == NULL || needle == NULL || *needle == NUL)
+       return 0;
+ 
+     if (ic)
+     {
+       size_t len = STRLEN(needle);
+ 
+       while (*p != NUL)
+       {
+           if (MB_STRNICMP(p, needle, len) == 0)
+           {
+               ++n;
+               p += len;
+           }
+           else
+               MB_PTR_ADV(p);
+       }
+     }
+     else
+       while ((next = (char_u *)strstr((char *)p, (char *)needle)) != NULL)
+       {
+           ++n;
+           p = next + STRLEN(needle);
+       }
+ 
+     return n;
+ }
+ 
+ /*
+  * Make a typval_T of the first character of "input" and store it in "output".
+  * Return OK or FAIL.
+  */
+     static int
+ copy_first_char_to_tv(char_u *input, typval_T *output)
+ {
+     char_u    buf[MB_MAXBYTES + 1];
+     int               len;
+ 
+     if (input == NULL || output == NULL)
+       return FAIL;
+ 
+     len = has_mbyte ? mb_ptr2len(input) : 1;
+     STRNCPY(buf, input, len);
+     buf[len] = NUL;
+     output->v_type = VAR_STRING;
+     output->vval.v_string = vim_strsave(buf);
+ 
+     return output->vval.v_string == NULL ? FAIL : OK;
+ }
+ 
+ /*
+  * Implementation of map() and filter() for a String. Apply "expr" to every
+  * character in string "str" and return the result in "rettv".
+  */
+     void
+ string_filter_map(
+       char_u          *str,
+       filtermap_T     filtermap,
+       typval_T        *expr,
+       typval_T        *rettv)
+ {
+     char_u    *p;
+     typval_T  tv;
+     garray_T  ga;
+     int               len = 0;
+     int               idx = 0;
+     int               rem;
+ 
+     rettv->v_type = VAR_STRING;
+     rettv->vval.v_string = NULL;
+ 
+     // set_vim_var_nr() doesn't set the type
+     set_vim_var_type(VV_KEY, VAR_NUMBER);
+ 
+     ga_init2(&ga, (int)sizeof(char), 80);
+     for (p = str; *p != NUL; p += len)
+     {
+       typval_T newtv;
+ 
+       if (copy_first_char_to_tv(p, &tv) == FAIL)
+           break;
+       len = (int)STRLEN(tv.vval.v_string);
+ 
+       set_vim_var_nr(VV_KEY, idx);
+       if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
+               || did_emsg)
+           break;
+       if (did_emsg)
+       {
+           clear_tv(&newtv);
+           clear_tv(&tv);
+           break;
+       }
+       else if (filtermap != FILTERMAP_FILTER)
+       {
+           if (newtv.v_type != VAR_STRING)
+           {
+               clear_tv(&newtv);
+               clear_tv(&tv);
+               emsg(_(e_stringreq));
+               break;
+           }
+           else
+               ga_concat(&ga, newtv.vval.v_string);
+       }
+       else if (!rem)
+           ga_concat(&ga, tv.vval.v_string);
+ 
+       clear_tv(&newtv);
+       clear_tv(&tv);
+ 
+       ++idx;
+     }
+     ga_append(&ga, NUL);
+     rettv->vval.v_string = ga.ga_data;
+ }
+ 
+ /*
+  * reduce() String argvars[0] using the function 'funcname' with arguments in
+  * 'funcexe' starting with the initial value argvars[2] and return the result
+  * in 'rettv'.
+  */
+     void
+ string_reduce(
+       typval_T        *argvars,
+       char_u          *func_name,
+       funcexe_T       *funcexe,
+       typval_T        *rettv)
+ {
+     char_u    *p = tv_get_string(&argvars[0]);
+     int               len;
+     typval_T  argv[3];
+     int               r;
+     int               called_emsg_start = called_emsg;
+ 
+     if (argvars[2].v_type == VAR_UNKNOWN)
+     {
+       if (*p == NUL)
+       {
+           semsg(_(e_reduceempty), "String");
+           return;
+       }
+       if (copy_first_char_to_tv(p, rettv) == FAIL)
+           return;
+       p += STRLEN(rettv->vval.v_string);
+     }
+     else if (argvars[2].v_type != VAR_STRING)
+     {
+       semsg(_(e_string_expected_for_argument_nr), 3);
+       return;
+     }
+     else
+       copy_tv(&argvars[2], rettv);
+ 
+     for ( ; *p != NUL; p += len)
+     {
+       argv[0] = *rettv;
+       if (copy_first_char_to_tv(p, &argv[1]) == FAIL)
+           break;
+       len = (int)STRLEN(argv[1].vval.v_string);
+       r = call_func(func_name, -1, rettv, 2, argv, funcexe);
+       clear_tv(&argv[0]);
+       clear_tv(&argv[1]);
+       if (r == FAIL || called_emsg != called_emsg_start)
+           return;
+     }
+ }
+ 
      static void
  byteidx(typval_T *argvars, typval_T *rettv, int comp UNUSED)
  {
***************
*** 1686,1694 ****
      rettv->vval.v_string = vim_strnsave(head, tail - head);
  }
  
- #endif
- 
- #if defined(FEAT_EVAL)
  static char *e_printf = N_("E766: Insufficient arguments for printf()");
  
  /*
--- 1864,1869 ----
***************
*** 1766,1771 ****
--- 1941,1947 ----
      return f;
  }
  # endif
+ 
  #endif
  
  #ifdef FEAT_FLOAT
*** ../vim-8.2.3870/src/structs.h       2021-12-14 18:14:34.129509139 +0000
--- src/structs.h       2021-12-22 18:04:56.847931657 +0000
***************
*** 4492,4494 ****
--- 4492,4502 ----
      int               sve_did_save;
      hashtab_T sve_hashtab;
  } save_v_event_T;
+ 
+ // Enum used by filter(), map() and mapnew()
+ typedef enum {
+     FILTERMAP_FILTER,
+     FILTERMAP_MAP,
+     FILTERMAP_MAPNEW
+ } filtermap_T;
+ 
*** ../vim-8.2.3870/src/testdir/test_filter_map.vim     2021-12-16 
08:21:05.302828003 +0000
--- src/testdir/test_filter_map.vim     2021-12-22 18:04:56.847931657 +0000
***************
*** 182,187 ****
--- 182,188 ----
      call assert_equal('', map('abc', LSTART i, x LMIDDLE '' LEND))
      call assert_equal('', map('', "v:val == 'a'"))
      call assert_equal('', map(test_null_string(), "v:val == 'a'"))
+     call assert_fails('echo map("abc", "10")', 'E928:')
    END
    call CheckLegacyAndVim9Success(lines)
  
*** ../vim-8.2.3870/src/testdir/test_listdict.vim       2021-12-19 
18:33:17.321954811 +0000
--- src/testdir/test_listdict.vim       2021-12-22 18:04:56.847931657 +0000
***************
*** 783,788 ****
--- 783,814 ----
    call CheckScriptFailure(lines, 'E741:')
  endfunc
  
+ " Lock one item in a list
+ func Test_list_item_lock_map()
+   let lines =<< trim END
+       VAR l = [99, 100, 101]
+       lockvar l[1]
+       call assert_fails("echo map(l, 'v:val + 200')", 'E741:')
+       call assert_equal([299, 100, 101], l)
+   END
+   " This won't work in a :def function
+   call CheckTransLegacySuccess(lines)
+   call CheckTransVim9Success(lines)
+ endfunc
+ 
+ " Lock one item in a dict
+ func Test_dict_item_lock_map()
+   let lines =<< trim END
+       VAR d = {'a': 99, 'b': 100, 'c': 101}
+       lockvar d.b
+       call assert_fails("echo map(d, 'v:val + 200')", 'E741:')
+       call assert_equal({'a': 299, 'b': 100, 'c': 101}, d)
+   END
+   " This won't work in a :def function
+   call CheckTransLegacySuccess(lines)
+   call CheckTransVim9Success(lines)
+ endfunc
+ 
  " No extend() after lock on dict item
  func Test_dict_lock_extend()
    let d = {'a': 99, 'b': 100}
*** ../vim-8.2.3870/src/testdir/test_sort.vim   2021-09-19 16:01:35.502496563 
+0100
--- src/testdir/test_sort.vim   2021-12-22 18:04:56.847931657 +0000
***************
*** 1548,1551 ****
--- 1548,1567 ----
    close!
  endfunc
  
+ " Test for sort() using a dict function
+ func Test_sort_using_dict_func()
+   func DictSort(a, b) dict
+     if self.order == 'reverse'
+       return a:b - a:a
+     else
+       return a:a - a:b
+     endif
+   endfunc
+   let d = #{order: ''}
+   call assert_equal([1, 2, 3], sort([2, 1, 3], 'DictSort', d))
+   let d = #{order: 'reverse'}
+   call assert_equal([3, 2, 1], sort([2, 1, 3], 'DictSort', d))
+   delfunc DictSort
+ endfunc
+ 
  " vim: shiftwidth=2 sts=2 expandtab
*** ../vim-8.2.3870/src/version.c       2021-12-22 15:17:43.258403793 +0000
--- src/version.c       2021-12-22 18:06:26.255766161 +0000
***************
*** 751,752 ****
--- 751,754 ----
  {   /* Add new patch number below this line */
+ /**/
+     3871,
  /**/

-- 
>From "know your smileys":
 :-{}   Too much lipstick

 /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
///                                                                      \\\
\\\        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/20211222182005.361431C0A5B%40moolenaar.net.

Raspunde prin e-mail lui