diff --git a/mypy/argmap.py b/mypy/argmap.py index ec8463fd06252..e6700c9f10928 100644 --- a/mypy/argmap.py +++ b/mypy/argmap.py @@ -14,6 +14,8 @@ Type, TypedDictType, TypeOfAny, + TypeVarTupleType, + UnpackType, get_proper_type, ) @@ -174,6 +176,7 @@ def expand_actual_type( actual_kind: nodes.ArgKind, formal_name: str | None, formal_kind: nodes.ArgKind, + allow_unpack: bool = False, ) -> Type: """Return the actual (caller) type(s) of a formal argument with the given kinds. @@ -189,6 +192,11 @@ def expand_actual_type( original_actual = actual_type actual_type = get_proper_type(actual_type) if actual_kind == nodes.ARG_STAR: + if isinstance(actual_type, TypeVarTupleType): + # This code path is hit when *Ts is passed to a callable and various + # special-handling didn't catch this. The best thing we can do is to use + # the upper bound. + actual_type = get_proper_type(actual_type.upper_bound) if isinstance(actual_type, Instance) and actual_type.args: from mypy.subtypes import is_subtype @@ -209,7 +217,20 @@ def expand_actual_type( self.tuple_index = 1 else: self.tuple_index += 1 - return actual_type.items[self.tuple_index - 1] + item = actual_type.items[self.tuple_index - 1] + if isinstance(item, UnpackType) and not allow_unpack: + # An upack item that doesn't have special handling, use upper bound as above. + unpacked = get_proper_type(item.type) + if isinstance(unpacked, TypeVarTupleType): + fallback = get_proper_type(unpacked.upper_bound) + else: + fallback = unpacked + assert ( + isinstance(fallback, Instance) + and fallback.type.fullname == "builtins.tuple" + ) + item = fallback.args[0] + return item elif isinstance(actual_type, ParamSpecType): # ParamSpec is valid in *args but it can't be unpacked. return actual_type diff --git a/mypy/checker.py b/mypy/checker.py index 95a65b0a8cd1a..507d9869f6381 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -205,10 +205,13 @@ TypeType, TypeVarId, TypeVarLikeType, + TypeVarTupleType, TypeVarType, UnboundType, UninhabitedType, UnionType, + UnpackType, + find_unpack_in_list, flatten_nested_unions, get_proper_type, get_proper_types, @@ -3430,6 +3433,37 @@ def is_assignable_slot(self, lvalue: Lvalue, typ: Type | None) -> bool: return all(self.is_assignable_slot(lvalue, u) for u in typ.items) return False + def flatten_rvalues(self, rvalues: list[Expression]) -> list[Expression]: + """Flatten expression list by expanding those * items that have tuple type. + + For each regular type item in the tuple type use a TempNode(), for an Unpack + item use a corresponding StarExpr(TempNode()). + """ + new_rvalues = [] + for rv in rvalues: + if not isinstance(rv, StarExpr): + new_rvalues.append(rv) + continue + typ = get_proper_type(self.expr_checker.accept(rv.expr)) + if not isinstance(typ, TupleType): + new_rvalues.append(rv) + continue + for t in typ.items: + if not isinstance(t, UnpackType): + new_rvalues.append(TempNode(t)) + else: + unpacked = get_proper_type(t.type) + if isinstance(unpacked, TypeVarTupleType): + fallback = unpacked.upper_bound + else: + assert ( + isinstance(unpacked, Instance) + and unpacked.type.fullname == "builtins.tuple" + ) + fallback = unpacked + new_rvalues.append(StarExpr(TempNode(fallback))) + return new_rvalues + def check_assignment_to_multiple_lvalues( self, lvalues: list[Lvalue], @@ -3439,18 +3473,16 @@ def check_assignment_to_multiple_lvalues( ) -> None: if isinstance(rvalue, (TupleExpr, ListExpr)): # Recursively go into Tuple or List expression rhs instead of - # using the type of rhs, because this allowed more fine grained + # using the type of rhs, because this allows more fine-grained # control in cases like: a, b = [int, str] where rhs would get # type List[object] rvalues: list[Expression] = [] iterable_type: Type | None = None last_idx: int | None = None - for idx_rval, rval in enumerate(rvalue.items): + for idx_rval, rval in enumerate(self.flatten_rvalues(rvalue.items)): if isinstance(rval, StarExpr): typs = get_proper_type(self.expr_checker.accept(rval.expr)) - if isinstance(typs, TupleType): - rvalues.extend([TempNode(typ) for typ in typs.items]) - elif self.type_is_iterable(typs) and isinstance(typs, Instance): + if self.type_is_iterable(typs) and isinstance(typs, Instance): if iterable_type is not None and iterable_type != self.iterable_item_type( typs, rvalue ): @@ -3517,8 +3549,32 @@ def check_assignment_to_multiple_lvalues( self.check_multi_assignment(lvalues, rvalue, context, infer_lvalue_type) def check_rvalue_count_in_assignment( - self, lvalues: list[Lvalue], rvalue_count: int, context: Context + self, + lvalues: list[Lvalue], + rvalue_count: int, + context: Context, + rvalue_unpack: int | None = None, ) -> bool: + if rvalue_unpack is not None: + if not any(isinstance(e, StarExpr) for e in lvalues): + self.fail("Variadic tuple unpacking requires a star target", context) + return False + if len(lvalues) > rvalue_count: + self.fail(message_registry.TOO_MANY_TARGETS_FOR_VARIADIC_UNPACK, context) + return False + left_star_index = next(i for i, lv in enumerate(lvalues) if isinstance(lv, StarExpr)) + left_prefix = left_star_index + left_suffix = len(lvalues) - left_star_index - 1 + right_prefix = rvalue_unpack + right_suffix = rvalue_count - rvalue_unpack - 1 + if left_suffix > right_suffix or left_prefix > right_prefix: + # Case of asymmetric unpack like: + # rv: tuple[int, *Ts, int, int] + # x, y, *xs, z = rv + # it is technically valid, but is tricky to reason about. + # TODO: support this (at least if the r.h.s. unpack is a homogeneous tuple). + self.fail(message_registry.TOO_MANY_TARGETS_FOR_VARIADIC_UNPACK, context) + return True if any(isinstance(lvalue, StarExpr) for lvalue in lvalues): if len(lvalues) - 1 > rvalue_count: self.msg.wrong_number_values_to_unpack(rvalue_count, len(lvalues) - 1, context) @@ -3552,6 +3608,13 @@ def check_multi_assignment( if len(relevant_items) == 1: rvalue_type = get_proper_type(relevant_items[0]) + if ( + isinstance(rvalue_type, TupleType) + and find_unpack_in_list(rvalue_type.items) is not None + ): + # Normalize for consistent handling with "old-style" homogeneous tuples. + rvalue_type = expand_type(rvalue_type, {}) + if isinstance(rvalue_type, AnyType): for lv in lvalues: if isinstance(lv, StarExpr): @@ -3663,7 +3726,10 @@ def check_multi_assignment_from_tuple( undefined_rvalue: bool, infer_lvalue_type: bool = True, ) -> None: - if self.check_rvalue_count_in_assignment(lvalues, len(rvalue_type.items), context): + rvalue_unpack = find_unpack_in_list(rvalue_type.items) + if self.check_rvalue_count_in_assignment( + lvalues, len(rvalue_type.items), context, rvalue_unpack=rvalue_unpack + ): star_index = next( (i for i, lv in enumerate(lvalues) if isinstance(lv, StarExpr)), len(lvalues) ) @@ -3708,12 +3774,37 @@ def check_multi_assignment_from_tuple( self.check_assignment(lv, self.temp_node(rv_type, context), infer_lvalue_type) if star_lv: list_expr = ListExpr( - [self.temp_node(rv_type, context) for rv_type in star_rv_types] + [ + self.temp_node(rv_type, context) + if not isinstance(rv_type, UnpackType) + else StarExpr(self.temp_node(rv_type.type, context)) + for rv_type in star_rv_types + ] ) list_expr.set_line(context) self.check_assignment(star_lv.expr, list_expr, infer_lvalue_type) for lv, rv_type in zip(right_lvs, right_rv_types): self.check_assignment(lv, self.temp_node(rv_type, context), infer_lvalue_type) + else: + # Store meaningful Any types for lvalues, errors are already given + # by check_rvalue_count_in_assignment() + if infer_lvalue_type: + for lv in lvalues: + if ( + isinstance(lv, NameExpr) + and isinstance(lv.node, Var) + and lv.node.type is None + ): + lv.node.type = AnyType(TypeOfAny.from_error) + elif isinstance(lv, StarExpr): + if ( + isinstance(lv.expr, NameExpr) + and isinstance(lv.expr.node, Var) + and lv.expr.node.type is None + ): + lv.expr.node.type = self.named_generic_type( + "builtins.list", [AnyType(TypeOfAny.from_error)] + ) def lvalue_type_for_inference(self, lvalues: list[Lvalue], rvalue_type: TupleType) -> Type: star_index = next( diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index c132b35e5a2a3..48fc2485e3260 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -95,6 +95,7 @@ YieldExpr, YieldFromExpr, ) +from mypy.options import TYPE_VAR_TUPLE from mypy.plugin import ( FunctionContext, FunctionSigContext, @@ -2510,7 +2511,11 @@ def check_argument_types( ) self.msg.invalid_keyword_var_arg(actual_type, is_mapping, context) expanded_actual = mapper.expand_actual_type( - actual_type, actual_kind, callee.arg_names[i], callee_arg_kind + actual_type, + actual_kind, + callee.arg_names[i], + callee_arg_kind, + allow_unpack=isinstance(callee_arg_type, UnpackType), ) check_arg( expanded_actual, @@ -3338,7 +3343,45 @@ def visit_op_expr(self, e: OpExpr) -> Type: if isinstance(proper_right_type, TupleType): right_radd_method = proper_right_type.partial_fallback.type.get("__radd__") if right_radd_method is None: - return self.concat_tuples(proper_left_type, proper_right_type) + # One cannot have two variadic items in the same tuple. + if ( + find_unpack_in_list(proper_left_type.items) is None + or find_unpack_in_list(proper_right_type.items) is None + ): + return self.concat_tuples(proper_left_type, proper_right_type) + elif ( + TYPE_VAR_TUPLE in self.chk.options.enable_incomplete_feature + and isinstance(proper_right_type, Instance) + and self.chk.type_is_iterable(proper_right_type) + ): + # Handle tuple[X, Y] + tuple[Z, ...] = tuple[X, Y, *tuple[Z, ...]]. + right_radd_method = proper_right_type.type.get("__radd__") + if ( + right_radd_method is None + and proper_left_type.partial_fallback.type.fullname == "builtins.tuple" + and find_unpack_in_list(proper_left_type.items) is None + ): + item_type = self.chk.iterable_item_type(proper_right_type, e) + mapped = self.chk.named_generic_type("builtins.tuple", [item_type]) + return proper_left_type.copy_modified( + items=proper_left_type.items + [UnpackType(mapped)] + ) + if TYPE_VAR_TUPLE in self.chk.options.enable_incomplete_feature: + # Handle tuple[X, ...] + tuple[Y, Z] = tuple[*tuple[X, ...], Y, Z]. + if ( + e.op == "+" + and isinstance(proper_left_type, Instance) + and proper_left_type.type.fullname == "builtins.tuple" + ): + proper_right_type = get_proper_type(self.accept(e.right)) + if ( + isinstance(proper_right_type, TupleType) + and proper_right_type.partial_fallback.type.fullname == "builtins.tuple" + and find_unpack_in_list(proper_right_type.items) is None + ): + return proper_right_type.copy_modified( + items=[UnpackType(proper_left_type)] + proper_right_type.items + ) if e.op in operators.op_methods: method = operators.op_methods[e.op] @@ -4721,6 +4764,19 @@ def check_lst_expr(self, e: ListExpr | SetExpr | TupleExpr, fullname: str, tag: )[0] return remove_instance_last_known_values(out) + def tuple_context_matches(self, expr: TupleExpr, ctx: TupleType) -> bool: + ctx_unpack_index = find_unpack_in_list(ctx.items) + if ctx_unpack_index is None: + # For fixed tuples accept everything that can possibly match, even if this + # requires all star items to be empty. + return len([e for e in expr.items if not isinstance(e, StarExpr)]) <= len(ctx.items) + # For variadic context, the only easy case is when structure matches exactly. + # TODO: try using tuple type context in more cases. + if len([e for e in expr.items if not isinstance(e, StarExpr)]) != 1: + return False + expr_star_index = next(i for i, lv in enumerate(expr.items) if isinstance(lv, StarExpr)) + return len(expr.items) == len(ctx.items) and ctx_unpack_index == expr_star_index + def visit_tuple_expr(self, e: TupleExpr) -> Type: """Type check a tuple expression.""" # Try to determine type context for type inference. @@ -4730,7 +4786,7 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type: tuples_in_context = [ t for t in get_proper_types(type_context.items) - if (isinstance(t, TupleType) and len(t.items) == len(e.items)) + if (isinstance(t, TupleType) and self.tuple_context_matches(e, t)) or is_named_instance(t, TUPLE_LIKE_INSTANCE_NAMES) ] if len(tuples_in_context) == 1: @@ -4740,7 +4796,7 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type: # more than one. Either way, we can't decide on a context. pass - if isinstance(type_context, TupleType): + if isinstance(type_context, TupleType) and self.tuple_context_matches(e, type_context): type_context_items = type_context.items elif type_context and is_named_instance(type_context, TUPLE_LIKE_INSTANCE_NAMES): assert isinstance(type_context, Instance) @@ -4751,6 +4807,11 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type: # items that match a position in e, and we'll worry about type # mismatches later. + unpack_in_context = False + 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 + # Infer item types. Give up if there's a star expression # that's not a Tuple. items: list[Type] = [] @@ -4763,12 +4824,44 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type: # TupleExpr, flatten it, so we can benefit from the # context? Counterargument: Why would anyone write # (1, *(2, 3)) instead of (1, 2, 3) except in a test? - tt = self.accept(item.expr) + if unpack_in_context: + # Note: this logic depends on full structure match in tuple_context_matches(). + assert type_context_items + ctx_item = type_context_items[j] + assert isinstance(ctx_item, UnpackType) + ctx = ctx_item.type + else: + ctx = None + tt = self.accept(item.expr, ctx) tt = get_proper_type(tt) if isinstance(tt, TupleType): + if find_unpack_in_list(tt.items) is not None: + if seen_unpack_in_items: + # Multiple unpack items are not allowed in tuples, + # fall back to instance type. + return self.check_lst_expr(e, "builtins.tuple", "") + else: + seen_unpack_in_items = True items.extend(tt.items) - j += len(tt.items) + # Note: this logic depends on full structure match in tuple_context_matches(). + if unpack_in_context: + j += 1 + else: + # If there is an unpack in expressions, but not in context, this will + # result in an error later, just do something predictable here. + j += len(tt.items) else: + if ( + TYPE_VAR_TUPLE in self.chk.options.enable_incomplete_feature + and not seen_unpack_in_items + ): + # Handle (x, *y, z), where y is e.g. tuple[Y, ...]. + if isinstance(tt, Instance) and self.chk.type_is_iterable(tt): + item_type = self.chk.iterable_item_type(tt, e) + mapped = self.chk.named_generic_type("builtins.tuple", [item_type]) + items.append(UnpackType(mapped)) + seen_unpack_in_items = True + continue # A star expression that's not a Tuple. # Treat the whole thing as a variable-length tuple. return self.check_lst_expr(e, "builtins.tuple", "") @@ -4781,7 +4874,13 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type: items.append(tt) # This is a partial fallback item type. A precise type will be calculated on demand. fallback_item = AnyType(TypeOfAny.special_form) - return TupleType(items, self.chk.named_generic_type("builtins.tuple", [fallback_item])) + result: ProperType = TupleType( + items, self.chk.named_generic_type("builtins.tuple", [fallback_item]) + ) + if seen_unpack_in_items: + # Return already normalized tuple type just in case. + result = expand_type(result, {}) + return result def fast_dict_type(self, e: DictExpr) -> Type | None: """ diff --git a/mypy/constraints.py b/mypy/constraints.py index ebd6765e8e82b..58d0f4dbed297 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -156,7 +156,11 @@ def infer_constraints_for_callable( continue expanded_actual = mapper.expand_actual_type( - actual_arg_type, arg_kinds[actual], callee.arg_names[i], callee.arg_kinds[i] + actual_arg_type, + arg_kinds[actual], + callee.arg_names[i], + callee.arg_kinds[i], + allow_unpack=True, ) if arg_kinds[actual] != ARG_STAR or isinstance( diff --git a/mypy/message_registry.py b/mypy/message_registry.py index d75a1fab1b66c..dc46eb5033909 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -84,6 +84,9 @@ def with_additional_msg(self, info: str) -> ErrorMessage: 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") +TOO_MANY_TARGETS_FOR_VARIADIC_UNPACK: Final = ErrorMessage( + "Too many assignment targets for variadic unpack" +) 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/mypyc/irbuild/mapper.py b/mypyc/irbuild/mapper.py index 5b77b4b1537b2..a3abbb1f84fb7 100644 --- a/mypyc/irbuild/mapper.py +++ b/mypyc/irbuild/mapper.py @@ -19,6 +19,7 @@ UnboundType, UninhabitedType, UnionType, + find_unpack_in_list, get_proper_type, ) from mypyc.ir.class_ir import ClassIR @@ -112,8 +113,11 @@ def type_to_rtype(self, typ: Type | None) -> RType: return object_rprimitive elif isinstance(typ, TupleType): # Use our unboxed tuples for raw tuples but fall back to - # being boxed for NamedTuple. - if typ.partial_fallback.type.fullname == "builtins.tuple": + # being boxed for NamedTuple or for variadic tuples. + if ( + typ.partial_fallback.type.fullname == "builtins.tuple" + and find_unpack_in_list(typ.items) is None + ): return RTuple([self.type_to_rtype(t) for t in typ.items]) else: return tuple_rprimitive diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index 9dfee38bc0c6f..85bea59e526af 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -1100,15 +1100,15 @@ reveal_type(b) # N: Revealed type is "Tuple[builtins.int, builtins.int, builtin [case testTupleWithStarExpr2] a = [1] b = (0, *a) -reveal_type(b) # N: Revealed type is "builtins.tuple[builtins.int, ...]" +reveal_type(b) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.int, ...]]]" [builtins fixtures/tuple.pyi] [case testTupleWithStarExpr3] a = [''] b = (0, *a) -reveal_type(b) # N: Revealed type is "builtins.tuple[builtins.object, ...]" +reveal_type(b) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.str, ...]]]" c = (*a, '') -reveal_type(c) # N: Revealed type is "builtins.tuple[builtins.str, ...]" +reveal_type(c) # N: Revealed type is "Tuple[Unpack[builtins.tuple[builtins.str, ...]], builtins.str]" [builtins fixtures/tuple.pyi] [case testTupleWithStarExpr4] @@ -1333,7 +1333,7 @@ reveal_type(subtup if int() else tup2) # N: Revealed type is "builtins.tuple[bu [case testTupleWithUndersizedContext] a = ([1], 'x') if int(): - a = ([], 'x', 1) # E: Incompatible types in assignment (expression has type "Tuple[List[int], str, int]", variable has type "Tuple[List[int], str]") + a = ([], 'x', 1) # E: Incompatible types in assignment (expression has type "Tuple[List[Never], str, int]", variable has type "Tuple[List[int], str]") [builtins fixtures/tuple.pyi] [case testTupleWithOversizedContext] diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index 850b7ef8a5244..0212518bdec01 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -1527,6 +1527,198 @@ x = c1 x = c2 [builtins fixtures/tuple.pyi] +[case testUnpackingVariadicTuplesTypeVar] +from typing import Tuple +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +def foo(arg: Tuple[int, Unpack[Ts], str]) -> None: + x1, y1, z1 = arg # E: Variadic tuple unpacking requires a star target + reveal_type(x1) # N: Revealed type is "Any" + reveal_type(y1) # N: Revealed type is "Any" + reveal_type(z1) # N: Revealed type is "Any" + x2, *y2, z2 = arg + reveal_type(x2) # N: Revealed type is "builtins.int" + reveal_type(y2) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(z2) # N: Revealed type is "builtins.str" + x3, *y3 = arg + reveal_type(x3) # N: Revealed type is "builtins.int" + reveal_type(y3) # N: Revealed type is "builtins.list[builtins.object]" + *y4, z4 = arg + reveal_type(y4) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(z4) # N: Revealed type is "builtins.str" + x5, xx5, *y5, z5, zz5 = arg # E: Too many assignment targets for variadic unpack + reveal_type(x5) # N: Revealed type is "Any" + reveal_type(xx5) # N: Revealed type is "Any" + reveal_type(y5) # N: Revealed type is "builtins.list[Any]" + reveal_type(z5) # N: Revealed type is "Any" + reveal_type(zz5) # N: Revealed type is "Any" +[builtins fixtures/tuple.pyi] + +[case testUnpackingVariadicTuplesHomogeneous] +from typing import Tuple +from typing_extensions import Unpack + +def bar(arg: Tuple[int, Unpack[Tuple[float, ...]], str]) -> None: + x1, y1, z1 = arg # E: Variadic tuple unpacking requires a star target + reveal_type(x1) # N: Revealed type is "Any" + reveal_type(y1) # N: Revealed type is "Any" + reveal_type(z1) # N: Revealed type is "Any" + x2, *y2, z2 = arg + reveal_type(x2) # N: Revealed type is "builtins.int" + reveal_type(y2) # N: Revealed type is "builtins.list[builtins.float]" + reveal_type(z2) # N: Revealed type is "builtins.str" + x3, *y3 = arg + reveal_type(x3) # N: Revealed type is "builtins.int" + reveal_type(y3) # N: Revealed type is "builtins.list[builtins.object]" + *y4, z4 = arg + reveal_type(y4) # N: Revealed type is "builtins.list[builtins.float]" + reveal_type(z4) # N: Revealed type is "builtins.str" + x5, xx5, *y5, z5, zz5 = arg # E: Too many assignment targets for variadic unpack + reveal_type(x5) # N: Revealed type is "Any" + reveal_type(xx5) # N: Revealed type is "Any" + reveal_type(y5) # N: Revealed type is "builtins.list[Any]" + reveal_type(z5) # N: Revealed type is "Any" + reveal_type(zz5) # N: Revealed type is "Any" +[builtins fixtures/tuple.pyi] + +[case testRepackingVariadicTuplesTypeVar] +from typing import Tuple +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +def foo(arg: Tuple[int, Unpack[Ts], str]) -> None: + x1, *y1, z1 = *arg, + reveal_type(x1) # N: Revealed type is "builtins.int" + reveal_type(y1) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(z1) # N: Revealed type is "builtins.str" + x2, *y2, z2 = 1, *arg, 2 + reveal_type(x2) # N: Revealed type is "builtins.int" + reveal_type(y2) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(z2) # N: Revealed type is "builtins.int" + x3, *y3 = *arg, 42 + reveal_type(x3) # N: Revealed type is "builtins.int" + reveal_type(y3) # N: Revealed type is "builtins.list[builtins.object]" + *y4, z4 = 42, *arg + reveal_type(y4) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(z4) # N: Revealed type is "builtins.str" + x5, xx5, *y5, z5, zz5 = 1, *arg, 2 + reveal_type(x5) # N: Revealed type is "builtins.int" + reveal_type(xx5) # N: Revealed type is "builtins.int" + reveal_type(y5) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(z5) # N: Revealed type is "builtins.str" + reveal_type(zz5) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] + +[case testRepackingVariadicTuplesHomogeneous] +from typing import Tuple +from typing_extensions import Unpack + +def foo(arg: Tuple[int, Unpack[Tuple[float, ...]], str]) -> None: + x1, *y1, z1 = *arg, + reveal_type(x1) # N: Revealed type is "builtins.int" + reveal_type(y1) # N: Revealed type is "builtins.list[builtins.float]" + reveal_type(z1) # N: Revealed type is "builtins.str" + x2, *y2, z2 = 1, *arg, 2 + reveal_type(x2) # N: Revealed type is "builtins.int" + reveal_type(y2) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(z2) # N: Revealed type is "builtins.int" + x3, *y3 = *arg, 42 + reveal_type(x3) # N: Revealed type is "builtins.int" + reveal_type(y3) # N: Revealed type is "builtins.list[builtins.object]" + *y4, z4 = 42, *arg + reveal_type(y4) # N: Revealed type is "builtins.list[builtins.float]" + reveal_type(z4) # N: Revealed type is "builtins.str" + x5, xx5, *y5, z5, zz5 = 1, *arg, 2 + reveal_type(x5) # N: Revealed type is "builtins.int" + reveal_type(xx5) # N: Revealed type is "builtins.int" + reveal_type(y5) # N: Revealed type is "builtins.list[builtins.float]" + reveal_type(z5) # N: Revealed type is "builtins.str" + reveal_type(zz5) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] + +[case testPackingVariadicTuplesTypeVar] +from typing import Tuple +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +def foo(arg: Tuple[int, Unpack[Ts], str]) -> None: + x = *arg, + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" + y = 1, *arg, 2 + reveal_type(y) # N: Revealed type is "Tuple[builtins.int, builtins.int, Unpack[Ts`-1], builtins.str, builtins.int]" + z = (*arg, *arg) + reveal_type(z) # N: Revealed type is "builtins.tuple[builtins.object, ...]" +[builtins fixtures/tuple.pyi] + +[case testPackingVariadicTuplesHomogeneous] +from typing import Tuple +from typing_extensions import Unpack + +a: Tuple[float, ...] +b: Tuple[int, Unpack[Tuple[float, ...]], str] + +x = *a, +reveal_type(x) # N: Revealed type is "builtins.tuple[builtins.float, ...]" +y = 1, *a, 2 +reveal_type(y) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.int]" +z = (*a, *a) +reveal_type(z) # N: Revealed type is "builtins.tuple[builtins.float, ...]" + +x2 = *b, +reveal_type(x2) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" +y2 = 1, *b, 2 +reveal_type(y2) # N: Revealed type is "Tuple[builtins.int, builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str, builtins.int]" +z2 = (*b, *b) +reveal_type(z2) # N: Revealed type is "builtins.tuple[builtins.object, ...]" +[builtins fixtures/tuple.pyi] + +[case testVariadicTupleInListSetExpr] +from typing import Tuple +from typing_extensions import TypeVarTuple, Unpack + +vt: Tuple[int, Unpack[Tuple[float, ...]], int] +reveal_type([1, *vt]) # N: Revealed type is "builtins.list[builtins.float]" +reveal_type({1, *vt}) # N: Revealed type is "builtins.set[builtins.float]" + +Ts = TypeVarTuple("Ts") +def foo(arg: Tuple[int, Unpack[Ts], str]) -> None: + reveal_type([1, *arg]) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type({1, *arg}) # N: Revealed type is "builtins.set[builtins.object]" +[builtins fixtures/isinstancelist.pyi] + +[case testVariadicTupleInTupleContext] +from typing import Tuple, Optional +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +def test(x: Optional[Tuple[Unpack[Ts]]] = None) -> Tuple[Unpack[Ts]]: ... + +vt: Tuple[int, Unpack[Tuple[float, ...]], int] +vt = 1, *test(), 2 # OK, type context is used +vt2 = 1, *test(), 2 # E: Need type annotation for "vt2" +[builtins fixtures/tuple.pyi] + +[case testVariadicTupleConcatenation] +from typing import Tuple +from typing_extensions import TypeVarTuple, Unpack + +vtf: Tuple[float, ...] +vt: Tuple[int, Unpack[Tuple[float, ...]], int] + +reveal_type(vt + (1, 2)) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.int, Literal[1]?, Literal[2]?]" +reveal_type((1, 2) + vt) # N: Revealed type is "Tuple[Literal[1]?, Literal[2]?, builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.int]" +reveal_type(vt + vt) # N: Revealed type is "builtins.tuple[builtins.float, ...]" +reveal_type(vtf + (1, 2)) # N: Revealed type is "Tuple[Unpack[builtins.tuple[builtins.float, ...]], Literal[1]?, Literal[2]?]" +reveal_type((1, 2) + vtf) # N: Revealed type is "Tuple[Literal[1]?, Literal[2]?, Unpack[builtins.tuple[builtins.float, ...]]]" + +Ts = TypeVarTuple("Ts") +def foo(arg: Tuple[int, Unpack[Ts], str]) -> None: + reveal_type(arg + (1, 2)) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str, Literal[1]?, Literal[2]?]" + reveal_type((1, 2) + arg) # N: Revealed type is "Tuple[Literal[1]?, Literal[2]?, builtins.int, Unpack[Ts`-1], builtins.str]" + reveal_type(arg + arg) # N: Revealed type is "builtins.tuple[builtins.object, ...]" +[builtins fixtures/tuple.pyi] + [case testTypeVarTupleAnyOverload] from typing import Any, Generic, overload, Tuple from typing_extensions import TypeVarTuple, Unpack