Skip to content

Commit 5afea77

Browse files
izquierdoilevkivskyi
authored andcommitted
Fix untyped decorator check for class instances (#5509)
Running `mypy --disallow-untyped-decorators` when the decorator is a typed callable instance produces an error even though `reveal_type` shows the correct type. Related to #4191
1 parent fed00ef commit 5afea77

File tree

3 files changed

+75
-2
lines changed

3 files changed

+75
-2
lines changed

mypy/checker.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4220,9 +4220,19 @@ def is_typed_callable(c: Optional[Type]) -> bool:
42204220

42214221

42224222
def is_untyped_decorator(typ: Optional[Type]) -> bool:
4223-
if not typ or not isinstance(typ, CallableType):
4223+
if not typ:
42244224
return True
4225-
return typ.implicit
4225+
elif isinstance(typ, CallableType):
4226+
return not is_typed_callable(typ)
4227+
elif isinstance(typ, Instance):
4228+
method = typ.type.get_method('__call__')
4229+
if method:
4230+
return not is_typed_callable(method.type)
4231+
else:
4232+
return False
4233+
elif isinstance(typ, Overloaded):
4234+
return any(is_untyped_decorator(item) for item in typ.items())
4235+
return True
42264236

42274237

42284238
def is_static(func: Union[FuncBase, Decorator]) -> bool:

test-data/unit/check-flags.test

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,45 @@ def d3(p) -> Any:
132132
@d1 # E: Untyped decorator makes function "f" untyped
133133
def f() -> None: pass
134134

135+
[case testDisallowUntypedDecoratorsCallableInstance]
136+
# flags: --disallow-untyped-decorators
137+
from typing import Callable
138+
139+
class TypedDecorator:
140+
def __call__(self, c: Callable) -> Callable:
141+
return function
142+
143+
class UntypedDecorator:
144+
def __call__(self, c):
145+
return function
146+
147+
@TypedDecorator()
148+
def f() -> None: pass
149+
150+
@UntypedDecorator() # E: Untyped decorator makes function "g" untyped
151+
def g() -> None: pass
152+
153+
@TypedDecorator()
154+
@UntypedDecorator() # E: Untyped decorator makes function "h" untyped
155+
def h() -> None: pass
156+
157+
@UntypedDecorator() # E: Untyped decorator makes function "i" untyped
158+
@TypedDecorator()
159+
def i() -> None: pass
160+
161+
reveal_type(f) # E: Revealed type is 'def (*Any, **Any) -> Any'
162+
reveal_type(g) # E: Revealed type is 'Any'
163+
reveal_type(h) # E: Revealed type is 'def (*Any, **Any) -> Any'
164+
reveal_type(i) # E: Revealed type is 'Any'
165+
166+
[case testDisallowUntypedDecoratorsNonCallableInstance]
167+
# flags: --disallow-untyped-decorators
168+
class Decorator:
169+
pass
170+
171+
@Decorator() # E: "Decorator" not callable
172+
def f() -> None: pass
173+
135174
[case testSubclassingAny]
136175
# flags: --disallow-subclassing-any
137176
from typing import Any

test-data/unit/check-overloading.test

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4849,3 +4849,27 @@ reveal_type(b) # E: Revealed type is 'builtins.int'
48494849
c = single_plausible([Other()]) # E: List item 0 has incompatible type "Other"; expected "str"
48504850
reveal_type(c) # E: Revealed type is 'builtins.str'
48514851
[builtins fixtures/list.pyi]
4852+
4853+
[case testDisallowUntypedDecoratorsOverload]
4854+
# flags: --disallow-untyped-decorators
4855+
from typing import Any, Callable, overload, TypeVar
4856+
4857+
F = TypeVar('F', bound=Callable[..., Any])
4858+
4859+
@overload
4860+
def dec(x: F) -> F: ...
4861+
@overload
4862+
def dec(x: str) -> Callable[[F], F]: ...
4863+
def dec(x) -> Any:
4864+
pass
4865+
4866+
@dec
4867+
def f(name: str) -> int:
4868+
return 0
4869+
4870+
@dec('abc')
4871+
def g(name: str) -> int:
4872+
return 0
4873+
4874+
reveal_type(f) # E: Revealed type is 'def (name: builtins.str) -> builtins.int'
4875+
reveal_type(g) # E: Revealed type is 'def (name: builtins.str) -> builtins.int'

0 commit comments

Comments
 (0)