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

Commit: 
https://github.com/vim/vim/commit/b1d4b03058ca7f7c39959dcc8f9efae28ddad754
Author: Hirohito Higashi <[email protected]>
Date:   Tue Feb 24 21:22:38 2026 +0000

    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 <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/src/testdir/test_tuple.vim b/src/testdir/test_tuple.vim
index 191546a5e..26609a20f 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<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
diff --git a/src/testdir/test_vim9_builtin.vim 
b/src/testdir/test_vim9_builtin.vim
index d5521cf87..d3effd834 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<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()
diff --git a/src/version.c b/src/version.c
index 478b9f1bc..620e1a5bd 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 f6bf1b255..be578f26b 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);
 }
 

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1vuzyo-00BOCl-GU%40256bit.org.

Raspunde prin e-mail lui