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