]> granicus.if.org Git - vim/commitdiff
patch 8.2.1969: Vim9: map() may change the list or dict item type v8.2.1969
authorBram Moolenaar <Bram@vim.org>
Mon, 9 Nov 2020 17:31:39 +0000 (18:31 +0100)
committerBram Moolenaar <Bram@vim.org>
Mon, 9 Nov 2020 17:31:39 +0000 (18:31 +0100)
Problem:    Vim9: map() may change the list or dict item type.
Solution:   Add mapnew().

runtime/doc/eval.txt
runtime/doc/usr_41.txt
src/evalfunc.c
src/list.c
src/proto/list.pro
src/testdir/test_filter_map.vim
src/version.c

index b573a11bffdce549bf75072e3f345b5de5d5f126..c08b75bb8199133f01f1259e4d017d0a69085bf4 100644 (file)
@@ -2669,8 +2669,9 @@ maparg({name} [, {mode} [, {abbr} [, {dict}]]])
                                        rhs of mapping {name} in mode {mode}
 mapcheck({name} [, {mode} [, {abbr}]])
                                String  check for mappings matching {name}
-mapset({mode}, {abbr}, {dict})
-                               none    restore mapping from |maparg()| result
+mapnew({expr1}, {expr2})       List/Dict  like |map()| but creates a new List
+                                          or Dictionary
+mapset({mode}, {abbr}, {dict}) none    restore mapping from |maparg()| result
 match({expr}, {pat} [, {start} [, {count}]])
                                Number  position where {pat} matches in {expr}
 matchadd({group}, {pattern} [, {priority} [, {id} [, {dict}]]])
@@ -6987,9 +6988,14 @@ luaeval({expr} [, {expr}])                                       *luaeval()*
 <              {only available when compiled with the |+lua| feature}
 
 map({expr1}, {expr2})                                  *map()*
-               {expr1} must be a |List| or a |Dictionary|.
+               {expr1} must be a |List|, |Blob| or |Dictionary|.
                Replace each item in {expr1} with the result of evaluating
-               {expr2}.  {expr2} must be a |string| or |Funcref|.
+               {expr2}.  For a |Blob| each byte is replaced.
+               If the item type changes you may want to use |mapnew()| to
+               create a new List or Dictionary.  This is required when using
+               Vim9 script.
+
+               {expr2} must be a |string| or |Funcref|.
 
                If {expr2} is a |string|, inside {expr2} |v:val| has the value
                of the current item.  For a |Dictionary| |v:key| has the key
@@ -7024,11 +7030,11 @@ map({expr1}, {expr2})                                   *map()*
                |Dictionary| to remain unmodified make a copy first: >
                        :let tlist = map(copy(mylist), ' v:val . "\t"')
 
-<              Returns {expr1}, the |List| or |Dictionary| that was filtered.
-               When an error is encountered while evaluating {expr2} no
-               further items in {expr1} are processed.  When {expr2} is a
-               Funcref errors inside a function are ignored, unless it was
-               defined with the "abort" flag.
+<              Returns {expr1}, the |List|, |Blob| or |Dictionary| that was
+               filtered.  When an error is encountered while evaluating
+               {expr2} no further items in {expr1} are processed.  When
+               {expr2} is a Funcref errors inside a function are ignored,
+               unless it was defined with the "abort" flag.
 
                Can also be used as a |method|: >
                        mylist->map(expr2)
@@ -7137,7 +7143,13 @@ mapcheck({name} [, {mode} [, {abbr}]])                   *mapcheck()*
                        GetKey()->mapcheck('n')
 
 
-mapset({mode}, {abbr}, {dict})                         *mapset()*
+mapnew({expr1}, {expr2})                                       *mapnew()*
+               Like |map()| but instead of replacing items in {expr1} a new
+               List or Dictionary is created and returned.  {expr1} remains
+               unchanged.
+
+
+mapset({mode}, {abbr}, {dict})                                 *mapset()*
                Restore a mapping from a dictionary returned by |maparg()|.
                {mode} and {abbr} should be the same as for the call to
                |maparg()|. *E460*
index 387232b52eb3b4ba7b3112aaaa60636119bb218e..ea9cfbbecb63c9dd359d632d2ffdf0c168c68796 100644 (file)
@@ -644,6 +644,7 @@ List manipulation:                                  *list-functions*
        deepcopy()              make a full copy of a List
        filter()                remove selected items from a List
        map()                   change each List item
+       mapnew()                make a new List with changed items
        reduce()                reduce a List to a value
        sort()                  sort a List
        reverse()               reverse the order of a List
@@ -669,6 +670,7 @@ Dictionary manipulation:                            *dict-functions*
        extend()                add entries from one Dictionary to another
        filter()                remove selected entries from a Dictionary
        map()                   change each Dictionary entry
+       mapnew()                make a new Dictionary with changed items
        keys()                  get List of Dictionary keys
        values()                get List of Dictionary values
        items()                 get List of Dictionary key-value pairs
index 6a43120d480234708a10a0a6c546e8b38e6c4115..b5f1c00516391a21347879e5e667f35ddeb2f2d4 100644 (file)
@@ -479,7 +479,6 @@ ret_job(int argcount UNUSED, type_T **argtypes UNUSED)
 {
     return &t_job;
 }
-
     static type_T *
 ret_first_arg(int argcount, type_T **argtypes)
 {
@@ -487,6 +486,18 @@ ret_first_arg(int argcount, type_T **argtypes)
        return argtypes[0];
     return &t_void;
 }
+// for map(): returns first argument but item type may differ
+    static type_T *
+ret_first_cont(int argcount UNUSED, type_T **argtypes)
+{
+    if (argtypes[0]->tt_type == VAR_LIST)
+       return &t_list_any;
+    if (argtypes[0]->tt_type == VAR_DICT)
+       return &t_dict_any;
+    if (argtypes[0]->tt_type == VAR_BLOB)
+       return argtypes[0];
+    return &t_any;
+}
 
 /*
  * Used for getqflist(): returns list if there is no argument, dict if there is
@@ -1115,11 +1126,13 @@ static funcentry_T global_functions[] =
 #endif
                        },
     {"map",            2, 2, FEARG_1,      NULL,
-                       ret_any,            f_map},
+                       ret_first_cont,     f_map},
     {"maparg",         1, 4, FEARG_1,      NULL,
                        ret_maparg,         f_maparg},
     {"mapcheck",       1, 3, FEARG_1,      NULL,
                        ret_string,         f_mapcheck},
+    {"mapnew",         2, 2, FEARG_1,      NULL,
+                       ret_first_cont,     f_mapnew},
     {"mapset",         3, 3, FEARG_1,      NULL,
                        ret_void,           f_mapset},
     {"match",          2, 4, FEARG_1,      NULL,
index af12aea79dd1e6fd2978ecec287348acf768664e..866de187183b2305afebcb500f74cd74956a9c3e 100644 (file)
@@ -1903,38 +1903,42 @@ f_uniq(typval_T *argvars, typval_T *rettv)
     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, typval_T *expr, int map, int *remp)
+filter_map_one(
+       typval_T        *tv,        // original value
+       typval_T        *expr,      // callback
+       filtermap_T     filtermap,
+       typval_T        *newtv,     // for map() and mapnew(): new value
+       int             *remp)      // for filter(): remove flag
 {
-    typval_T   rettv;
     typval_T   argv[3];
     int                retval = FAIL;
 
     copy_tv(tv, get_vim_var_tv(VV_VAL));
     argv[0] = *get_vim_var_tv(VV_KEY);
     argv[1] = *get_vim_var_tv(VV_VAL);
-    if (eval_expr_typval(expr, argv, 2, &rettv) == FAIL)
+    if (eval_expr_typval(expr, argv, 2, newtv) == FAIL)
        goto theend;
-    if (map)
-    {
-       // map(): replace the list item value
-       clear_tv(tv);
-       rettv.v_lock = 0;
-       *tv = rettv;
-    }
-    else
+    if (filtermap == FILTERMAP_FILTER)
     {
        int         error = FALSE;
 
        // filter(): when expr is zero remove the item
        if (in_vim9script())
-           *remp = !tv2bool(&rettv);
+           *remp = !tv2bool(newtv);
        else
-           *remp = (tv_get_number_chk(&rettv, &error) == 0);
-       clear_tv(&rettv);
+           *remp = (tv_get_number_chk(newtv, &error) == 0);
+       clear_tv(newtv);
        // On type error, nothing has been removed; return FAIL to stop the
        // loop.  The error message was given by tv_get_number_chk().
        if (error)
@@ -1950,7 +1954,7 @@ theend:
  * Implementation of map() and filter().
  */
     static void
-filter_map(typval_T *argvars, typval_T *rettv, int map)
+filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
 {
     typval_T   *expr;
     listitem_T *li, *nli;
@@ -1962,30 +1966,53 @@ filter_map(typval_T *argvars, typval_T *rettv, int map)
     blob_T     *b = NULL;
     int                rem;
     int                todo;
-    char_u     *ermsg = (char_u *)(map ? "map()" : "filter()");
-    char_u     *arg_errmsg = (char_u *)(map ? N_("map() argument")
-                                  : N_("filter() argument"));
+    char_u     *ermsg = (char_u *)(filtermap == FILTERMAP_MAP ? "map()"
+                                 : filtermap == FILTERMAP_MAPNEW ? "mapnew()"
+                                 : "filter()");
+    char_u     *arg_errmsg = (char_u *)(filtermap == FILTERMAP_MAP
+                                                        ? N_("map() argument")
+                                      : filtermap == FILTERMAP_MAPNEW
+                                                     ? N_("mapnew() argument")
+                                                   : N_("filter() argument"));
     int                save_did_emsg;
     int                idx = 0;
 
-    // Always return the first argument, also on failure.
-    copy_tv(&argvars[0], rettv);
+    // map() and filter() return the first argument, also on failure.
+    if (filtermap != FILTERMAP_MAPNEW)
+       copy_tv(&argvars[0], rettv);
 
     if (argvars[0].v_type == VAR_BLOB)
     {
+       if (filtermap == FILTERMAP_MAPNEW)
+       {
+           rettv->v_type = VAR_BLOB;
+           rettv->vval.v_blob = NULL;
+       }
        if ((b = argvars[0].vval.v_blob) == NULL)
            return;
     }
     else if (argvars[0].v_type == VAR_LIST)
     {
+       if (filtermap == FILTERMAP_MAPNEW)
+       {
+           rettv->v_type = VAR_LIST;
+           rettv->vval.v_list = NULL;
+       }
        if ((l = argvars[0].vval.v_list) == NULL
-             || (!map && value_check_lock(l->lv_lock, arg_errmsg, TRUE)))
+             || (filtermap == FILTERMAP_FILTER
+                           && value_check_lock(l->lv_lock, arg_errmsg, TRUE)))
            return;
     }
     else if (argvars[0].v_type == VAR_DICT)
     {
+       if (filtermap == FILTERMAP_MAPNEW)
+       {
+           rettv->v_type = VAR_DICT;
+           rettv->vval.v_dict = NULL;
+       }
        if ((d = argvars[0].vval.v_dict) == NULL
-             || (!map && value_check_lock(d->dv_lock, arg_errmsg, TRUE)))
+             || (filtermap == FILTERMAP_FILTER
+                           && value_check_lock(d->dv_lock, arg_errmsg, TRUE)))
            return;
     }
     else
@@ -2014,8 +2041,16 @@ filter_map(typval_T *argvars, typval_T *rettv, int map)
        if (argvars[0].v_type == VAR_DICT)
        {
            int     prev_lock = d->dv_lock;
+           dict_T  *d_ret = NULL;
+
+           if (filtermap == FILTERMAP_MAPNEW)
+           {
+               if (rettv_dict_alloc(rettv) == FAIL)
+                   return;
+               d_ret = rettv->vval.v_dict;
+           }
 
-           if (map && d->dv_lock == 0)
+           if (filtermap != FILTERMAP_FILTER && d->dv_lock == 0)
                d->dv_lock = VAR_LOCKED;
            ht = &d->dv_hashtab;
            hash_lock(ht);
@@ -2024,22 +2059,44 @@ filter_map(typval_T *argvars, typval_T *rettv, int map)
            {
                if (!HASHITEM_EMPTY(hi))
                {
-                   int r;
+                   int         r;
+                   typval_T    newtv;
 
                    --todo;
                    di = HI2DI(hi);
-                   if (map && (value_check_lock(di->di_tv.v_lock,
+                   if (filtermap != FILTERMAP_FILTER
+                                        && (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);
-                   r = filter_map_one(&di->di_tv, expr, map, &rem);
+                   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 (!map && rem)
+                   }
+                   if (filtermap == FILTERMAP_MAP)
+                   {
+                       // 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;
@@ -2055,27 +2112,39 @@ filter_map(typval_T *argvars, typval_T *rettv, int map)
            int         i;
            typval_T    tv;
            varnumber_T val;
+           blob_T      *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, map, &rem) == FAIL || did_emsg)
+               if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
+                                                                  || did_emsg)
                    break;
-               if (tv.v_type != VAR_NUMBER)
+               if (newtv.v_type != VAR_NUMBER)
                {
+                   clear_tv(&newtv);
                    emsg(_(e_invalblob));
                    break;
                }
-               if (map)
+               if (filtermap != FILTERMAP_FILTER)
                {
-                   if (tv.vval.v_number != val)
-                       blob_set(b, i, tv.vval.v_number);
+                   if (newtv.vval.v_number != val)
+                       blob_set(b_ret, i, newtv.vval.v_number);
                }
                else if (rem)
                {
@@ -2091,24 +2160,47 @@ filter_map(typval_T *argvars, typval_T *rettv, int map)
        }
        else // argvars[0].v_type == VAR_LIST
        {
-           int prev_lock = l->lv_lock;
+           int     prev_lock = l->lv_lock;
+           list_T  *l_ret = NULL;
 
+           if (filtermap == FILTERMAP_MAPNEW)
+           {
+               if (rettv_list_alloc(rettv) == FAIL)
+                   return;
+               l_ret = rettv->vval.v_list;
+           }
            // set_vim_var_nr() doesn't set the type
            set_vim_var_type(VV_KEY, VAR_NUMBER);
 
            CHECK_LIST_MATERIALIZE(l);
-           if (map && l->lv_lock == 0)
+           if (filtermap != FILTERMAP_FILTER && l->lv_lock == 0)
                l->lv_lock = VAR_LOCKED;
            for (li = l->lv_first; li != NULL; li = nli)
            {
-               if (map && value_check_lock(li->li_tv.v_lock, arg_errmsg, TRUE))
+               typval_T newtv;
+
+               if (filtermap != FILTERMAP_FILTER
+                      && value_check_lock(li->li_tv.v_lock, arg_errmsg, TRUE))
                    break;
                nli = li->li_next;
                set_vim_var_nr(VV_KEY, idx);
-               if (filter_map_one(&li->li_tv, expr, map, &rem) == FAIL
-                                                                 || did_emsg)
+               if (filter_map_one(&li->li_tv, expr, filtermap,
+                                            &newtv, &rem) == FAIL || did_emsg)
                    break;
-               if (!map && rem)
+               if (filtermap == FILTERMAP_MAP)
+               {
+                   // map(): replace the list item value
+                   clear_tv(&li->li_tv);
+                   newtv.v_lock = 0;
+                   li->li_tv = newtv;
+               }
+               else if (filtermap == FILTERMAP_MAPNEW)
+               {
+                   // mapnew(): append the list item value
+                   if (list_append_tv_move(l_ret, &newtv) == FAIL)
+                       break;
+               }
+               else if (filtermap == FILTERMAP_FILTER && rem)
                    listitem_remove(l, li);
                ++idx;
            }
@@ -2128,7 +2220,7 @@ filter_map(typval_T *argvars, typval_T *rettv, int map)
     void
 f_filter(typval_T *argvars, typval_T *rettv)
 {
-    filter_map(argvars, rettv, FALSE);
+    filter_map(argvars, rettv, FILTERMAP_FILTER);
 }
 
 /*
@@ -2137,7 +2229,16 @@ f_filter(typval_T *argvars, typval_T *rettv)
     void
 f_map(typval_T *argvars, typval_T *rettv)
 {
-    filter_map(argvars, rettv, TRUE);
+    filter_map(argvars, rettv, FILTERMAP_MAP);
+}
+
+/*
+ * "mapnew()" function
+ */
+    void
+f_mapnew(typval_T *argvars, typval_T *rettv)
+{
+    filter_map(argvars, rettv, FILTERMAP_MAPNEW);
 }
 
 /*
index 5a2feea2e3a8caa45ffc88c7d113728077996f5b..26990509ff552804cf129beb6d2f52e6069e5143 100644 (file)
@@ -48,6 +48,7 @@ void f_sort(typval_T *argvars, typval_T *rettv);
 void f_uniq(typval_T *argvars, typval_T *rettv);
 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);
 void f_add(typval_T *argvars, typval_T *rettv);
 void f_count(typval_T *argvars, typval_T *rettv);
 void f_extend(typval_T *argvars, typval_T *rettv);
index e6587c23bdc4b22aa56d218b8a158eef99288465..e88f755b9d2531b837a1f4a186cbb084d6145947 100644 (file)
@@ -118,4 +118,25 @@ func Test_map_and_modify()
   call assert_fails('echo map(d, {k,v -> remove(d, k)})', 'E741:')
 endfunc
 
+func Test_mapnew_dict()
+  let din = #{one: 1, two: 2}
+  let dout = mapnew(din, {k, v -> string(v)})
+  call assert_equal(#{one: 1, two: 2}, din)
+  call assert_equal(#{one: '1', two: '2'}, dout)
+endfunc
+
+func Test_mapnew_list()
+  let lin = [1, 2, 3]
+  let lout = mapnew(lin, {k, v -> string(v)})
+  call assert_equal([1, 2, 3], lin)
+  call assert_equal(['1', '2', '3'], lout)
+endfunc
+
+func Test_mapnew_blob()
+  let bin = 0z123456
+  let bout = mapnew(bin, {k, v -> k == 1 ? 0x99 : v})
+  call assert_equal(0z123456, bin)
+  call assert_equal(0z129956, bout)
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
index 40f38516441394ccad65f8169b4d2ef5bfd4e7f3..f452d96dd172935578d198b43c89700bb40961d5 100644 (file)
@@ -750,6 +750,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1969,
 /**/
     1968,
 /**/