Skip to content

Commit 934976d

Browse files
committed
Defer when constructor type is not yet available
1 parent 8437cf5 commit 934976d

File tree

3 files changed

+51
-0
lines changed

3 files changed

+51
-0
lines changed

mypy/checkexpr.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@
138138
function_type,
139139
get_all_type_vars,
140140
get_type_vars,
141+
has_deferred_constructor,
141142
is_literal_type_like,
142143
make_simplified_union,
143144
simple_literal_type,
@@ -400,6 +401,10 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type:
400401
result = node.type
401402
elif isinstance(node, (FuncDef, TypeInfo, TypeAlias, MypyFile, TypeVarLikeExpr)):
402403
result = self.analyze_static_reference(node, e, e.is_alias_rvalue or lvalue)
404+
if isinstance(node, TypeInfo) and has_deferred_constructor(node):
405+
# When __init__ or __new__ is wrapped in a custom decorator, we need to defer.
406+
# analyze_static_reference guarantees that it never defers, so play along.
407+
self.chk.handle_cannot_determine_type(node.name, e)
403408
else:
404409
if isinstance(node, PlaceholderNode):
405410
assert False, f"PlaceholderNode {node.fullname!r} leaked to checker"

mypy/typeops.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,21 @@ def type_object_type(info: TypeInfo, named_type: Callable[[str], Instance]) -> P
237237
return result
238238

239239

240+
def has_deferred_constructor(info: TypeInfo) -> bool:
241+
init_method = info.get("__init__")
242+
new_method = info.get("__new__") or init_method
243+
return (
244+
init_method is not None
245+
and _is_deferred_decorator(init_method.node)
246+
or new_method is not None
247+
and _is_deferred_decorator(new_method.node)
248+
)
249+
250+
251+
def _is_deferred_decorator(n: SymbolNode | None) -> bool:
252+
return isinstance(n, Decorator) and n.type is None
253+
254+
240255
def is_valid_constructor(n: SymbolNode | None) -> bool:
241256
"""Does this node represents a valid constructor method?
242257

test-data/unit/check-classes.test

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9289,3 +9289,34 @@ from typ import NT
92899289
def f() -> NT:
92909290
return NT(x='')
92919291
[builtins fixtures/tuple.pyi]
9292+
9293+
[case testDecoratedConstructorDeferral]
9294+
from typing import Any, Callable, TypeVar
9295+
9296+
Tc = TypeVar('Tc', bound=Callable[..., Any])
9297+
9298+
def any_decorator_factory() -> Callable[[Tc], Tc]:
9299+
def inner(func: Tc) -> Tc:
9300+
return func
9301+
return inner
9302+
9303+
9304+
def function_pre() -> None:
9305+
reveal_type(GoodClass()) # N: Revealed type is "__main__.GoodClass"
9306+
reveal_type(BadClass()) # N: Revealed type is "Any"
9307+
9308+
9309+
class GoodClass:
9310+
@any_decorator_factory()
9311+
def __init__(self):
9312+
pass
9313+
9314+
class BadClass:
9315+
@unknown() # E: Name "unknown" is not defined
9316+
def __init__(self):
9317+
pass
9318+
9319+
9320+
def function_post() -> None:
9321+
reveal_type(GoodClass()) # N: Revealed type is "__main__.GoodClass"
9322+
reveal_type(BadClass()) # N: Revealed type is "Any"

0 commit comments

Comments
 (0)