From 0be6f608e6490946c5aadf9b44e633f1f2541161 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Fri, 9 Jan 2026 09:45:49 -0800 Subject: [PATCH] Fix duplicate Iter in type being cached. --- tests/test_type_eval.py | 18 +++++++++++++++++- typemap/type_eval/_eval_typing.py | 6 +++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index 77829f4..ea851bc 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -640,7 +640,7 @@ def test_never_is(): assert d is True -def test_eval_iter(): +def test_eval_iter_01(): d = eval_typing(Iter[tuple[int, str]]) assert tuple(d) == (int, str) @@ -654,6 +654,22 @@ def test_eval_iter(): assert tuple(d) == () +type DuplicateTuple[T] = tuple[*[x for x in Iter[T]], *[x for x in Iter[T]]] +type ConcatTupleWithSelf[T] = ConcatTuples[T, T] + + +def test_eval_iter_02(): + # ensure iterating duplicate tuples can be iterated multiple times + d = eval_typing(ConcatTuples[tuple[int, str], tuple[int, str]]) + assert d == tuple[int, str, int, str] + + d = eval_typing(DuplicateTuple[tuple[int, str]]) + assert d == tuple[int, str, int, str] + + d = eval_typing(ConcatTupleWithSelf[tuple[int, str]]) + assert d == tuple[int, str, int, str] + + def test_eval_length_01(): d = eval_typing(Length[tuple[int, str]]) assert d == Literal[2] diff --git a/typemap/type_eval/_eval_typing.py b/typemap/type_eval/_eval_typing.py index bab4cce..a8ab683 100644 --- a/typemap/type_eval/_eval_typing.py +++ b/typemap/type_eval/_eval_typing.py @@ -1,5 +1,6 @@ import annotationlib +import collections.abc import contextlib import contextvars import dataclasses @@ -231,7 +232,10 @@ def _eval_types(obj: typing.Any, ctx: EvalContext): ctx.resolved |= {x: x for x in child_ctx.known_recursive_types.keys()} ctx.known_recursive_types |= child_ctx.known_recursive_types - ctx.resolved[obj] = evaled + # Don't cache iterators as they are stateful and can only be consumed once. + # This is important for Iter results that may be used multiple times. + if not isinstance(evaled, collections.abc.Iterator): + ctx.resolved[obj] = evaled return evaled