From 75316e7d80b878d64336162d26a695281bf8c8c6 Mon Sep 17 00:00:00 2001 From: adhavan <99407456+adhavan18@users.noreply.github.com> Date: Mon, 27 Jul 2026 16:49:50 +0530 Subject: [PATCH] Use a variadic tuple context to infer precise tuple types When a tuple expression is checked against a variadic tuple context, tuple_context_matches() only accepts the context if the structure lines up exactly, i.e. the star item sits at the same index as the context unpack. For (*a, "last") against tuple[str, *tuple[str, ...]] it does not, so no context was used, allow_precise_tuples stayed off, and the expression collapsed to tuple[str, ...] -- which is then not assignable back to the variadic context. Derive the 'is the context variadic' question separately from the exact structural match and use it to enable precise tuple inference. This is kept out of unpack_in_context, whose index bookkeeping still relies on a full structure match. Fixes #21476. --- mypy/checkexpr.py | 14 +++++++++++++- test-data/unit/check-typevar-tuple.test | 21 +++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 172d44555b946..fc11b5363358e 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -5348,8 +5348,20 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type: if type_context_items is not None: unpack_in_context = find_unpack_in_list(type_context_items) is not None seen_unpack_in_items = False + # A variadic tuple context justifies inferring a precise tuple type even when + # tuple_context_matches() rejected the context because the structure doesn't + # line up exactly (e.g. (*a, "last") against tuple[str, *tuple[str, ...]]). + # Without this, such an expression collapses to tuple[str, ...], which is not + # assignable back to the variadic context. Note this deliberately does not feed + # unpack_in_context, whose index bookkeeping below relies on a full match. + variadic_context = ( + isinstance(type_context, TupleType) + and find_unpack_in_list(type_context.items) is not None + ) allow_precise_tuples = ( - unpack_in_context or PRECISE_TUPLE_TYPES in self.chk.options.enable_incomplete_feature + unpack_in_context + or variadic_context + or PRECISE_TUPLE_TYPES in self.chk.options.enable_incomplete_feature ) # Infer item types. Give up if there's a star expression diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index 7db0fa6f4388c..79ed5c4b41752 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -1738,6 +1738,27 @@ vt = 1, *test(), 2 # OK, type context is used vt2 = 1, *test(), 2 # E: Need type annotation for "vt2" [builtins fixtures/tuple.pyi] +[case testVariadicTupleContextStarPositionMismatch] +from typing import Tuple +from typing_extensions import Unpack + +a: Tuple[str, ...] +b: Tuple[str, Unpack[Tuple[str, ...]]] + +# The star lines up with the context unpack. +b = ("first", *a) +# The star does not line up, but the context is still variadic, so the +# precise tuple type is inferred rather than collapsing to tuple[str, ...]. +b = (*a, "last") + +# A variadic context must not paper over genuinely incompatible item types. +c: Tuple[int, Unpack[Tuple[int, ...]]] +c = (*a, "last") # E: Incompatible types in assignment (expression has type "tuple[Unpack[tuple[str, ...]], str]", variable has type "tuple[int, Unpack[tuple[int, ...]]]") +# A fixed-length context still requires enough items. +d: Tuple[str, str, Unpack[Tuple[str, ...]]] +d = ("only",) # E: Incompatible types in assignment (expression has type "tuple[str]", variable has type "tuple[str, str, Unpack[tuple[str, ...]]]") +[builtins fixtures/tuple.pyi] + [case testVariadicTupleConcatenation] # flags: --enable-incomplete-feature=PreciseTupleTypes from typing import Tuple