diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index f46c8cb15c6f..55947a44daca 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1638,6 +1638,27 @@ def check_callable_call( callee.type_object().name, abstract_attributes, context ) + var_arg = callee.var_arg() + if var_arg and isinstance(var_arg.typ, UnpackType): + # It is hard to support multiple variadic unpacks (except for old-style *args: int), + # fail gracefully to avoid crashes later. + seen_unpack = False + for arg, arg_kind in zip(args, arg_kinds): + if arg_kind != ARG_STAR: + continue + arg_type = get_proper_type(self.accept(arg)) + if not isinstance(arg_type, TupleType) or any( + isinstance(t, UnpackType) for t in arg_type.items + ): + if seen_unpack: + self.msg.fail( + "Passing multiple variadic unpacks in a call is not supported", + context, + code=codes.CALL_ARG, + ) + return AnyType(TypeOfAny.from_error), callee + seen_unpack = True + formal_to_actual = map_actuals_to_formals( arg_kinds, arg_names, @@ -2403,7 +2424,7 @@ def check_argument_types( ] actual_kinds = [nodes.ARG_STAR] + [nodes.ARG_POS] * (len(actuals) - 1) - # TODO: can we really assert this? What if formal is just plain Unpack[Ts]? + # If we got here, the callee was previously inferred to have a suffix. assert isinstance(orig_callee_arg_type, UnpackType) assert isinstance(orig_callee_arg_type.type, ProperType) and isinstance( orig_callee_arg_type.type, TupleType @@ -2429,22 +2450,29 @@ def check_argument_types( inner_unpack = unpacked_type.items[inner_unpack_index] assert isinstance(inner_unpack, UnpackType) inner_unpacked_type = get_proper_type(inner_unpack.type) - # We assume heterogenous tuples are desugared earlier - assert isinstance(inner_unpacked_type, Instance) - assert inner_unpacked_type.type.fullname == "builtins.tuple" - callee_arg_types = ( - unpacked_type.items[:inner_unpack_index] - + [inner_unpacked_type.args[0]] - * (len(actuals) - len(unpacked_type.items) + 1) - + unpacked_type.items[inner_unpack_index + 1 :] - ) - callee_arg_kinds = [ARG_POS] * len(actuals) + if isinstance(inner_unpacked_type, TypeVarTupleType): + # This branch mimics the expanded_tuple case above but for + # the case where caller passed a single * unpacked tuple argument. + callee_arg_types = unpacked_type.items + callee_arg_kinds = [ + ARG_POS if i != inner_unpack_index else ARG_STAR + for i in range(len(unpacked_type.items)) + ] + else: + # We assume heterogeneous tuples are desugared earlier. + assert isinstance(inner_unpacked_type, Instance) + assert inner_unpacked_type.type.fullname == "builtins.tuple" + callee_arg_types = ( + unpacked_type.items[:inner_unpack_index] + + [inner_unpacked_type.args[0]] + * (len(actuals) - len(unpacked_type.items) + 1) + + unpacked_type.items[inner_unpack_index + 1 :] + ) + callee_arg_kinds = [ARG_POS] * len(actuals) elif isinstance(unpacked_type, TypeVarTupleType): callee_arg_types = [orig_callee_arg_type] callee_arg_kinds = [ARG_STAR] else: - # TODO: Any and Never can appear in Unpack (as a result of user error), - # fail gracefully here and elsewhere (and/or normalize them away). assert isinstance(unpacked_type, Instance) assert unpacked_type.type.fullname == "builtins.tuple" callee_arg_types = [unpacked_type.args[0]] * len(actuals) @@ -2456,8 +2484,10 @@ def check_argument_types( assert len(actual_types) == len(actuals) == len(actual_kinds) if len(callee_arg_types) != len(actual_types): - # TODO: Improve error message - self.chk.fail("Invalid number of arguments", context) + if len(actual_types) > len(callee_arg_types): + self.chk.msg.too_many_arguments(callee, context) + else: + self.chk.msg.too_few_arguments(callee, context, None) continue assert len(callee_arg_types) == len(actual_types) @@ -2762,11 +2792,17 @@ def infer_overload_return_type( ) is_match = not w.has_new_errors() if is_match: - # Return early if possible; otherwise record info so we can + # Return early if possible; otherwise record info, so we can # check for ambiguity due to 'Any' below. if not args_contain_any: return ret_type, infer_type - matches.append(typ) + p_infer_type = get_proper_type(infer_type) + if isinstance(p_infer_type, CallableType): + # Prefer inferred types if possible, this will avoid false triggers for + # Any-ambiguity caused by arguments with Any passed to generic overloads. + matches.append(p_infer_type) + else: + matches.append(typ) return_types.append(ret_type) inferred_types.append(infer_type) type_maps.append(m) @@ -4107,6 +4143,12 @@ def visit_index_with_type( # Visit the index, just to make sure we have a type for it available self.accept(index) + if isinstance(left_type, TupleType) and any( + isinstance(it, UnpackType) for it in left_type.items + ): + # Normalize variadic tuples for consistency. + left_type = expand_type(left_type, {}) + if isinstance(left_type, UnionType): original_type = original_type or left_type # Don't combine literal types, since we may need them for type narrowing. @@ -4127,12 +4169,15 @@ def visit_index_with_type( if ns is not None: out = [] for n in ns: - if n < 0: - n += len(left_type.items) - if 0 <= n < len(left_type.items): - out.append(left_type.items[n]) + item = self.visit_tuple_index_helper(left_type, n) + if item is not None: + out.append(item) else: self.chk.fail(message_registry.TUPLE_INDEX_OUT_OF_RANGE, e) + if any(isinstance(t, UnpackType) for t in left_type.items): + self.chk.note( + f"Variadic tuple can have length {left_type.length() - 1}", e + ) return AnyType(TypeOfAny.from_error) return make_simplified_union(out) else: @@ -4156,6 +4201,46 @@ def visit_index_with_type( e.method_type = method_type return result + def visit_tuple_index_helper(self, left: TupleType, n: int) -> Type | None: + unpack_index = find_unpack_in_list(left.items) + if unpack_index is None: + if n < 0: + n += len(left.items) + if 0 <= n < len(left.items): + return left.items[n] + return None + unpack = left.items[unpack_index] + assert isinstance(unpack, UnpackType) + unpacked = get_proper_type(unpack.type) + if isinstance(unpacked, TypeVarTupleType): + # Usually we say that TypeVarTuple can't be split, be in case of + # indexing it seems benign to just return the fallback item, similar + # to what we do when indexing a regular TypeVar. + middle = unpacked.tuple_fallback.args[0] + else: + assert isinstance(unpacked, Instance) + assert unpacked.type.fullname == "builtins.tuple" + middle = unpacked.args[0] + if n >= 0: + if n < unpack_index: + return left.items[n] + if n >= len(left.items) - 1: + # For tuple[int, *tuple[str, ...], int] we allow either index 0 or 1, + # since variadic item may have zero items. + return None + return UnionType.make_union( + [middle] + left.items[unpack_index + 1 : n + 2], left.line, left.column + ) + n += len(left.items) + if n <= 0: + # Similar to above, we only allow -1, and -2 for tuple[int, *tuple[str, ...], int] + return None + if n > unpack_index: + return left.items[n] + return UnionType.make_union( + left.items[n - 1 : unpack_index] + [middle], left.line, left.column + ) + def visit_tuple_slice_helper(self, left_type: TupleType, slic: SliceExpr) -> Type: begin: Sequence[int | None] = [None] end: Sequence[int | None] = [None] @@ -4181,7 +4266,11 @@ def visit_tuple_slice_helper(self, left_type: TupleType, slic: SliceExpr) -> Typ items: list[Type] = [] for b, e, s in itertools.product(begin, end, stride): - items.append(left_type.slice(b, e, s)) + item = left_type.slice(b, e, s) + if item is None: + self.chk.fail(message_registry.AMBIGUOUS_SLICE_OF_VARIADIC_TUPLE, slic) + return AnyType(TypeOfAny.from_error) + items.append(item) return make_simplified_union(items) def try_getting_int_literals(self, index: Expression) -> list[int] | None: @@ -4190,7 +4279,7 @@ def try_getting_int_literals(self, index: Expression) -> list[int] | None: Otherwise, returns None. Specifically, this function is guaranteed to return a list with - one or more ints if one one the following is true: + one or more ints if one the following is true: 1. 'expr' is a IntExpr or a UnaryExpr backed by an IntExpr 2. 'typ' is a LiteralType containing an int @@ -4221,11 +4310,30 @@ def try_getting_int_literals(self, index: Expression) -> list[int] | None: def nonliteral_tuple_index_helper(self, left_type: TupleType, index: Expression) -> Type: self.check_method_call_by_name("__getitem__", left_type, [index], [ARG_POS], context=index) # We could return the return type from above, but unions are often better than the join - union = make_simplified_union(left_type.items) + union = self.union_tuple_fallback_item(left_type) if isinstance(index, SliceExpr): return self.chk.named_generic_type("builtins.tuple", [union]) return union + def union_tuple_fallback_item(self, left_type: TupleType) -> Type: + # TODO: this duplicates logic in typeops.tuple_fallback(). + items = [] + for item in left_type.items: + if isinstance(item, UnpackType): + unpacked_type = get_proper_type(item.type) + if isinstance(unpacked_type, TypeVarTupleType): + unpacked_type = get_proper_type(unpacked_type.upper_bound) + if ( + isinstance(unpacked_type, Instance) + and unpacked_type.type.fullname == "builtins.tuple" + ): + items.append(unpacked_type.args[0]) + else: + raise NotImplementedError + else: + items.append(item) + return make_simplified_union(items) + def visit_typeddict_index_expr( self, td_type: TypedDictType, index: Expression, setitem: bool = False ) -> Type: diff --git a/mypy/constraints.py b/mypy/constraints.py index 0524e38f9643..764e6867a981 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -137,25 +137,38 @@ def infer_constraints_for_callable( unpack_type = callee.arg_types[i] assert isinstance(unpack_type, UnpackType) - # In this case we are binding all of the actuals to *args + # In this case we are binding all the actuals to *args, # and we want a constraint that the typevar tuple being unpacked # is equal to a type list of all the actuals. actual_types = [] + + unpacked_type = get_proper_type(unpack_type.type) + if isinstance(unpacked_type, TypeVarTupleType): + tuple_instance = unpacked_type.tuple_fallback + elif isinstance(unpacked_type, TupleType): + tuple_instance = unpacked_type.partial_fallback + else: + assert False, "mypy bug: unhandled constraint inference case" + for actual in actuals: actual_arg_type = arg_types[actual] if actual_arg_type is None: continue - actual_types.append( - mapper.expand_actual_type( - actual_arg_type, - arg_kinds[actual], - callee.arg_names[i], - callee.arg_kinds[i], - ) + expanded_actual = mapper.expand_actual_type( + actual_arg_type, arg_kinds[actual], callee.arg_names[i], callee.arg_kinds[i] ) - unpacked_type = get_proper_type(unpack_type.type) + if arg_kinds[actual] != ARG_STAR or isinstance( + get_proper_type(actual_arg_type), TupleType + ): + actual_types.append(expanded_actual) + else: + # If we are expanding an iterable inside * actual, append a homogeneous item instead + actual_types.append( + UnpackType(tuple_instance.copy_modified(args=[expanded_actual])) + ) + if isinstance(unpacked_type, TypeVarTupleType): constraints.append( Constraint( diff --git a/mypy/erasetype.py b/mypy/erasetype.py index 24471f918319..7231ede66c65 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -82,7 +82,9 @@ def visit_instance(self, t: Instance) -> ProperType: # Valid erasure for *Ts is *tuple[Any, ...], not just Any. if isinstance(tv, TypeVarTupleType): args.append( - tv.tuple_fallback.copy_modified(args=[AnyType(TypeOfAny.special_form)]) + UnpackType( + tv.tuple_fallback.copy_modified(args=[AnyType(TypeOfAny.special_form)]) + ) ) else: args.append(AnyType(TypeOfAny.special_form)) diff --git a/mypy/message_registry.py b/mypy/message_registry.py index 713ec2e3c759..d75a1fab1b66 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -83,6 +83,7 @@ def with_additional_msg(self, info: str) -> ErrorMessage: INCOMPATIBLE_TYPES_IN_CAPTURE: Final = ErrorMessage("Incompatible types in capture pattern") MUST_HAVE_NONE_RETURN_TYPE: Final = ErrorMessage('The return type of "{}" must be None') TUPLE_INDEX_OUT_OF_RANGE: Final = ErrorMessage("Tuple index out of range") +AMBIGUOUS_SLICE_OF_VARIADIC_TUPLE: Final = ErrorMessage("Ambiguous slice of a variadic tuple") INVALID_SLICE_INDEX: Final = ErrorMessage("Slice index must be an integer, SupportsIndex or None") CANNOT_INFER_LAMBDA_TYPE: Final = ErrorMessage("Cannot infer type of lambda") CANNOT_ACCESS_INIT: Final = ( diff --git a/mypy/types.py b/mypy/types.py index 22fcd601d6a0..189a1f953545 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -2418,14 +2418,53 @@ def copy_modified( items = self.items return TupleType(items, fallback, self.line, self.column) - def slice(self, begin: int | None, end: int | None, stride: int | None) -> TupleType: - return TupleType( - self.items[begin:end:stride], - self.partial_fallback, - self.line, - self.column, - self.implicit, - ) + def slice(self, begin: int | None, end: int | None, stride: int | None) -> TupleType | None: + if any(isinstance(t, UnpackType) for t in self.items): + 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: + # We special-case this to support reversing variadic tuples. + # General support for slicing is tricky, so we handle only simple cases. + if stride == -1: + slice_items = self.items[::-1] + elif stride is None or stride == 1: + slice_items = self.items + else: + return None + elif (begin is None or unpack_index >= begin >= 0) and ( + end is not None and unpack_index >= end >= 0 + ): + # Start and end are in the prefix, everything works in this case. + slice_items = self.items[begin:end:stride] + elif (begin is not None and unpack_index - total < begin < 0) and ( + end is None or unpack_index - total < end < 0 + ): + # Start and end are in the suffix, everything works in this case. + slice_items = self.items[begin:end:stride] + elif (begin is None or unpack_index >= begin >= 0) and ( + end is None or unpack_index - total < end < 0 + ): + # Start in the prefix, end in the suffix, we can support only trivial strides. + if stride is None or stride == 1: + slice_items = self.items[begin:end:stride] + else: + return None + elif (begin is not None and unpack_index - total < begin < 0) and ( + end is not None and unpack_index >= end >= 0 + ): + # Start in the suffix, end in the prefix, we can support only trivial strides. + if stride is None or stride == -1: + slice_items = self.items[begin:end:stride] + else: + return None + else: + # TODO: there some additional cases we can support for homogeneous variadic + # items, we can "eat away" finite number of items. + return None + else: + slice_items = self.items[begin:end:stride] + return TupleType(slice_items, self.partial_fallback, self.line, self.column, self.implicit) class TypedDictType(ProperType): diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 4546c7171856..13ae6dca0ac3 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -6501,8 +6501,7 @@ eggs = lambda: 'eggs' reveal_type(func(eggs)) # N: Revealed type is "def (builtins.str) -> builtins.str" spam: Callable[..., str] = lambda x, y: 'baz' -reveal_type(func(spam)) # N: Revealed type is "def (*Any, **Any) -> Any" - +reveal_type(func(spam)) # N: Revealed type is "def (*Any, **Any) -> builtins.str" [builtins fixtures/paramspec.pyi] [case testGenericOverloadOverlapWithType] diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index 391fa20db738..08fc4aaa4a47 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -1664,7 +1664,6 @@ def zip(*i: Iterable[Any]) -> Iterator[Tuple[Any, ...]]: ... def zip(i): ... def g(t: Tuple): - # Ideally, we'd infer that these are iterators of tuples - reveal_type(zip(*t)) # N: Revealed type is "typing.Iterator[Any]" - reveal_type(zip(t)) # N: Revealed type is "typing.Iterator[Any]" + reveal_type(zip(*t)) # N: Revealed type is "typing.Iterator[builtins.tuple[Any, ...]]" + reveal_type(zip(t)) # N: Revealed type is "typing.Iterator[Tuple[Any]]" [builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index d38d492fe9b2..e8d7966029e3 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -366,13 +366,25 @@ from typing_extensions import TypeVarTuple, Unpack Ts = TypeVarTuple("Ts") -# TODO: add less trivial tests with prefix/suffix etc. -# TODO: add tests that call with a type var tuple instead of just args. def args_to_tuple(*args: Unpack[Ts]) -> Tuple[Unpack[Ts]]: reveal_type(args) # N: Revealed type is "Tuple[Unpack[Ts`-1]]" - return args + reveal_type(args_to_tuple(1, *args)) # N: Revealed type is "Tuple[Literal[1]?, Unpack[Ts`-1]]" + reveal_type(args_to_tuple(*args, 'a')) # N: Revealed type is "Tuple[Unpack[Ts`-1], Literal['a']?]" + reveal_type(args_to_tuple(1, *args, 'a')) # N: Revealed type is "Tuple[Literal[1]?, Unpack[Ts`-1], Literal['a']?]" + args_to_tuple(*args, *args) # E: Passing multiple variadic unpacks in a call is not supported + ok = (1, 'a') + reveal_type(args_to_tuple(*ok, *ok)) # N: Revealed type is "Tuple[builtins.int, builtins.str, builtins.int, builtins.str]" + if int(): + return args + else: + return args_to_tuple(*args) reveal_type(args_to_tuple(1, 'a')) # N: Revealed type is "Tuple[Literal[1]?, Literal['a']?]" +vt: Tuple[int, ...] +reveal_type(args_to_tuple(1, *vt)) # N: Revealed type is "Tuple[Literal[1]?, Unpack[builtins.tuple[builtins.int, ...]]]" +reveal_type(args_to_tuple(*vt, 'a')) # N: Revealed type is "Tuple[Unpack[builtins.tuple[builtins.int, ...]], Literal['a']?]" +reveal_type(args_to_tuple(1, *vt, 'a')) # N: Revealed type is "Tuple[Literal[1]?, Unpack[builtins.tuple[builtins.int, ...]], Literal['a']?]" +args_to_tuple(*vt, *vt) # E: Passing multiple variadic unpacks in a call is not supported [builtins fixtures/tuple.pyi] [case testTypeVarTuplePep646TypeVarStarArgs] @@ -381,8 +393,17 @@ from typing_extensions import TypeVarTuple, Unpack Ts = TypeVarTuple("Ts") +def args_to_tuple(*args: Unpack[Ts]) -> Tuple[Unpack[Ts]]: + with_prefix_suffix(*args) # E: Too few arguments for "with_prefix_suffix" \ + # E: Argument 1 to "with_prefix_suffix" has incompatible type "*Tuple[Unpack[Ts]]"; expected "bool" + new_args = (True, "foo", *args, 5) + with_prefix_suffix(*new_args) + return args + def with_prefix_suffix(*args: Unpack[Tuple[bool, str, Unpack[Ts], int]]) -> Tuple[bool, str, Unpack[Ts], int]: reveal_type(args) # N: Revealed type is "Tuple[builtins.bool, builtins.str, Unpack[Ts`-1], builtins.int]" + reveal_type(args_to_tuple(*args)) # N: Revealed type is "Tuple[builtins.bool, builtins.str, Unpack[Ts`-1], builtins.int]" + reveal_type(args_to_tuple(1, *args, 'a')) # N: Revealed type is "Tuple[Literal[1]?, builtins.bool, builtins.str, Unpack[Ts`-1], builtins.int, Literal['a']?]" return args reveal_type(with_prefix_suffix(True, "bar", "foo", 5)) # N: Revealed type is "Tuple[builtins.bool, builtins.str, Literal['foo']?, builtins.int]" @@ -395,8 +416,7 @@ t = (True, "bar", "foo", 5) reveal_type(with_prefix_suffix(*t)) # N: Revealed type is "Tuple[builtins.bool, builtins.str, builtins.str, builtins.int]" reveal_type(with_prefix_suffix(True, *("bar", "foo"), 5)) # N: Revealed type is "Tuple[builtins.bool, builtins.str, Literal['foo']?, builtins.int]" -# TODO: handle list case -#reveal_type(with_prefix_suffix(True, "bar", *["foo1", "foo2"], 5)) +reveal_type(with_prefix_suffix(True, "bar", *["foo1", "foo2"], 5)) # N: Revealed type is "Tuple[builtins.bool, builtins.str, Unpack[builtins.tuple[builtins.str, ...]], builtins.int]" bad_t = (True, "bar") with_prefix_suffix(*bad_t) # E: Too few arguments for "with_prefix_suffix" @@ -434,7 +454,7 @@ reveal_type(C().foo2) # N: Revealed type is "def (*args: Unpack[Tuple[builtins. [case testTypeVarTuplePep646TypeVarStarArgsVariableLengthTuple] from typing import Tuple -from typing_extensions import Unpack +from typing_extensions import Unpack, TypeVarTuple def foo(*args: Unpack[Tuple[int, ...]]) -> None: reveal_type(args) # N: Revealed type is "builtins.tuple[builtins.int, ...]" @@ -442,11 +462,28 @@ def foo(*args: Unpack[Tuple[int, ...]]) -> None: foo(0, 1, 2) foo(0, 1, "bar") # E: Argument 3 to "foo" has incompatible type "str"; expected "int" - def foo2(*args: Unpack[Tuple[str, Unpack[Tuple[int, ...]], bool, bool]]) -> None: reveal_type(args) # N: Revealed type is "Tuple[builtins.str, Unpack[builtins.tuple[builtins.int, ...]], builtins.bool, builtins.bool]" - # TODO: generate an error - # reveal_type(args[1]) + reveal_type(args[1]) # N: Revealed type is "builtins.int" + +def foo3(*args: Unpack[Tuple[str, Unpack[Tuple[int, ...]], str, float]]) -> None: + reveal_type(args[0]) # N: Revealed type is "builtins.str" + reveal_type(args[1]) # N: Revealed type is "Union[builtins.int, builtins.str]" + reveal_type(args[2]) # N: Revealed type is "Union[builtins.int, builtins.str, builtins.float]" + args[3] # E: Tuple index out of range \ + # N: Variadic tuple can have length 3 + reveal_type(args[-1]) # N: Revealed type is "builtins.float" + reveal_type(args[-2]) # N: Revealed type is "builtins.str" + reveal_type(args[-3]) # N: Revealed type is "Union[builtins.str, builtins.int]" + args[-4] # E: Tuple index out of range \ + # N: Variadic tuple can have length 3 + reveal_type(args[::-1]) # N: Revealed type is "Tuple[builtins.float, builtins.str, Unpack[builtins.tuple[builtins.int, ...]], builtins.str]" + args[::2] # E: Ambiguous slice of a variadic tuple + args[:2] # E: Ambiguous slice of a variadic tuple + +Ts = TypeVarTuple("Ts") +def foo4(*args: Unpack[Tuple[str, Unpack[Ts], bool, bool]]) -> None: + reveal_type(args[1]) # N: Revealed type is "builtins.object" foo2("bar", 1, 2, 3, False, True) foo2(0, 1, 2, 3, False, True) # E: Argument 1 to "foo2" has incompatible type "int"; expected "str" @@ -908,7 +945,7 @@ def cons( return wrapped def star(f: Callable[[X], Y]) -> Callable[[Unpack[Tuple[X, ...]]], Tuple[Y, ...]]: - def wrapped(*xs: X): + def wrapped(*xs: X) -> Tuple[Y, ...]: if not xs: return nil() return cons(f, star(f))(*xs) @@ -1516,3 +1553,54 @@ def test(x: int, t: Tuple[T, ...]) -> Tuple[int, Unpack[Tuple[T, ...]]]: ... a: Any = test(42, ()) [builtins fixtures/tuple.pyi] + +[case testTypeVarTupleIndexTypeVar] +from typing import Any, List, Sequence, Tuple, TypeVar +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +def f(data: Sequence[Tuple[Unpack[Ts]]]) -> List[Any]: + return [d[0] for d in data] # E: Tuple index out of range \ + # N: Variadic tuple can have length 0 + +T = TypeVar("T") +def g(data: Sequence[Tuple[T, Unpack[Ts]]]) -> List[T]: + return [d[0] for d in data] # OK +[builtins fixtures/tuple.pyi] + +[case testTypeVarTupleOverloadMatch] +from typing import Any, Generic, overload, Tuple, TypeVar +from typing_extensions import TypeVarTuple, Unpack + +_Ts = TypeVarTuple("_Ts") +_T = TypeVar("_T") +_T2 = TypeVar("_T2") + +class Container(Generic[_T]): ... +class Array(Generic[Unpack[_Ts]]): ... + +@overload +def build(entity: Container[_T], /) -> Array[_T]: ... +@overload +def build(entity: Container[_T], entity2: Container[_T2], /) -> Array[_T, _T2]: ... +@overload +def build(*entities: Container[Any]) -> Array[Unpack[Tuple[Any, ...]]]: ... +def build(*entities: Container[Any]) -> Array[Unpack[Tuple[Any, ...]]]: + ... + +def test(a: Container[Any], b: Container[int], c: Container[str]): + reveal_type(build(a, b)) # N: Revealed type is "__main__.Array[Any, builtins.int]" + reveal_type(build(b, c)) # N: Revealed type is "__main__.Array[builtins.int, builtins.str]" +[builtins fixtures/tuple.pyi] + +[case testTypeVarTupleIndexOldStyleNonNormalizedAndNonLiteral] +from typing import Any, Tuple +from typing_extensions import Unpack + +t: Tuple[Unpack[Tuple[int, ...]]] +reveal_type(t[42]) # N: Revealed type is "builtins.int" +i: int +reveal_type(t[i]) # N: Revealed type is "builtins.int" +t1: Tuple[int, Unpack[Tuple[int, ...]]] +reveal_type(t1[i]) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi]