]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0049: Vim9: typename() wrong for lists/dicts/tuples with shared references v9.2.0049
authorHirohito Higashi <h.east.727@gmail.com>
Tue, 24 Feb 2026 21:22:38 +0000 (21:22 +0000)
committerChristian Brabandt <cb@256bit.org>
Tue, 24 Feb 2026 21:27:30 +0000 (21:27 +0000)
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>
src/testdir/test_tuple.vim
src/testdir/test_vim9_builtin.vim
src/version.c
src/vim9type.c

index 191546a5e4f9305edb1f28d3184c53229a0e9d07..26609a20fe0ce1d6e7ffc694d76c6e9a773abff2 100644 (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
index d5521cf879851509b6927cd4cb594d1ab7146474..d3effd83420e2b0a8c7b80c55bf103e3b58323fa 100644 (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()
index 478b9f1bc76b80d59a2fca3ee3d09609dd4c3def..620e1a5bd904710a08ef70a98a3d0ad5e6230f9d 100644 (file)
@@ -734,6 +734,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    49,
 /**/
     48,
 /**/
index f6bf1b255d594addad4ce5395f6707109d43d76c..be578f26b4db7bf4fd14cd1d9c7e197b31f9ebf9 100644 (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);
 }