From: Hirohito Higashi Date: Tue, 24 Feb 2026 21:22:38 +0000 (+0000) Subject: patch 9.2.0049: Vim9: typename() wrong for lists/dicts/tuples with shared references X-Git-Tag: v9.2.0049^0 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b1d4b03058ca7f7c39959dcc8f9efae28ddad754;p=thirdparty%2Fvim.git 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 Signed-off-by: Christian Brabandt --- diff --git a/src/testdir/test_tuple.vim b/src/testdir/test_tuple.vim index 191546a5e4..26609a20fe 100644 --- a/src/testdir/test_tuple.vim +++ b/src/testdir/test_tuple.vim @@ -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, tuple>', ((1, 2),)->repeat(3)->typename()) + call assert_equal('list>', [(1, 2)]->repeat(3)->typename()) + END + call v9.CheckSourceLegacyAndVim9Success(lines) + " When a tuple item is used in a "for" loop, the type is tuple let lines =<< trim END vim9script diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim index d5521cf879..d3effd8342 100644 --- a/src/testdir/test_vim9_builtin.vim +++ b/src/testdir/test_vim9_builtin.vim @@ -4983,6 +4983,22 @@ def Test_typename() endif var l: list): any> = [function('min')] assert_equal('list): 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>', typename(circ_l))", + 'let circ_d = {}', + "let circ_d['self'] = circ_d", + "call assert_equal('dict>', 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>', [[" "]]->repeat(3)->typename()) + assert_equal('list>', [{'a': 1}]->repeat(3)->typename()) enddef def Test_undofile() diff --git a/src/version.c b/src/version.c index 478b9f1bc7..620e1a5bd9 100644 --- a/src/version.c +++ b/src/version.c @@ -734,6 +734,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 49, /**/ 48, /**/ diff --git a/src/vim9type.c b/src/vim9type.c index f6bf1b255d..be578f26b4 100644 --- a/src/vim9type.c +++ b/src/vim9type.c @@ -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); }