From 9618f9cf2e0ae1c9b65df6682529a2728e467ccc Mon Sep 17 00:00:00 2001 From: Henry Su Date: Thu, 15 Oct 2026 17:13:45 -0500 Subject: [PATCH] Treat provably empty slices of variadic tuples as empty Slicing a variadic tuple with literal start and stop on the same side (both non-negative or both negative) where start >= stop (or start <= stop with a negative step) always gives an empty tuple, whatever the tuple's length. TupleType.slice didn't handle this when the start fell after the unpack, so mypy reported "Ambiguous slice of a variadic tuple" and inferred Any. Return tuple[()] in that case instead. --- mypy/types.py | 10 +++++++++- test-data/unit/check-typevar-tuple.test | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/mypy/types.py b/mypy/types.py index a6df7bf2e452..c3ac2caf82bf 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -2941,7 +2941,15 @@ def slice( total = len(self.items) unpack_index = find_unpack_in_list(self.items) assert unpack_index is not None - if begin is None and end is None: + if ( + begin is not None + and end is not None + and (begin >= 0) == (end >= 0) + and (begin >= end if stride is None or stride > 0 else begin <= end) + ): + # Start and end count from the same side, so the slice is empty for any length. + slice_items = [] + elif begin is None and end is None: # We special-case this to support reversing variadic tuples. # General support for slicing is tricky, so we handle only simple cases. if stride == -1: diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index 5449613aa3c5..0056b2e88e60 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -1587,6 +1587,21 @@ bt = t[::0] # E: Slice step cannot be zero reveal_type(bt) # N: Revealed type is "builtins.tuple[Any, ...]" [builtins fixtures/tuple.pyi] +[case testVariadicTupleEmptySlice] +from typing import Unpack, TypeVarTuple + +Ts = TypeVarTuple("Ts") +def f(x: tuple[int, Unpack[Ts], str], y: tuple[int, Unpack[tuple[str, ...]]]) -> None: + reveal_type(x[2:1]) # N: Revealed type is "tuple[()]" + reveal_type(x[3:3]) # N: Revealed type is "tuple[()]" + reveal_type(x[-1:-2]) # N: Revealed type is "tuple[()]" + reveal_type(x[1:2:-1]) # N: Revealed type is "tuple[()]" + reveal_type(y[5:2]) # N: Revealed type is "tuple[()]" + reveal_type(y[-3:-3:2]) # N: Revealed type is "tuple[()]" + y[1:5] # E: Ambiguous slice of a variadic tuple + y[-2:-5:-1] # E: Ambiguous slice of a variadic tuple +[builtins fixtures/tuple.pyi] + [case testAnyVariadicTupleLenientUnpacking] from typing import Any, Unpack