diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 835eeb725394..bfcf0f43134e 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -138,6 +138,7 @@ function_type, get_all_type_vars, get_type_vars, + has_deferred_constructor, is_literal_type_like, make_simplified_union, simple_literal_type, @@ -400,6 +401,15 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type: result = node.type elif isinstance(node, (FuncDef, TypeInfo, TypeAlias, MypyFile, TypeVarLikeExpr)): result = self.analyze_static_reference(node, e, e.is_alias_rvalue or lvalue) + if ( + isinstance(node, TypeInfo) + and isinstance(result, ProperType) + and isinstance(result, AnyType) + and has_deferred_constructor(node) + ): + # When __init__ or __new__ is wrapped in a custom decorator, we need to defer. + # analyze_static_reference guarantees that it never defers, so play along. + self.chk.handle_cannot_determine_type(node.name, e) else: if isinstance(node, PlaceholderNode): assert False, f"PlaceholderNode {node.fullname!r} leaked to checker" diff --git a/mypy/typeops.py b/mypy/typeops.py index 87a4d8cefd13..60104c5e97ba 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -237,6 +237,21 @@ def type_object_type(info: TypeInfo, named_type: Callable[[str], Instance]) -> P return result +def has_deferred_constructor(info: TypeInfo) -> bool: + init_method = info.get("__init__") + new_method = info.get("__new__") or init_method + return ( + init_method is not None + and _is_deferred_decorator(init_method.node) + or new_method is not None + and _is_deferred_decorator(new_method.node) + ) + + +def _is_deferred_decorator(n: SymbolNode | None) -> bool: + return isinstance(n, Decorator) and n.type is None and not n.var.is_ready + + def is_valid_constructor(n: SymbolNode | None) -> bool: """Does this node represents a valid constructor method? diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 498a2c12b6e8..08f3366613d1 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -9289,3 +9289,34 @@ from typ import NT def f() -> NT: return NT(x='') [builtins fixtures/tuple.pyi] + +[case testDecoratedConstructorDeferral] +from typing import Any, Callable, TypeVar + +Tc = TypeVar('Tc', bound=Callable[..., Any]) + +def any_decorator_factory() -> Callable[[Tc], Tc]: + def inner(func: Tc) -> Tc: + return func + return inner + + +def function_pre() -> None: + reveal_type(GoodClass()) # N: Revealed type is "__main__.GoodClass" + reveal_type(BadClass()) # N: Revealed type is "Any" + + +class GoodClass: + @any_decorator_factory() + def __init__(self): + pass + +class BadClass: + @unknown() # E: Name "unknown" is not defined + def __init__(self): + pass + + +def function_post() -> None: + reveal_type(GoodClass()) # N: Revealed type is "__main__.GoodClass" + reveal_type(BadClass()) # N: Revealed type is "Any"