diff --git a/mypy/mixedtraverser.py b/mypy/mixedtraverser.py index 535391886b02..40f3e640ef03 100644 --- a/mypy/mixedtraverser.py +++ b/mypy/mixedtraverser.py @@ -16,6 +16,7 @@ TypeApplication, TypedDictExpr, TypeFormExpr, + TypeInfo, TypeVarExpr, Var, WithStmt, @@ -41,15 +42,29 @@ def visit_func(self, o: FuncItem, /) -> None: self.visit_optional_type(o.type) def visit_class_def(self, o: ClassDef, /) -> None: - # TODO: Should we visit generated methods/variables as well, either here or in - # TraverserVisitor? super().visit_class_def(o) - info = o.info - if info: - for base in info.bases: - base.accept(self) - if info.special_alias: - info.special_alias.accept(self) + if o.info: + self.process_type_info(o.info) + + def process_type_info(self, info: TypeInfo) -> None: + # TODO: Should we visit generated methods/variables as well? + # We should for methods generated by us (see below). But it is less clear for + # 3rd party plugin generated methods (since we don't want to emit errors there). + for base in info.bases: + base.accept(self) + if info.special_alias: + # We need to accept all types that are conceptually identical like special + # alias target and corresponding tuple_type or typeddict_type, since those + # may be copies, and not the same object. + info.special_alias.accept(self) + if info.tuple_type: + info.tuple_type.accept(self) + if info.typeddict_type: + info.typeddict_type.accept(self) + if info.is_named_tuple or info.is_newtype: + for sym in info.names.values(): + if sym.plugin_generated and sym.node: + sym.node.accept(self) def visit_type_alias_expr(self, o: TypeAliasExpr, /) -> None: super().visit_type_alias_expr(o) @@ -64,12 +79,11 @@ def visit_type_var_expr(self, o: TypeVarExpr, /) -> None: def visit_typeddict_expr(self, o: TypedDictExpr, /) -> None: super().visit_typeddict_expr(o) - self.visit_optional_type(o.info.typeddict_type) + self.process_type_info(o.info) def visit_namedtuple_expr(self, o: NamedTupleExpr, /) -> None: super().visit_namedtuple_expr(o) - assert o.info.tuple_type - o.info.tuple_type.accept(self) + self.process_type_info(o.info) def visit__promote_expr(self, o: PromoteExpr, /) -> None: super().visit__promote_expr(o) @@ -77,6 +91,8 @@ def visit__promote_expr(self, o: PromoteExpr, /) -> None: def visit_newtype_expr(self, o: NewTypeExpr, /) -> None: super().visit_newtype_expr(o) + if o.info: + self.process_type_info(o.info) self.visit_optional_type(o.old_type) # Statements diff --git a/mypy/typetraverser.py b/mypy/typetraverser.py index 2a7f41bd97f2..9cbf9d95067a 100644 --- a/mypy/typetraverser.py +++ b/mypy/typetraverser.py @@ -82,7 +82,11 @@ def visit_instance(self, t: Instance, /) -> None: self.traverse_type_tuple(t.args) def visit_callable_type(self, t: CallableType, /) -> None: - # FIX generics + for tv in t.variables: + tv.upper_bound.accept(self) + if isinstance(tv, TypeVarType): + for v in tv.values: + v.accept(self) self.traverse_type_list(t.arg_types) t.ret_type.accept(self) t.fallback.accept(self) diff --git a/test-data/unit/check-python312.test b/test-data/unit/check-python312.test index c4a232079746..23626ccd0a93 100644 --- a/test-data/unit/check-python312.test +++ b/test-data/unit/check-python312.test @@ -2293,3 +2293,27 @@ reveal_type(x) # N: Revealed type is "__main__.C[()]" y: T[bool] reveal_type(y) # N: Revealed type is "__main__.C[()]" [builtins fixtures/tuple.pyi] + +[case testTupleBaseTupleTypeUnpack] +class X(tuple[*tuple[int], *tuple[int]]): + pass +x = X((1, 2)) +reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int, fallback=__main__.X]" +[builtins fixtures/tuple.pyi] + +[case testNewTypeTupleTypeUnpack] +from typing import NewType + +T = NewType("T", tuple[*tuple[int], *tuple[int]]) +t = T((1, 2)) +reveal_type(t) # N: Revealed type is "tuple[builtins.int, builtins.int, fallback=__main__.T]" +[builtins fixtures/tuple.pyi] + +[case testNamedTupleTupleTypeUnpack] +from typing import NamedTuple + +class N(NamedTuple): + x: tuple[*tuple[int], *tuple[int]] +n = N((1, 2)) +reveal_type(n) # N: Revealed type is "tuple[tuple[builtins.int, builtins.int], fallback=__main__.N]" +[builtins fixtures/tuple.pyi]