From 81cfd1c6c496e9130b59f75381073f087412bf1d Mon Sep 17 00:00:00 2001 From: Daniel Izquierdo Date: Mon, 20 Aug 2018 10:02:22 +0900 Subject: [PATCH 1/8] Fix untyped decorator check for class instances --- mypy/checker.py | 10 ++++++++-- test-data/unit/check-flags.test | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 5ba197323820..ddfdfe71d5e0 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4004,9 +4004,15 @@ 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 + if isinstance(typ, CallableType): + return typ.implicit + if isinstance(typ, Instance): + method = typ.type.get_method('__call__') + if method and method.type: + return not is_typed_callable(method.type) + 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..1b7f0b42d338 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -132,6 +132,22 @@ 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 + +@UntypedDecorator() # E: Untyped decorator makes function "f" untyped +@TypedDecorator() +def f() -> None: pass + [case testSubclassingAny] # flags: --disallow-subclassing-any from typing import Any From 5ab8e77af41fe009ffc7ee54a8ef9d521a0861c3 Mon Sep 17 00:00:00 2001 From: Daniel Izquierdo Date: Mon, 20 Aug 2018 22:36:45 +0900 Subject: [PATCH 2/8] Change if to elif --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index ddfdfe71d5e0..60ee3999a05c 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4008,7 +4008,7 @@ def is_untyped_decorator(typ: Optional[Type]) -> bool: return True if isinstance(typ, CallableType): return typ.implicit - if isinstance(typ, Instance): + elif isinstance(typ, Instance): method = typ.type.get_method('__call__') if method and method.type: return not is_typed_callable(method.type) From 8277397bd67851a3de0d797cc5aacff18628541c Mon Sep 17 00:00:00 2001 From: Daniel Izquierdo Date: Mon, 20 Aug 2018 22:39:35 +0900 Subject: [PATCH 3/8] Be consistent in using is_typed_callable() --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 60ee3999a05c..4667bcf3c90d 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4007,7 +4007,7 @@ def is_untyped_decorator(typ: Optional[Type]) -> bool: if not typ: return True if isinstance(typ, CallableType): - return typ.implicit + return not is_typed_callable(typ) elif isinstance(typ, Instance): method = typ.type.get_method('__call__') if method and method.type: From ba8add5584555f7a6a6b21cababdbac0d284f5f6 Mon Sep 17 00:00:00 2001 From: Daniel Izquierdo Date: Mon, 20 Aug 2018 22:45:14 +0900 Subject: [PATCH 4/8] Fix spurious error for Instance without __call__ --- mypy/checker.py | 4 +++- test-data/unit/check-flags.test | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 4667bcf3c90d..9b0ed909dac6 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4010,8 +4010,10 @@ def is_untyped_decorator(typ: Optional[Type]) -> bool: return not is_typed_callable(typ) elif isinstance(typ, Instance): method = typ.type.get_method('__call__') - if method and method.type: + if method: return not is_typed_callable(method.type) + else: + return False return True diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 1b7f0b42d338..4150b44ac90c 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -148,6 +148,14 @@ class UntypedDecorator: @TypedDecorator() def f() -> None: pass +[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 From c1e0ea5d51798387c15299902640ea27de232502 Mon Sep 17 00:00:00 2001 From: Daniel Izquierdo Date: Mon, 20 Aug 2018 22:47:18 +0900 Subject: [PATCH 5/8] Check untyped decorators more thoroughly --- test-data/unit/check-flags.test | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 4150b44ac90c..67eb8df5ed48 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -144,10 +144,25 @@ class UntypedDecorator: def __call__(self, c): return function -@UntypedDecorator() # E: Untyped decorator makes function "f" untyped @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: From aeabb1531f8b49e44d02d3ef7cdf986f70b19fe8 Mon Sep 17 00:00:00 2001 From: Daniel Izquierdo Date: Mon, 20 Aug 2018 22:58:21 +0900 Subject: [PATCH 6/8] Fix untyped decorator check for overloads --- mypy/checker.py | 2 ++ test-data/unit/check-overloading.test | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index 9b0ed909dac6..c301d1b76dda 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4014,6 +4014,8 @@ def is_untyped_decorator(typ: Optional[Type]) -> bool: 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 diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index ef26f7f91484..9b4ca8727c6a 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -4257,3 +4257,22 @@ def f() -> None: [builtins fixtures/dict.pyi] [out] + +[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 + +reveal_type(f) # E: Revealed type is 'def (name: builtins.str) -> builtins.int' From 0d3c68a6542d8d13f4ea6bb28c8dbb30dd629507 Mon Sep 17 00:00:00 2001 From: Daniel Izquierdo Date: Tue, 28 Aug 2018 09:56:12 +0900 Subject: [PATCH 7/8] Change if to elif --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 715096001c32..0732b8b92fc9 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4017,7 +4017,7 @@ def is_typed_callable(c: Optional[Type]) -> bool: def is_untyped_decorator(typ: Optional[Type]) -> bool: if not typ: return True - if isinstance(typ, CallableType): + elif isinstance(typ, CallableType): return not is_typed_callable(typ) elif isinstance(typ, Instance): method = typ.type.get_method('__call__') From 15461a65e68101ad153718b55c4ae5e73b777d5b Mon Sep 17 00:00:00 2001 From: Daniel Izquierdo Date: Tue, 28 Aug 2018 10:00:12 +0900 Subject: [PATCH 8/8] Expand test case for overloaded decorator --- test-data/unit/check-overloading.test | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 3ef290eb340d..0d92a337cf5d 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -4867,4 +4867,9 @@ def dec(x) -> Any: 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'