Skip to content

Commit

Permalink
Fix untyped decorator check for class instances (#5509)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
izquierdo authored and ilevkivskyi committed Sep 21, 2018
1 parent fed00ef commit 5afea77
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 2 deletions.
14 changes: 12 additions & 2 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -4220,9 +4220,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'

0 comments on commit 5afea77

Please sign in to comment.