diff --git a/mypy/checker.py b/mypy/checker.py index 64852cb724f4..8172546f240d 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -248,6 +248,7 @@ def __init__(self) -> None: false_only, fixup_partial_type, function_type, + get_tuple_item_types, is_literal_type_like, is_singleton_equality_type, is_singleton_identity_type, @@ -9037,19 +9038,7 @@ def builtin_item_type(tp: Type) -> Type | None: if not isinstance(get_proper_type(tp.args[0]), AnyType): return tp.args[0] elif isinstance(tp, TupleType): - normalized_items = [] - for it in tp.items: - # This use case is probably rare, but not handling unpacks here can cause crashes. - if isinstance(it, UnpackType): - unpacked = get_proper_type(it.type) - if isinstance(unpacked, TypeVarTupleType): - unpacked = get_proper_type(unpacked.upper_bound) - assert ( - isinstance(unpacked, Instance) and unpacked.type.fullname == "builtins.tuple" - ) - normalized_items.append(unpacked.args[0]) - else: - normalized_items.append(it) + normalized_items = get_tuple_item_types(tp) if all(not isinstance(it, AnyType) for it in get_proper_types(normalized_items)): return make_simplified_union(normalized_items) # this type is not externally visible elif isinstance(tp, TypedDictType): diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 769e37ac97ee..f7ff23f0e648 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -155,6 +155,7 @@ fixup_partial_type, freeze_all_type_vars, get_all_type_vars, + get_tuple_item_types, get_type_vars, is_literal_type_like, make_simplified_union, @@ -4823,30 +4824,11 @@ 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 = self.union_tuple_fallback_item(left_type) + union = make_simplified_union(get_tuple_item_types(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 ) -> tuple[Type, set[str]]: diff --git a/mypy/typeops.py b/mypy/typeops.py index 3035a15e2119..4c19748b2c18 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -109,6 +109,17 @@ def tuple_fallback(typ: TupleType) -> Instance: info = typ.partial_fallback.type if info.fullname != "builtins.tuple": return typ.partial_fallback + return Instance( + info, + # Note: flattening recursive unions is dangerous, since it can fool recursive + # types optimization in subtypes.py and go into infinite recursion. + [make_simplified_union(get_tuple_item_types(typ), handle_recursive=False)], + extra_attrs=typ.partial_fallback.extra_attrs, + ) + + +def get_tuple_item_types(typ: TupleType) -> list[Type]: + """Return tuple item types, replacing unpacks with their possible item types.""" items = [] for item in typ.items: if isinstance(item, UnpackType): @@ -124,13 +135,7 @@ def tuple_fallback(typ: TupleType) -> Instance: raise NotImplementedError else: items.append(item) - return Instance( - info, - # Note: flattening recursive unions is dangerous, since it can fool recursive - # types optimization in subtypes.py and go into infinite recursion. - [make_simplified_union(items, handle_recursive=False)], - extra_attrs=typ.partial_fallback.extra_attrs, - ) + return items def get_self_type(func: CallableType, def_info: TypeInfo) -> Type | None: