patch 9.2.0049: Vim9: typename() wrong for lists/dicts/tuples with shared references

Problem:  Vim9: typename() returns wrong type for lists/dicts/tuples
          with shared references (Mao-Yining).
Solution: Reset CopyID after processing the item so it can be
          re-inspected if encountered again via a different reference
          (Hirohito Higashi).

fixes:  #19490
closes: #19492

Signed-off-by: Hirohito Higashi <h.east.727@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Hirohito Higashi
2026-02-24 21:22:38 +00:00
committed by Christian Brabandt
parent 71cc1b12cd
commit b1d4b03058
4 changed files with 41 additions and 0 deletions
+8
View File
@@ -2238,6 +2238,14 @@ func Test_tuple_typename()
END
call v9.CheckSourceDefAndScriptSuccess(lines)
" Shared (non-circular) references must not be treated as circular.
" repeat() makes all elements point to the same inner tuple object.
let lines =<< trim END
call assert_equal('tuple<tuple<number, number>, tuple<number, number>, tuple<number, number>>', ((1, 2),)->repeat(3)->typename())
call assert_equal('list<tuple<number, number>>', [(1, 2)]->repeat(3)->typename())
END
call v9.CheckSourceLegacyAndVim9Success(lines)
" When a tuple item is used in a "for" loop, the type is tuple<any>
let lines =<< trim END
vim9script
+16
View File
@@ -4983,6 +4983,22 @@ def Test_typename()
endif
var l: list<func(list<number>): any> = [function('min')]
assert_equal('list<func(list<number>): any>', typename(l))
# Check that circular list/dict references don't cause infinite recursion.
# Use legacy script where lv_type is not set so the copyID mechanism is used.
v9.CheckSourceLegacySuccess([
'let circ_l = []',
'call add(circ_l, circ_l)',
"call assert_equal('list<list<any>>', typename(circ_l))",
'let circ_d = {}',
"let circ_d['self'] = circ_d",
"call assert_equal('dict<dict<any>>', typename(circ_d))",
])
# Shared (non-circular) references must not be treated as circular.
# repeat() makes all elements point to the same inner list/dict object.
assert_equal('list<list<string>>', [[" "]]->repeat(3)->typename())
assert_equal('list<dict<number>>', [{'a': 1}]->repeat(3)->typename())
enddef
def Test_undofile()
+2
View File
@@ -734,6 +734,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
49,
/**/
48,
/**/
+15
View File
@@ -612,6 +612,10 @@ list_typval2type(typval_T *tv, int copyID, garray_T *type_gap, int flags)
common_type(typval2type(&li->li_tv, copyID, type_gap, TVTT_DO_MEMBER),
member_type, &member_type, type_gap);
// Reset copyID so that a shared reference to this list (not a circular
// reference) can be processed again to get the correct type.
l->lv_copyID = 0;
return get_list_type(member_type, type_gap);
}
@@ -661,6 +665,9 @@ tuple_typval2type(typval_T *tv, int copyID, garray_T *type_gap, int flags)
if (ga_grow(&tuple_types_ga, 1) == FAIL)
{
ga_clear(&tuple_types_ga);
// Reset copyID so that a shared reference to this tuple can be
// processed again.
tuple->tv_copyID = 0;
return NULL;
}
((type_T **)tuple_types_ga.ga_data)[tuple_types_ga.ga_len] = type;
@@ -670,6 +677,10 @@ tuple_typval2type(typval_T *tv, int copyID, garray_T *type_gap, int flags)
type_T *tuple_type = get_tuple_type(&tuple_types_ga, type_gap);
ga_clear(&tuple_types_ga);
// Reset copyID so that a shared reference to this tuple (not a circular
// reference) can be processed again to get the correct type.
tuple->tv_copyID = 0;
return tuple_type;
}
@@ -716,6 +727,10 @@ dict_typval2type(typval_T *tv, int copyID, garray_T *type_gap, int flags)
common_type(typval2type(value, copyID, type_gap, TVTT_DO_MEMBER),
member_type, &member_type, type_gap);
// Reset copyID so that a shared reference to this dict (not a circular
// reference) can be processed again to get the correct type.
d->dv_copyID = 0;
return get_dict_type(member_type, type_gap);
}