Skip to content

Fix untyped decorator check for class instances #5509

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Sep 21, 2018
14 changes: 12 additions & 2 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
39 changes: 39 additions & 0 deletions test-data/unit/check-flags.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions test-data/unit/check-overloading.test
Original file line number Diff line number Diff line change
Expand Up @@ -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'