diff --git a/mypy/checker.py b/mypy/checker.py index 096ec7abade1..0732b8b92fc9 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4015,9 +4015,19 @@ def is_typed_callable(c: Optional[Type]) -> bool: def is_untyped_decorator(typ: Optional[Type]) -> bool: - if not typ or not isinstance(typ, CallableType): + if not typ: return True - return typ.implicit + elif isinstance(typ, CallableType): + return not is_typed_callable(typ) + elif isinstance(typ, Instance): + method = typ.type.get_method('__call__') + if method: + return not is_typed_callable(method.type) + else: + return False + elif isinstance(typ, Overloaded): + return any(is_untyped_decorator(item) for item in typ.items()) + return True def is_static(func: Union[FuncBase, Decorator]) -> bool: diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 2892ff8c5db9..67eb8df5ed48 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -132,6 +132,45 @@ def d3(p) -> Any: @d1 # E: Untyped decorator makes function "f" untyped def f() -> None: pass +[case testDisallowUntypedDecoratorsCallableInstance] +# flags: --disallow-untyped-decorators +from typing import Callable + +class TypedDecorator: + def __call__(self, c: Callable) -> Callable: + return function + +class UntypedDecorator: + def __call__(self, c): + return function + +@TypedDecorator() +def f() -> None: pass + +@UntypedDecorator() # E: Untyped decorator makes function "g" untyped +def g() -> None: pass + +@TypedDecorator() +@UntypedDecorator() # E: Untyped decorator makes function "h" untyped +def h() -> None: pass + +@UntypedDecorator() # E: Untyped decorator makes function "i" untyped +@TypedDecorator() +def i() -> None: pass + +reveal_type(f) # E: Revealed type is 'def (*Any, **Any) -> Any' +reveal_type(g) # E: Revealed type is 'Any' +reveal_type(h) # E: Revealed type is 'def (*Any, **Any) -> Any' +reveal_type(i) # E: Revealed type is 'Any' + +[case testDisallowUntypedDecoratorsNonCallableInstance] +# flags: --disallow-untyped-decorators +class Decorator: + pass + +@Decorator() # E: "Decorator" not callable +def f() -> None: pass + [case testSubclassingAny] # flags: --disallow-subclassing-any from typing import Any diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index ac56d09329ae..0d92a337cf5d 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -4849,3 +4849,27 @@ reveal_type(b) # E: Revealed type is 'builtins.int' c = single_plausible([Other()]) # E: List item 0 has incompatible type "Other"; expected "str" reveal_type(c) # E: Revealed type is 'builtins.str' [builtins fixtures/list.pyi] + +[case testDisallowUntypedDecoratorsOverload] +# flags: --disallow-untyped-decorators +from typing import Any, Callable, overload, TypeVar + +F = TypeVar('F', bound=Callable[..., Any]) + +@overload +def dec(x: F) -> F: ... +@overload +def dec(x: str) -> Callable[[F], F]: ... +def dec(x) -> Any: + pass + +@dec +def f(name: str) -> int: + return 0 + +@dec('abc') +def g(name: str) -> int: + return 0 + +reveal_type(f) # E: Revealed type is 'def (name: builtins.str) -> builtins.int' +reveal_type(g) # E: Revealed type is 'def (name: builtins.str) -> builtins.int'