mirror of
https://github.com/vim/vim.git
synced 2025-09-29 04:34:16 -04:00
patch 8.2.4286: Vim9: strict type checking after copy() and deepcopy()
Problem: Vim9: strict type checking after copy() and deepcopy(). Solution: Allow type to change after making a copy. (closes #9644)
This commit is contained in:
12
src/dict.c
12
src/dict.c
@@ -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;
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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"
|
||||
|
17
src/list.c
17
src/list.c
@@ -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;
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
|
@@ -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()
|
||||
|
@@ -746,6 +746,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
4286,
|
||||
/**/
|
||||
4285,
|
||||
/**/
|
||||
|
@@ -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:
|
||||
|
@@ -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)
|
||||
{
|
||||
|
Reference in New Issue
Block a user