To: vim_dev@googlegroups.com Subject: Patch 8.2.4286 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.4286 Problem: Vim9: strict type checking after copy() and deepcopy(). Solution: Allow type to change after making a copy. (closes #9644) Files: src/eval.c, src/proto/eval.pro, src/dict.c, src/proto/dict.pro, src/list.c, src/proto/list.pro, src/evalfunc.c, src/vim9execute.c, src/vim9type.c, src/proto/vim9type.pro, src/evalvars.c, src/testdir/test_vim9_builtin.vim, src/testdir/test_vim9_assign.vim *** ../vim-8.2.4285/src/eval.c 2022-01-31 14:59:33.514943760 +0000 --- src/eval.c 2022-02-02 17:54:04.251948737 +0000 *************** *** 6160,6165 **** --- 6160,6166 ---- /* * 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,6174 **** --- 6170,6176 ---- typval_T *from, typval_T *to, int deep, + int top, int copyID) { static int recurse = 0; *************** *** 6207,6213 **** ++to->vval.v_list->lv_refcount; } else ! to->vval.v_list = list_copy(from->vval.v_list, deep, copyID); if (to->vval.v_list == NULL) ret = FAIL; break; --- 6209,6216 ---- ++to->vval.v_list->lv_refcount; } else ! to->vval.v_list = list_copy(from->vval.v_list, ! deep, top, copyID); if (to->vval.v_list == NULL) ret = FAIL; break; *************** *** 6226,6232 **** ++to->vval.v_dict->dv_refcount; } else ! to->vval.v_dict = dict_copy(from->vval.v_dict, deep, copyID); if (to->vval.v_dict == NULL) ret = FAIL; break; --- 6229,6236 ---- ++to->vval.v_dict->dv_refcount; } else ! to->vval.v_dict = dict_copy(from->vval.v_dict, ! deep, top, copyID); if (to->vval.v_dict == NULL) ret = FAIL; break; *** ../vim-8.2.4285/src/proto/eval.pro 2022-01-22 13:39:04.103476264 +0000 --- src/proto/eval.pro 2022-02-02 17:51:25.495814222 +0000 *************** *** 69,75 **** 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); 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); --- 69,75 ---- 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 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); *** ../vim-8.2.4285/src/dict.c 2022-01-08 16:19:18.497639954 +0000 --- src/dict.c 2022-02-02 17:59:22.971938304 +0000 *************** *** 284,294 **** /* * 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". * Returns NULL when out of memory. */ dict_T * ! dict_copy(dict_T *orig, int deep, int copyID) { dict_T *copy; dictitem_T *di; --- 284,294 ---- /* * 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 "top" and "copyID". * Returns NULL when out of memory. */ dict_T * ! dict_copy(dict_T *orig, int deep, int top, int copyID) { dict_T *copy; dictitem_T *di; *************** *** 306,311 **** --- 306,313 ---- 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,325 **** break; if (deep) { ! if (item_copy(&HI2DI(hi)->di_tv, &di->di_tv, deep, ! copyID) == FAIL) { vim_free(di); break; --- 320,327 ---- break; if (deep) { ! if (item_copy(&HI2DI(hi)->di_tv, &di->di_tv, ! deep, FALSE, copyID) == FAIL) { vim_free(di); break; *************** *** 1239,1245 **** { if (is_new) { ! d1 = dict_copy(d1, FALSE, get_copyID()); if (d1 == NULL) return; } --- 1241,1247 ---- { if (is_new) { ! d1 = dict_copy(d1, FALSE, TRUE, get_copyID()); if (d1 == NULL) return; } *** ../vim-8.2.4285/src/proto/dict.pro 2021-12-22 18:19:22.602372473 +0000 --- src/proto/dict.pro 2022-02-02 17:57:38.307973893 +0000 *************** *** 12,18 **** 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); 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); --- 12,18 ---- 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 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); *** ../vim-8.2.4285/src/list.c 2022-01-29 15:19:19.546172430 +0000 --- src/list.c 2022-02-02 18:00:52.295889380 +0000 *************** *** 1015,1021 **** if (make_copy) { ! l = list_copy(l, TRUE, get_copyID()); rettv->vval.v_list = l; if (l == NULL) return; --- 1015,1021 ---- if (make_copy) { ! l = list_copy(l, TRUE, TRUE, get_copyID()); rettv->vval.v_list = l; if (l == NULL) return; *************** *** 1102,1108 **** if (l1 == NULL) l = list_alloc(); else ! l = list_copy(l1, FALSE, 0); if (l == NULL) return FAIL; tv->v_type = VAR_LIST; --- 1102,1108 ---- if (l1 == NULL) l = list_alloc(); else ! l = list_copy(l1, FALSE, TRUE, 0); if (l == NULL) return FAIL; tv->v_type = VAR_LIST; *************** *** 1200,1210 **** /* * 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". * Returns NULL when out of memory. */ list_T * ! list_copy(list_T *orig, int deep, int copyID) { list_T *copy; listitem_T *item; --- 1200,1210 ---- /* * 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 "top" and "copyID". * Returns NULL when out of memory. */ list_T * ! list_copy(list_T *orig, int deep, int top, int copyID) { list_T *copy; listitem_T *item; *************** *** 1216,1222 **** copy = list_alloc(); if (copy != NULL) { ! copy->lv_type = alloc_type(orig->lv_type); if (copyID != 0) { // Do this before adding the items, because one of the items may --- 1216,1222 ---- copy = list_alloc(); if (copy != NULL) { ! 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,1239 **** break; if (deep) { ! if (item_copy(&item->li_tv, &ni->li_tv, deep, copyID) == FAIL) { vim_free(ni); break; --- 1233,1240 ---- break; if (deep) { ! if (item_copy(&item->li_tv, &ni->li_tv, ! deep, FALSE, copyID) == FAIL) { vim_free(ni); break; *************** *** 2701,2711 **** } l2 = argvars[1].vval.v_list; if ((is_new || !value_check_lock(l1->lv_lock, arg_errmsg, TRUE)) ! && l2 != NULL) { if (is_new) { ! l1 = list_copy(l1, FALSE, get_copyID()); if (l1 == NULL) return; } --- 2702,2712 ---- } l2 = argvars[1].vval.v_list; if ((is_new || !value_check_lock(l1->lv_lock, arg_errmsg, TRUE)) ! && l2 != NULL) { if (is_new) { ! l1 = list_copy(l1, FALSE, TRUE, get_copyID()); if (l1 == NULL) return; } *** ../vim-8.2.4285/src/proto/list.pro 2021-12-22 18:19:22.602372473 +0000 --- src/proto/list.pro 2022-02-02 17:57:42.895972899 +0000 *************** *** 39,45 **** 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); 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); --- 39,45 ---- 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 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); *** ../vim-8.2.4285/src/evalfunc.c 2022-02-01 12:11:53.443042004 +0000 --- src/evalfunc.c 2022-02-02 19:36:15.150679570 +0000 *************** *** 1170,1175 **** --- 1170,1202 ---- 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,1577 **** {"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}, {"cos", 1, 1, FEARG_1, arg1_float_or_nr, ret_float, FLOAT_FUNC(f_cos)}, {"cosh", 1, 1, FEARG_1, arg1_float_or_nr, --- 1598,1604 ---- {"confirm", 1, 4, FEARG_1, arg4_string_string_number_string, ret_number, f_confirm}, {"copy", 1, 1, FEARG_1, NULL, ! 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,1597 **** #endif }, {"deepcopy", 1, 2, FEARG_1, arg12_deepcopy, ! ret_first_arg, f_deepcopy}, {"delete", 1, 2, FEARG_1, arg2_string, ret_number_bool, f_delete}, {"deletebufline", 2, 3, FEARG_1, arg3_buffer_lnum_lnum, --- 1618,1624 ---- #endif }, {"deepcopy", 1, 2, FEARG_1, arg12_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,3303 **** static void f_copy(typval_T *argvars, typval_T *rettv) { ! item_copy(&argvars[0], rettv, FALSE, 0); } /* --- 3324,3330 ---- static void f_copy(typval_T *argvars, typval_T *rettv) { ! item_copy(&argvars[0], rettv, FALSE, TRUE, 0); } /* *************** *** 3439,3445 **** else { copyID = get_copyID(); ! item_copy(&argvars[0], rettv, TRUE, noref == 0 ? copyID : 0); } } --- 3466,3472 ---- else { copyID = get_copyID(); ! item_copy(&argvars[0], rettv, TRUE, TRUE, noref == 0 ? copyID : 0); } } *** ../vim-8.2.4285/src/vim9execute.c 2022-01-26 21:01:11.192928481 +0000 --- src/vim9execute.c 2022-02-02 19:16:08.883877194 +0000 *************** *** 4412,4418 **** == NULL) { SOURCING_LNUM = iptr->isn_lnum; ! 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 --- 4412,4419 ---- == NULL) { SOURCING_LNUM = iptr->isn_lnum; ! 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,4551 **** 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); ! } ! } break; case ISN_2BOOL: --- 4532,4538 ---- break; case ISN_SETTYPE: ! set_tv_type(STACK_TV_BOT(-1), iptr->isn_arg.type.ct_type); break; case ISN_2BOOL: *** ../vim-8.2.4285/src/vim9type.c 2022-02-01 12:11:53.443042004 +0000 --- src/vim9type.c 2022-02-02 19:16:41.391844832 +0000 *************** *** 102,107 **** --- 102,172 ---- 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) { *** ../vim-8.2.4285/src/proto/vim9type.pro 2022-02-01 12:11:53.443042004 +0000 --- src/proto/vim9type.pro 2022-02-02 19:17:20.839805247 +0000 *************** *** 2,7 **** --- 2,8 ---- 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); *** ../vim-8.2.4285/src/evalvars.c 2022-01-30 15:28:26.642295028 +0000 --- src/evalvars.c 2022-02-02 19:15:51.895893995 +0000 *************** *** 3695,3718 **** 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); ! } ! } ! } // ":const var = value" locks the value // ":final var = value" locks "var" --- 3695,3701 ---- free_tv_arg = FALSE; if (vim9script && type != NULL) ! set_tv_type(dest_tv, type); // ":const var = value" locks the value // ":final var = value" locks "var" *** ../vim-8.2.4285/src/testdir/test_vim9_builtin.vim 2022-02-01 12:11:53.447041944 +0000 --- src/testdir/test_vim9_builtin.vim 2022-02-02 19:38:14.762580654 +0000 *************** *** 720,725 **** --- 720,750 ---- 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 = [1, 2] + assert_equal([1, 2, 'x'], nl->copy()->extend(['x'])) + + var lines =<< trim END + var nll: list> = [[1, 2]] + nll->copy()[0]->extend(['x']) + END + v9.CheckDefExecAndScriptFailure(lines, 'E1013: Argument 2: type mismatch, expected list but got list in extend()') + + var nd: dict = {a: 1, b: 2} + assert_equal({a: 1, b: 2, c: 'x'}, nd->copy()->extend({c: 'x'})) + lines =<< trim END + var ndd: dict> = {a: {x: 1, y: 2}} + ndd->copy()['a']->extend({z: 'x'}) + END + v9.CheckDefExecAndScriptFailure(lines, 'E1013: Argument 2: type mismatch, expected dict but got dict in extend()') + + # after a deepcopy() the item type can also change + var nll: list> = [[1, 2]] + assert_equal([1, 2, 'x'], nll->deepcopy()[0]->extend(['x'])) + + var ndd: dict> = {a: {x: 1, y: 2}} + assert_equal({x: 1, y: 2, z: 'x'}, ndd->deepcopy()['a']->extend({z: 'x'})) enddef def Test_count() *** ../vim-8.2.4285/src/testdir/test_vim9_assign.vim 2022-01-30 15:28:26.642295028 +0000 --- src/testdir/test_vim9_assign.vim 2022-02-02 19:59:17.037341185 +0000 *************** *** 484,490 **** ->copy() ->copy() END ! v9.CheckDefFailure(lines, 'E1012:', 2) lines =<< trim END var x: any --- 484,490 ---- ->copy() ->copy() END ! v9.CheckDefExecFailure(lines, 'E1012:', 4) lines =<< trim END var x: any *** ../vim-8.2.4285/src/version.c 2022-02-02 16:20:22.390554553 +0000 --- src/version.c 2022-02-02 20:00:06.125290064 +0000 *************** *** 748,749 **** --- 748,751 ---- { /* Add new patch number below this line */ + /**/ + 4286, /**/ -- The budget process was invented by an alien race of sadistic beings who resemble large cats. (Scott Adams - The Dilbert principle) /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///