]> granicus.if.org Git - vim/commitdiff
patch 8.2.4286: Vim9: strict type checking after copy() and deepcopy() v8.2.4286
authorBram Moolenaar <Bram@vim.org>
Wed, 2 Feb 2022 20:01:27 +0000 (20:01 +0000)
committerBram Moolenaar <Bram@vim.org>
Wed, 2 Feb 2022 20:01:27 +0000 (20:01 +0000)
Problem:    Vim9: strict type checking after copy() and deepcopy().
Solution:   Allow type to change after making a copy. (closes #9644)

14 files changed:
src/dict.c
src/eval.c
src/evalfunc.c
src/evalvars.c
src/list.c
src/proto/dict.pro
src/proto/eval.pro
src/proto/list.pro
src/proto/vim9type.pro
src/testdir/test_vim9_assign.vim
src/testdir/test_vim9_builtin.vim
src/version.c
src/vim9execute.c
src/vim9type.c

index 9c6b7d4532f8e802116c6ee26503209a9602f19c..06f38716b57ac22dea484fbb49fe5a8304d65734 100644 (file)
@@ -284,11 +284,11 @@ dictitem_free(dictitem_T *item)
 /*
  * Make a copy of dict "d".  Shallow if "deep" is FALSE.
  * The refcount of the new dict is set to 1.
- * See item_copy() for "copyID".
+ * See item_copy() for "top" and "copyID".
  * Returns NULL when out of memory.
  */
     dict_T *
-dict_copy(dict_T *orig, int deep, int copyID)
+dict_copy(dict_T *orig, int deep, int top, int copyID)
 {
     dict_T     *copy;
     dictitem_T *di;
@@ -306,6 +306,8 @@ dict_copy(dict_T *orig, int deep, int copyID)
            orig->dv_copyID = copyID;
            orig->dv_copydict = copy;
        }
+       copy->dv_type = alloc_type(top || deep ? &t_dict_any : orig->dv_type);
+
        todo = (int)orig->dv_hashtab.ht_used;
        for (hi = orig->dv_hashtab.ht_array; todo > 0 && !got_int; ++hi)
        {
@@ -318,8 +320,8 @@ dict_copy(dict_T *orig, int deep, int copyID)
                    break;
                if (deep)
                {
-                   if (item_copy(&HI2DI(hi)->di_tv, &di->di_tv, deep,
-                                                             copyID) == FAIL)
+                   if (item_copy(&HI2DI(hi)->di_tv, &di->di_tv,
+                                                 deep, FALSE, copyID) == FAIL)
                    {
                        vim_free(di);
                        break;
@@ -1239,7 +1241,7 @@ dict_extend_func(
     {
        if (is_new)
        {
-           d1 = dict_copy(d1, FALSE, get_copyID());
+           d1 = dict_copy(d1, FALSE, TRUE, get_copyID());
            if (d1 == NULL)
                return;
        }
index 076ba1fed3291a92548918bd37bfbd958012be92..b5ccc4bdfb8043bbb87872c6781227956ac93b62 100644 (file)
@@ -6160,6 +6160,7 @@ handle_subscript(
 /*
  * Make a copy of an item.
  * Lists and Dictionaries are also copied.  A deep copy if "deep" is set.
+ * "top" is TRUE for the toplevel of copy().
  * For deepcopy() "copyID" is zero for a full copy or the ID for when a
  * reference to an already copied list/dict can be used.
  * Returns FAIL or OK.
@@ -6169,6 +6170,7 @@ item_copy(
     typval_T   *from,
     typval_T   *to,
     int                deep,
+    int                top,
     int                copyID)
 {
     static int recurse = 0;
@@ -6207,7 +6209,8 @@ item_copy(
                ++to->vval.v_list->lv_refcount;
            }
            else
-               to->vval.v_list = list_copy(from->vval.v_list, deep, copyID);
+               to->vval.v_list = list_copy(from->vval.v_list,
+                                                           deep, top, copyID);
            if (to->vval.v_list == NULL)
                ret = FAIL;
            break;
@@ -6226,7 +6229,8 @@ item_copy(
                ++to->vval.v_dict->dv_refcount;
            }
            else
-               to->vval.v_dict = dict_copy(from->vval.v_dict, deep, copyID);
+               to->vval.v_dict = dict_copy(from->vval.v_dict,
+                                                           deep, top, copyID);
            if (to->vval.v_dict == NULL)
                ret = FAIL;
            break;
index 281bd70d34b7067940017cebfe5b5fc8ddaf9546..71632cd294e85a58b4d631332e2684ad5fce129c 100644 (file)
@@ -1170,6 +1170,33 @@ ret_first_arg(int argcount,
     return &t_void;
 }
     static type_T *
+ret_copy(int argcount,
+       type2_T *argtypes,
+       type_T  **decl_type)
+{
+    if (argcount > 0)
+    {
+       if (argtypes[0].type_decl != NULL)
+       {
+           if (argtypes[0].type_decl->tt_type == VAR_LIST)
+               *decl_type = &t_list_any;
+           else if (argtypes[0].type_decl->tt_type == VAR_DICT)
+               *decl_type = &t_dict_any;
+           else
+               *decl_type = argtypes[0].type_decl;
+       }
+       if (argtypes[0].type_curr != NULL)
+       {
+           if (argtypes[0].type_curr->tt_type == VAR_LIST)
+               return &t_list_any;
+           else if (argtypes[0].type_curr->tt_type == VAR_DICT)
+               return &t_dict_any;
+       }
+       return argtypes[0].type_curr;
+    }
+    return &t_void;
+}
+    static type_T *
 ret_extend(int argcount,
        type2_T *argtypes,
        type_T  **decl_type)
@@ -1571,7 +1598,7 @@ static funcentry_T global_functions[] =
     {"confirm",                1, 4, FEARG_1,      arg4_string_string_number_string,
                        ret_number,         f_confirm},
     {"copy",           1, 1, FEARG_1,      NULL,
-                       ret_first_arg,      f_copy},
+                       ret_copy,           f_copy},
     {"cos",            1, 1, FEARG_1,      arg1_float_or_nr,
                        ret_float,          FLOAT_FUNC(f_cos)},
     {"cosh",           1, 1, FEARG_1,      arg1_float_or_nr,
@@ -1591,7 +1618,7 @@ static funcentry_T global_functions[] =
 #endif
                        },
     {"deepcopy",       1, 2, FEARG_1,      arg12_deepcopy,
-                       ret_first_arg,      f_deepcopy},
+                       ret_copy,           f_deepcopy},
     {"delete",         1, 2, FEARG_1,      arg2_string,
                        ret_number_bool,    f_delete},
     {"deletebufline",  2, 3, FEARG_1,      arg3_buffer_lnum_lnum,
@@ -3297,7 +3324,7 @@ f_confirm(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
     static void
 f_copy(typval_T *argvars, typval_T *rettv)
 {
-    item_copy(&argvars[0], rettv, FALSE, 0);
+    item_copy(&argvars[0], rettv, FALSE, TRUE, 0);
 }
 
 /*
@@ -3439,7 +3466,7 @@ f_deepcopy(typval_T *argvars, typval_T *rettv)
     else
     {
        copyID = get_copyID();
-       item_copy(&argvars[0], rettv, TRUE, noref == 0 ? copyID : 0);
+       item_copy(&argvars[0], rettv, TRUE, TRUE, noref == 0 ? copyID : 0);
     }
 }
 
index f88be3e8b9642c03f1d8983dff5dd517e2750ac1..b3436067f1db3cf4cfd9774724a104db3489b96c 100644 (file)
@@ -3695,24 +3695,7 @@ set_var_const(
     free_tv_arg = FALSE;
 
     if (vim9script && type != NULL)
-    {
-       if (type->tt_type == VAR_DICT && dest_tv->vval.v_dict != NULL)
-       {
-           if (dest_tv->vval.v_dict->dv_type != type)
-           {
-               free_type(dest_tv->vval.v_dict->dv_type);
-               dest_tv->vval.v_dict->dv_type = alloc_type(type);
-           }
-       }
-       else if (type->tt_type == VAR_LIST && dest_tv->vval.v_list != NULL)
-       {
-           if (dest_tv->vval.v_list->lv_type != type)
-           {
-               free_type(dest_tv->vval.v_list->lv_type);
-               dest_tv->vval.v_list->lv_type = alloc_type(type);
-           }
-       }
-    }
+       set_tv_type(dest_tv, type);
 
     // ":const var = value" locks the value
     // ":final var = value" locks "var"
index ff7d5ab9ba476d0535d315e475957305da1b742a..86c16793f9cb4bb13f1976a5d86908e7bb4e3ca0 100644 (file)
@@ -1015,7 +1015,7 @@ flatten_common(typval_T *argvars, typval_T *rettv, int make_copy)
 
     if (make_copy)
     {
-       l = list_copy(l, TRUE, get_copyID());
+       l = list_copy(l, TRUE, TRUE, get_copyID());
        rettv->vval.v_list = l;
        if (l == NULL)
            return;
@@ -1102,7 +1102,7 @@ list_concat(list_T *l1, list_T *l2, typval_T *tv)
     if (l1 == NULL)
        l = list_alloc();
     else
-       l = list_copy(l1, FALSE, 0);
+       l = list_copy(l1, FALSE, TRUE, 0);
     if (l == NULL)
        return FAIL;
     tv->v_type = VAR_LIST;
@@ -1200,11 +1200,11 @@ list_slice_or_index(
 /*
  * Make a copy of list "orig".  Shallow if "deep" is FALSE.
  * The refcount of the new list is set to 1.
- * See item_copy() for "copyID".
+ * See item_copy() for "top" and "copyID".
  * Returns NULL when out of memory.
  */
     list_T *
-list_copy(list_T *orig, int deep, int copyID)
+list_copy(list_T *orig, int deep, int top, int copyID)
 {
     list_T     *copy;
     listitem_T *item;
@@ -1216,7 +1216,7 @@ list_copy(list_T *orig, int deep, int copyID)
     copy = list_alloc();
     if (copy != NULL)
     {
-       copy->lv_type = alloc_type(orig->lv_type);
+       copy->lv_type = alloc_type(top || deep ? &t_list_any: orig->lv_type);
        if (copyID != 0)
        {
            // Do this before adding the items, because one of the items may
@@ -1233,7 +1233,8 @@ list_copy(list_T *orig, int deep, int copyID)
                break;
            if (deep)
            {
-               if (item_copy(&item->li_tv, &ni->li_tv, deep, copyID) == FAIL)
+               if (item_copy(&item->li_tv, &ni->li_tv,
+                                                 deep, FALSE, copyID) == FAIL)
                {
                    vim_free(ni);
                    break;
@@ -2701,11 +2702,11 @@ list_extend_func(
     }
     l2 = argvars[1].vval.v_list;
     if ((is_new || !value_check_lock(l1->lv_lock, arg_errmsg, TRUE))
-           && l2 != NULL)
+                                                                && l2 != NULL)
     {
        if (is_new)
        {
-           l1 = list_copy(l1, FALSE, get_copyID());
+           l1 = list_copy(l1, FALSE, TRUE, get_copyID());
            if (l1 == NULL)
                return;
        }
index d6cf15226307629a39390bc39cfc6c1ea93fb99a..7db011d210176cc6ea8e761deabb78afcc995006 100644 (file)
@@ -12,7 +12,7 @@ void dict_free_items(int copyID);
 dictitem_T *dictitem_alloc(char_u *key);
 void dictitem_remove(dict_T *dict, dictitem_T *item);
 void dictitem_free(dictitem_T *item);
-dict_T *dict_copy(dict_T *orig, int deep, int copyID);
+dict_T *dict_copy(dict_T *orig, int deep, int top, int copyID);
 int dict_wrong_func_name(dict_T *d, typval_T *tv, char_u *name);
 int dict_add(dict_T *d, dictitem_T *item);
 int dict_add_number(dict_T *d, char *key, varnumber_T nr);
index abb9c565e2538ecca8f81f0c1e14f34b8906621c..8be58855d7eca8c553f7cb4e15b30d88175103fa 100644 (file)
@@ -69,7 +69,7 @@ int eval_isnamec(int c);
 int eval_isnamec1(int c);
 int eval_isdictc(int c);
 int handle_subscript(char_u **arg, char_u *name_start, typval_T *rettv, evalarg_T *evalarg, int verbose);
-int item_copy(typval_T *from, typval_T *to, int deep, int copyID);
+int item_copy(typval_T *from, typval_T *to, int deep, int top, int copyID);
 void echo_one(typval_T *rettv, int with_space, int *atstart, int *needclr);
 void ex_echo(exarg_T *eap);
 void ex_echohl(exarg_T *eap);
index 5b7414bce2006294b4b806a5917f03a513d5c7a2..468775e59ad92861a8c441353484b6727288ba88 100644 (file)
@@ -39,7 +39,7 @@ int list_extend(list_T *l1, list_T *l2, listitem_T *bef);
 int list_concat(list_T *l1, list_T *l2, typval_T *tv);
 list_T *list_slice(list_T *ol, long n1, long n2);
 int list_slice_or_index(list_T *list, int range, varnumber_T n1_arg, varnumber_T n2_arg, int exclusive, typval_T *rettv, int verbose);
-list_T *list_copy(list_T *orig, int deep, int copyID);
+list_T *list_copy(list_T *orig, int deep, int top, int copyID);
 void vimlist_remove(list_T *l, listitem_T *item, listitem_T *item2);
 char_u *list2string(typval_T *tv, int copyID, int restore_copyID);
 int list_join(garray_T *gap, list_T *l, char_u *sep, int echo_style, int restore_copyID, int copyID);
index 61e38e0786aa3613729d128f7e9aa7436af85f07..fb3cb8d1451e4d8f0ab5146d850f2fc2637e921d 100644 (file)
@@ -2,6 +2,7 @@
 void clear_type_list(garray_T *gap);
 type_T *alloc_type(type_T *type);
 void free_type(type_T *type);
+void set_tv_type(typval_T *tv, type_T *type);
 type_T *get_list_type(type_T *member_type, garray_T *type_gap);
 type_T *get_dict_type(type_T *member_type, garray_T *type_gap);
 type_T *alloc_func_type(type_T *ret_type, int argcount, garray_T *type_gap);
index e3606d81ea63729379f8a98787d06c2432c81768..73b331070f6f3a2f76c24f4136bbbf6dabade400 100644 (file)
@@ -484,7 +484,7 @@ def Test_assign_linebreak()
           ->copy()
           ->copy()
   END
-  v9.CheckDefFailure(lines, 'E1012:', 2)
+  v9.CheckDefExecFailure(lines, 'E1012:', 4)
 
   lines =<< trim END
       var x: any
index 248887f7b0176bfc48d7061df5071bfd10776835..f7a5006b4556496130ba67d322b357d19405c619 100644 (file)
@@ -720,6 +720,31 @@ def Test_copy_return_type()
   res->assert_equal(6)
 
   dl = deepcopy([1, 2, 3], true)
+
+  # after a copy() the type can change, but not the item itself
+  var nl: list<number> = [1, 2]
+  assert_equal([1, 2, 'x'], nl->copy()->extend(['x']))
+
+  var lines =<< trim END
+      var nll: list<list<number>> = [[1, 2]]
+      nll->copy()[0]->extend(['x'])
+  END
+  v9.CheckDefExecAndScriptFailure(lines, 'E1013: Argument 2: type mismatch, expected list<number> but got list<string> in extend()')
+
+  var nd: dict<number> = {a: 1, b: 2}
+  assert_equal({a: 1, b: 2, c: 'x'}, nd->copy()->extend({c: 'x'}))
+  lines =<< trim END
+      var ndd: dict<dict<number>> = {a: {x: 1, y: 2}}
+      ndd->copy()['a']->extend({z: 'x'})
+  END
+  v9.CheckDefExecAndScriptFailure(lines, 'E1013: Argument 2: type mismatch, expected dict<number> but got dict<string> in extend()')
+
+  # after a deepcopy() the item type can also change
+  var nll: list<list<number>> = [[1, 2]]
+  assert_equal([1, 2, 'x'], nll->deepcopy()[0]->extend(['x']))
+
+  var ndd: dict<dict<number>> = {a: {x: 1, y: 2}}
+  assert_equal({x: 1, y: 2, z: 'x'}, ndd->deepcopy()['a']->extend({z: 'x'}))
 enddef
 
 def Test_count()
index 2c623671c73e57d4e3a32362a29c6401b522bcc7..e6edb17be2a3354470553adc32c18f9982920a82 100644 (file)
@@ -746,6 +746,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    4286,
 /**/
     4285,
 /**/
index a45d7c5538972cf722745974d70ed5f5074cd8ac..cac8c519d904fee29fca15c588a31058bfa3fa16 100644 (file)
@@ -4412,7 +4412,8 @@ exec_instructions(ectx_T *ectx)
                                                                       == NULL)
                    {
                        SOURCING_LNUM = iptr->isn_lnum;
-                       semsg(_(e_key_not_present_in_dictionary), iptr->isn_arg.string);
+                       semsg(_(e_key_not_present_in_dictionary),
+                                                        iptr->isn_arg.string);
                        goto on_error;
                    }
                    // Put the dict used on the dict stack, it might be used by
@@ -4531,21 +4532,7 @@ exec_instructions(ectx_T *ectx)
                break;
 
            case ISN_SETTYPE:
-               {
-                   checktype_T *ct = &iptr->isn_arg.type;
-
-                   tv = STACK_TV_BOT(-1);
-                   if (tv->v_type == VAR_DICT && tv->vval.v_dict != NULL)
-                   {
-                       free_type(tv->vval.v_dict->dv_type);
-                       tv->vval.v_dict->dv_type = alloc_type(ct->ct_type);
-                   }
-                   else if (tv->v_type == VAR_LIST && tv->vval.v_list != NULL)
-                   {
-                       free_type(tv->vval.v_list->lv_type);
-                       tv->vval.v_list->lv_type = alloc_type(ct->ct_type);
-                   }
-               }
+               set_tv_type(STACK_TV_BOT(-1), iptr->isn_arg.type.ct_type);
                break;
 
            case ISN_2BOOL:
index f2a69d0ab515c287636109887cf21c2fd3ab2543..f184171c1131ad83785f8c19a5670cc7f5c90e3f 100644 (file)
@@ -102,6 +102,71 @@ free_type(type_T *type)
     vim_free(type);
 }
 
+/*
+ * Return TRUE if "type" is to be recursed into for setting the type.
+ */
+    static int
+set_tv_type_recurse(type_T *type)
+{
+    return type->tt_member != NULL
+               && (type->tt_member->tt_type == VAR_DICT
+                                      || type->tt_member->tt_type == VAR_LIST)
+               && type->tt_member->tt_member != NULL
+               && type->tt_member->tt_member != &t_any
+               && type->tt_member->tt_member != &t_unknown;
+}
+
+/*
+ * Set the type of "tv" to "type" if it is a list or dict.
+ */
+    void
+set_tv_type(typval_T *tv, type_T *type)
+{
+    if (tv->v_type == VAR_DICT && tv->vval.v_dict != NULL)
+    {
+       dict_T *d = tv->vval.v_dict;
+
+       if (d->dv_type != type)
+       {
+           free_type(d->dv_type);
+           d->dv_type = alloc_type(type);
+           if (set_tv_type_recurse(type))
+           {
+               int             todo = (int)d->dv_hashtab.ht_used;
+               hashitem_T      *hi;
+               dictitem_T      *di;
+
+               for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi)
+               {
+                   if (!HASHITEM_EMPTY(hi))
+                   {
+                       --todo;
+                       di = HI2DI(hi);
+                       set_tv_type(&di->di_tv, type->tt_member);
+                   }
+               }
+           }
+       }
+    }
+    else if (tv->v_type == VAR_LIST && tv->vval.v_list != NULL)
+    {
+       list_T *l = tv->vval.v_list;
+
+       if (l->lv_type != type)
+       {
+           free_type(l->lv_type);
+           l->lv_type = alloc_type(type);
+           if (l->lv_first != &range_list_item && set_tv_type_recurse(type))
+           {
+               listitem_T      *li;
+
+               FOR_ALL_LIST_ITEMS(l, li)
+                   set_tv_type(&li->li_tv, type->tt_member);
+           }
+       }
+    }
+}
+
     type_T *
 get_list_type(type_T *member_type, garray_T *type_gap)
 {