From ef285d36fff6bbbd5386af462b2c157c4f4e1397 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Tue, 13 Jun 2017 11:34:19 -0700 Subject: [PATCH 01/14] Add flag to disallow decorating typed functions with untyped decorators If none of the argument types + return type of the decorated function are Any and the decorator has return type `Any`, `--disallow-any=decorator` reports an error. --- mypy/checker.py | 23 ++++++++++++ mypy/main.py | 2 +- mypy/messages.py | 12 +++++- test-data/unit/check-flags.test | 65 +++++++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 98bb35474bf99..097173b661c5f 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2262,6 +2262,7 @@ def visit_decorator(self, e: Decorator) -> None: fullname = None if isinstance(d, RefExpr): fullname = d.fullname + self.check_for_untyped_decorator(e, d, dec) sig, t2 = self.expr_checker.check_call(dec, [temp], [nodes.ARG_POS], e, callable_name=fullname) @@ -2293,6 +2294,28 @@ def visit_with_stmt(self, s: WithStmt) -> None: self.check_with_item(expr, target, s.target_type is None) self.accept(s.body) + def check_for_untyped_decorator(self, + decorated: Decorator, + dec_expr: Expression, + dec_type: Type + ) -> None: + def is_typed_func(func_type: Type) -> bool: + if func_type and isinstance(func_type, CallableType): + for t in func_type.arg_types + [func_type.ret_type]: + if isinstance(t, AnyType): + return False + return True + return False + short_name = None # type: Optional[str] + if isinstance(dec_expr, NameExpr): + short_name = dec_expr.name + + returns_any = isinstance(dec_type, CallableType) and isinstance(dec_type.ret_type, AnyType) + if ('decorator' in self.options.disallow_any and + (returns_any or isinstance(dec_type, AnyType)) and + is_typed_func(decorated.func.type)): + self.msg.untyped_decorator(short_name, decorated.func.name(), dec_expr) + def check_async_with_item(self, expr: Expression, target: Expression, infer_lvalue_type: bool) -> None: echk = self.expr_checker diff --git a/mypy/main.py b/mypy/main.py index a31feb2ff6be1..ebc9be4c50fc9 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -97,7 +97,7 @@ def type_check_only(sources: List[BuildSource], bin_dir: str, options: Options) options=options) -disallow_any_options = ['unimported', 'unannotated'] +disallow_any_options = ['unimported', 'unannotated', 'decorator'] def disallow_any_argument_type(raw_options: str) -> List[str]: diff --git a/mypy/messages.py b/mypy/messages.py index 223118c40fffa..528c517d505f1 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -6,7 +6,7 @@ import re import difflib -from typing import cast, List, Dict, Any, Sequence, Iterable, Tuple +from typing import cast, List, Dict, Any, Sequence, Iterable, Tuple, Optional from mypy.erasetype import erase_type from mypy.errors import Errors @@ -904,6 +904,16 @@ def typeddict_item_name_not_found(self, def type_arguments_not_allowed(self, context: Context) -> None: self.fail('Parameterized generics cannot be used with class or instance checks', context) + def untyped_decorator(self, + decorator_name: Optional[str], + func_name: str, + context: Context, + ) -> None: + prefix = 'Untyped decorator' + if decorator_name: + prefix += " '{}'".format(decorator_name) + self.fail("{} makes typed function '{}' untyped".format(prefix, func_name), context) + def capitalize(s: str) -> str: """Capitalize the first character of a string.""" diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 6ee0247c9bf48..b00daf699c1c3 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -515,3 +515,68 @@ x, y = 1, 2 # type: Unchecked, Unchecked [out] main:4: error: Type of variable becomes "Any" due to an unfollowed import main:6: error: A type on this line becomes "Any" due to an unfollowed import + +[case testDisallowAnyDecoratorSimple] +# flags: --disallow-any=decorator +from typing import Any + +def d(f): + return f + +@d # no error +def f(x: Any) -> Any: pass +@d # no error +def g(x: Any) -> None: pass +@d # no error +def h(x): pass +@d # E: Untyped decorator 'd' makes typed function 'i' untyped +def i(x: int) -> None: pass +@d # E: Untyped decorator 'd' makes typed function 'j' untyped +def j() -> None: pass + +[builtins fixtures/list.pyi] +[case testDisallowAnyDecoratorMultiple] +# flags: --disallow-any=decorator + +def d(f): + return f + +def d2(f): + return f + +@d # E: Untyped decorator 'd' makes typed function 'f' untyped +@d2 # E: Untyped decorator 'd2' makes typed function 'f' untyped +@d # E: Untyped decorator 'd' makes typed function 'f' untyped +def f(x: int) -> None: pass + +[case testDisallowAnyDecoratorReturnAny] +# flags: --disallow-any=decorator +from typing import Any + +def d(f) -> Any: + return f +def d2(f: int) -> Any: + return f + +@d2 # E: Untyped decorator 'd2' makes typed function 'f' untyped +@d # E: Untyped decorator 'd' makes typed function 'f' untyped +def f() -> None: pass +[builtins fixtures/list.pyi] +[case testDisallowAnyDecoratorReturnCallable] +# flags: --disallow-any=decorator +from typing import Any, Callable + +def d(f) -> Callable[..., Any]: + return f + +@d # no error +def f() -> None: pass +[builtins fixtures/list.pyi] +[case testDisallowAnyDecoratorNonexistentDecorator] +# flags: --disallow-any=decorator --ignore-missing-imports +from typing import Any, Callable +from nonexistent import d + +@d # E: Untyped decorator 'd' makes typed function 'f' untyped +def f() -> None: pass +[builtins fixtures/list.pyi] From 9fd9f69e1f9a097584fd35496db902235f0d8a13 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Tue, 13 Jun 2017 18:54:18 -0700 Subject: [PATCH 02/14] Report errors if a decorator makes any of parameters or return type Any --- mypy/checker.py | 44 ++++++++++++++++++---------- mypy/messages.py | 11 +++---- test-data/unit/check-flags.test | 52 +++++++++++++++++++++++++-------- 3 files changed, 74 insertions(+), 33 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 097173b661c5f..d3771a7c4fcaa 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2297,24 +2297,36 @@ def visit_with_stmt(self, s: WithStmt) -> None: def check_for_untyped_decorator(self, decorated: Decorator, dec_expr: Expression, - dec_type: Type + decorator_func_type: Type ) -> None: - def is_typed_func(func_type: Type) -> bool: - if func_type and isinstance(func_type, CallableType): - for t in func_type.arg_types + [func_type.ret_type]: - if isinstance(t, AnyType): - return False - return True - return False - short_name = None # type: Optional[str] - if isinstance(dec_expr, NameExpr): - short_name = dec_expr.name + if 'decorator' not in self.options.disallow_any: + return - returns_any = isinstance(dec_type, CallableType) and isinstance(dec_type.ret_type, AnyType) - if ('decorator' in self.options.disallow_any and - (returns_any or isinstance(dec_type, AnyType)) and - is_typed_func(decorated.func.type)): - self.msg.untyped_decorator(short_name, decorated.func.name(), dec_expr) + func_type = decorated.func.type + if not func_type or not isinstance(func_type, CallableType): + return + + new_ret_type = AnyType() # type: Type + new_param_types = [] # type: List[Type] + if (isinstance(decorator_func_type, CallableType) and + isinstance(decorator_func_type.ret_type, CallableType)): + new_param_types = decorator_func_type.ret_type.arg_types + new_ret_type = decorator_func_type.ret_type.ret_type + + num_missing_types = len(func_type.arg_types) - len(new_param_types) + if num_missing_types > 0: + new_param_types.extend([AnyType()] * num_missing_types) + + dec_name = None # type: Optional[str] + if isinstance(dec_expr, NameExpr): + dec_name = dec_expr.name + + for i, old, new in zip(itertools.count(start=1), func_type.arg_types, new_param_types): + if isinstance(new, AnyType) and not isinstance(old, AnyType): + prefix = "Parameter {}".format(i) + self.msg.untyped_decorator(prefix, func_type.name, dec_name, dec_expr) + if isinstance(new_ret_type, AnyType) and not isinstance(func_type.ret_type, AnyType): + self.msg.untyped_decorator("Return type", func_type.name, dec_name, dec_expr) def check_async_with_item(self, expr: Expression, target: Expression, infer_lvalue_type: bool) -> None: diff --git a/mypy/messages.py b/mypy/messages.py index 528c517d505f1..00cbf3b94b69c 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -905,15 +905,16 @@ def type_arguments_not_allowed(self, context: Context) -> None: self.fail('Parameterized generics cannot be used with class or instance checks', context) def untyped_decorator(self, - decorator_name: Optional[str], + prefix: str, func_name: str, + decorator_name: Optional[str], context: Context, ) -> None: - prefix = 'Untyped decorator' + postfix = '' if decorator_name: - prefix += " '{}'".format(decorator_name) - self.fail("{} makes typed function '{}' untyped".format(prefix, func_name), context) - + postfix = ' "{}"'.format(decorator_name) + self.fail('{} of {} becomes "Any" due to untyped decorator{}'.format( + prefix, func_name, postfix), context) def capitalize(s: str) -> str: """Capitalize the first character of a string.""" diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index b00daf699c1c3..47537654afea9 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -525,16 +525,20 @@ def d(f): @d # no error def f(x: Any) -> Any: pass -@d # no error +@d # error def g(x: Any) -> None: pass @d # no error def h(x): pass -@d # E: Untyped decorator 'd' makes typed function 'i' untyped +@d # errors def i(x: int) -> None: pass -@d # E: Untyped decorator 'd' makes typed function 'j' untyped +@d # errors def j() -> None: pass - [builtins fixtures/list.pyi] +[out] +main:9: error: Return type of "g" becomes "Any" due to untyped decorator "d" +main:13: error: Parameter 1 of "i" becomes "Any" due to untyped decorator "d" +main:13: error: Return type of "i" becomes "Any" due to untyped decorator "d" +main:15: error: Return type of "j" becomes "Any" due to untyped decorator "d" [case testDisallowAnyDecoratorMultiple] # flags: --disallow-any=decorator @@ -544,10 +548,17 @@ def d(f): def d2(f): return f -@d # E: Untyped decorator 'd' makes typed function 'f' untyped -@d2 # E: Untyped decorator 'd2' makes typed function 'f' untyped -@d # E: Untyped decorator 'd' makes typed function 'f' untyped +@d # errors +@d2 # errors +@d # errors def f(x: int) -> None: pass +[out] +main:9: error: Parameter 1 of "f" becomes "Any" due to untyped decorator "d" +main:9: error: Return type of "f" becomes "Any" due to untyped decorator "d" +main:10: error: Parameter 1 of "f" becomes "Any" due to untyped decorator "d2" +main:10: error: Return type of "f" becomes "Any" due to untyped decorator "d2" +main:11: error: Parameter 1 of "f" becomes "Any" due to untyped decorator "d" +main:11: error: Return type of "f" becomes "Any" due to untyped decorator "d" [case testDisallowAnyDecoratorReturnAny] # flags: --disallow-any=decorator @@ -558,8 +569,8 @@ def d(f) -> Any: def d2(f: int) -> Any: return f -@d2 # E: Untyped decorator 'd2' makes typed function 'f' untyped -@d # E: Untyped decorator 'd' makes typed function 'f' untyped +@d2 # E: Return type of "f" becomes "Any" due to untyped decorator "d2" +@d # E: Return type of "f" becomes "Any" due to untyped decorator "d" def f() -> None: pass [builtins fixtures/list.pyi] [case testDisallowAnyDecoratorReturnCallable] @@ -569,14 +580,31 @@ from typing import Any, Callable def d(f) -> Callable[..., Any]: return f -@d # no error +@d # errors +def g(i: int, s: str) -> None: pass + +@d # E: Return type of "f" becomes "Any" due to untyped decorator "d" def f() -> None: pass [builtins fixtures/list.pyi] +[out] +main:7: error: Parameter 1 of "g" becomes "Any" due to untyped decorator "d" +main:7: error: Parameter 2 of "g" becomes "Any" due to untyped decorator "d" +main:7: error: Return type of "g" becomes "Any" due to untyped decorator "d" + [case testDisallowAnyDecoratorNonexistentDecorator] # flags: --disallow-any=decorator --ignore-missing-imports -from typing import Any, Callable from nonexistent import d -@d # E: Untyped decorator 'd' makes typed function 'f' untyped +@d # E: Return type of "f" becomes "Any" due to untyped decorator "d" def f() -> None: pass [builtins fixtures/list.pyi] + +[case testDisallowAnyPartlyTypedCallable] +# flags: --disallow-any=decorator --ignore-missing-imports +from typing import Callable, Any + +def d(f) -> Callable[[int, Any], Any]: pass + +@d # E: Parameter 2 of "f" becomes "Any" due to untyped decorator "d" +def f(i: int, s: str): pass +[builtins fixtures/list.pyi] From 5e7633317ac931d1e935eb41a90b59018a2161e2 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Tue, 13 Jun 2017 19:34:59 -0700 Subject: [PATCH 03/14] fix lint error --- mypy/messages.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypy/messages.py b/mypy/messages.py index 00cbf3b94b69c..882df9e84f6fb 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -916,6 +916,7 @@ def untyped_decorator(self, self.fail('{} of {} becomes "Any" due to untyped decorator{}'.format( prefix, func_name, postfix), context) + def capitalize(s: str) -> str: """Capitalize the first character of a string.""" if s == '': From bcc01558e320c3849dc561089731a2d80a8ec97d Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Wed, 14 Jun 2017 13:30:49 -0700 Subject: [PATCH 04/14] Fix crash that was caused by mutating a list instead of copying --- mypy/checker.py | 2 +- test-data/unit/check-flags.test | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index d3771a7c4fcaa..e1db95b2d6be3 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2315,7 +2315,7 @@ def check_for_untyped_decorator(self, num_missing_types = len(func_type.arg_types) - len(new_param_types) if num_missing_types > 0: - new_param_types.extend([AnyType()] * num_missing_types) + new_param_types = new_param_types + [AnyType()] * num_missing_types dec_name = None # type: Optional[str] if isinstance(dec_expr, NameExpr): diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 47537654afea9..048703c6d579f 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -608,3 +608,15 @@ def d(f) -> Callable[[int, Any], Any]: pass @d # E: Parameter 2 of "f" becomes "Any" due to untyped decorator "d" def f(i: int, s: str): pass [builtins fixtures/list.pyi] + +[case testDisallowAnyDecoratorReturnsCallableNoParams] +# flags: --disallow-any=decorator +from typing import Callable + +def d(p) -> Callable[[], int]: + return p + +@d # E: Parameter 1 of "f" becomes "Any" due to untyped decorator "d" +def f(i: int) -> int: + return i +[builtins fixtures/list.pyi] From 1e5a6a55db7b51876e49d137f23b1790bbb3a8ad Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Wed, 14 Jun 2017 16:07:38 -0700 Subject: [PATCH 05/14] Update the flag to report untyped functions after decorator transformation --- mypy/checker.py | 40 ++++------------- mypy/checkexpr.py | 13 ++++++ mypy/messages.py | 18 +++----- test-data/unit/check-flags.test | 80 ++++++++++++--------------------- 4 files changed, 56 insertions(+), 95 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index e1db95b2d6be3..96708854d2b22 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -28,7 +28,7 @@ AwaitExpr, PromoteExpr, Node, EnumCallExpr, ARG_POS, MDEF, CONTRAVARIANT, COVARIANT) -from mypy import nodes +from mypy import nodes, checkexpr from mypy.typeanal import has_any_from_unimported_type from mypy.types import ( Type, AnyType, CallableType, FunctionLike, Overloaded, TupleType, TypedDictType, @@ -2262,10 +2262,10 @@ def visit_decorator(self, e: Decorator) -> None: fullname = None if isinstance(d, RefExpr): fullname = d.fullname - self.check_for_untyped_decorator(e, d, dec) sig, t2 = self.expr_checker.check_call(dec, [temp], [nodes.ARG_POS], e, callable_name=fullname) + self.check_untyped_after_decorator(sig, e.func) sig = cast(FunctionLike, sig) sig = set_callable_name(sig, e.func) e.var.type = sig @@ -2294,39 +2294,15 @@ def visit_with_stmt(self, s: WithStmt) -> None: self.check_with_item(expr, target, s.target_type is None) self.accept(s.body) - def check_for_untyped_decorator(self, - decorated: Decorator, - dec_expr: Expression, - decorator_func_type: Type - ) -> None: - if 'decorator' not in self.options.disallow_any: + def check_untyped_after_decorator(self, typ: Type, func: FuncDef) -> None: + if 'decorator' not in self.options.disallow_any or self.is_stub: return - - func_type = decorated.func.type - if not func_type or not isinstance(func_type, CallableType): + if not isinstance(typ, CallableType): + self.msg.untyped_decorated_function(typ, func) return - new_ret_type = AnyType() # type: Type - new_param_types = [] # type: List[Type] - if (isinstance(decorator_func_type, CallableType) and - isinstance(decorator_func_type.ret_type, CallableType)): - new_param_types = decorator_func_type.ret_type.arg_types - new_ret_type = decorator_func_type.ret_type.ret_type - - num_missing_types = len(func_type.arg_types) - len(new_param_types) - if num_missing_types > 0: - new_param_types = new_param_types + [AnyType()] * num_missing_types - - dec_name = None # type: Optional[str] - if isinstance(dec_expr, NameExpr): - dec_name = dec_expr.name - - for i, old, new in zip(itertools.count(start=1), func_type.arg_types, new_param_types): - if isinstance(new, AnyType) and not isinstance(old, AnyType): - prefix = "Parameter {}".format(i) - self.msg.untyped_decorator(prefix, func_type.name, dec_name, dec_expr) - if isinstance(new_ret_type, AnyType) and not isinstance(func_type.ret_type, AnyType): - self.msg.untyped_decorator("Return type", func_type.name, dec_name, dec_expr) + if checkexpr.has_any_type(typ): + self.msg.untyped_decorated_function(typ, func) def check_async_with_item(self, expr: Expression, target: Expression, infer_lvalue_type: bool) -> None: diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 027c0d85a854b..9093aec831292 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2429,6 +2429,19 @@ def narrow_type_from_binder(self, expr: Expression, known_type: Type) -> Type: return known_type +def has_any_type(t: Type) -> bool: + """Whether t contains Any type""" + return t.accept(HasAnyType()) + + +class HasAnyType(types.TypeQuery[bool]): + def __init__(self) -> None: + super().__init__(any) + + def visit_any(self, t: AnyType) -> bool: + return True + + def has_coroutine_decorator(t: Type) -> bool: """Whether t came from a function decorated with `@coroutine`.""" return isinstance(t, Instance) and t.type.fullname() == 'typing.AwaitableGenerator' diff --git a/mypy/messages.py b/mypy/messages.py index 882df9e84f6fb..f21adf224d156 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -904,18 +904,12 @@ def typeddict_item_name_not_found(self, def type_arguments_not_allowed(self, context: Context) -> None: self.fail('Parameterized generics cannot be used with class or instance checks', context) - def untyped_decorator(self, - prefix: str, - func_name: str, - decorator_name: Optional[str], - context: Context, - ) -> None: - postfix = '' - if decorator_name: - postfix = ' "{}"'.format(decorator_name) - self.fail('{} of {} becomes "Any" due to untyped decorator{}'.format( - prefix, func_name, postfix), context) - + def untyped_decorated_function(self, typ: Type, context: Context): + if isinstance(typ, AnyType): + self.fail("Function is untyped after decorator transformation", context) + else: + self.fail('Type of decorated function contains type "Any" ({})'.format( + self.format(typ)), context) def capitalize(s: str) -> str: """Capitalize the first character of a string.""" diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 048703c6d579f..7431ca99dcf39 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -516,30 +516,21 @@ x, y = 1, 2 # type: Unchecked, Unchecked main:4: error: Type of variable becomes "Any" due to an unfollowed import main:6: error: A type on this line becomes "Any" due to an unfollowed import -[case testDisallowAnyDecoratorSimple] +[case testDisallowAnyDecoratorUnannotatedDecorator] # flags: --disallow-any=decorator from typing import Any def d(f): return f -@d # no error -def f(x: Any) -> Any: pass -@d # error -def g(x: Any) -> None: pass -@d # no error -def h(x): pass -@d # errors -def i(x: int) -> None: pass -@d # errors -def j() -> None: pass +@d +def f(x: Any) -> Any: # E: Function is untyped after decorator transformation + pass +@d +def h(x): # E: Function is untyped after decorator transformation + pass [builtins fixtures/list.pyi] -[out] -main:9: error: Return type of "g" becomes "Any" due to untyped decorator "d" -main:13: error: Parameter 1 of "i" becomes "Any" due to untyped decorator "d" -main:13: error: Return type of "i" becomes "Any" due to untyped decorator "d" -main:15: error: Return type of "j" becomes "Any" due to untyped decorator "d" -[case testDisallowAnyDecoratorMultiple] +[case testDisallowAnyDecoratorErrorIsReportedOnlyOnce] # flags: --disallow-any=decorator def d(f): @@ -548,65 +539,52 @@ def d(f): def d2(f): return f -@d # errors -@d2 # errors -@d # errors -def f(x: int) -> None: pass -[out] -main:9: error: Parameter 1 of "f" becomes "Any" due to untyped decorator "d" -main:9: error: Return type of "f" becomes "Any" due to untyped decorator "d" -main:10: error: Parameter 1 of "f" becomes "Any" due to untyped decorator "d2" -main:10: error: Return type of "f" becomes "Any" due to untyped decorator "d2" -main:11: error: Parameter 1 of "f" becomes "Any" due to untyped decorator "d" -main:11: error: Return type of "f" becomes "Any" due to untyped decorator "d" - +@d +@d2 +@d +def f(x: int) -> None: pass # E: Function is untyped after decorator transformation [case testDisallowAnyDecoratorReturnAny] # flags: --disallow-any=decorator from typing import Any def d(f) -> Any: return f -def d2(f: int) -> Any: - return f -@d2 # E: Return type of "f" becomes "Any" due to untyped decorator "d2" -@d # E: Return type of "f" becomes "Any" due to untyped decorator "d" -def f() -> None: pass +@d +def f() -> None: pass # E: Function is untyped after decorator transformation [builtins fixtures/list.pyi] [case testDisallowAnyDecoratorReturnCallable] # flags: --disallow-any=decorator from typing import Any, Callable -def d(f) -> Callable[..., Any]: +def d(f) -> Callable[..., None]: return f -@d # errors -def g(i: int, s: str) -> None: pass +@d +def g(i: int, s: str) -> None: pass # E: Type of decorated function contains type "Any" (Callable[..., None]) -@d # E: Return type of "f" becomes "Any" due to untyped decorator "d" -def f() -> None: pass [builtins fixtures/list.pyi] -[out] -main:7: error: Parameter 1 of "g" becomes "Any" due to untyped decorator "d" -main:7: error: Parameter 2 of "g" becomes "Any" due to untyped decorator "d" -main:7: error: Return type of "g" becomes "Any" due to untyped decorator "d" - [case testDisallowAnyDecoratorNonexistentDecorator] # flags: --disallow-any=decorator --ignore-missing-imports from nonexistent import d -@d # E: Return type of "f" becomes "Any" due to untyped decorator "d" -def f() -> None: pass +@d +def f() -> None: pass # E: Function is untyped after decorator transformation [builtins fixtures/list.pyi] [case testDisallowAnyPartlyTypedCallable] # flags: --disallow-any=decorator --ignore-missing-imports -from typing import Callable, Any +from typing import Callable, Any, List def d(f) -> Callable[[int, Any], Any]: pass +def d2(f) -> Callable[[int], List[Any]]: pass -@d # E: Parameter 2 of "f" becomes "Any" due to untyped decorator "d" -def f(i: int, s: str): pass +@d +def f(i: int, s: str) -> None: # E: Type of decorated function contains type "Any" (Callable[[int, Any], Any]) + pass +@d2 +def g(i: int) -> None: # E: Type of decorated function contains type "Any" (Callable[[int], List[Any]]) + pass [builtins fixtures/list.pyi] [case testDisallowAnyDecoratorReturnsCallableNoParams] @@ -616,7 +594,7 @@ from typing import Callable def d(p) -> Callable[[], int]: return p -@d # E: Parameter 1 of "f" becomes "Any" due to untyped decorator "d" -def f(i: int) -> int: +@d +def f(i): return i [builtins fixtures/list.pyi] From 1ce158493e5008b8aff7e10b3f0030a5fc72e2f9 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Wed, 14 Jun 2017 16:10:39 -0700 Subject: [PATCH 06/14] Rename option to `--disallow-any=decorated` --- mypy/checker.py | 2 +- mypy/main.py | 2 +- test-data/unit/check-flags.test | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 96708854d2b22..892981be204c0 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2295,7 +2295,7 @@ def visit_with_stmt(self, s: WithStmt) -> None: self.accept(s.body) def check_untyped_after_decorator(self, typ: Type, func: FuncDef) -> None: - if 'decorator' not in self.options.disallow_any or self.is_stub: + if 'decorated' not in self.options.disallow_any or self.is_stub: return if not isinstance(typ, CallableType): self.msg.untyped_decorated_function(typ, func) diff --git a/mypy/main.py b/mypy/main.py index ebc9be4c50fc9..0866bd7eb2b82 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -97,7 +97,7 @@ def type_check_only(sources: List[BuildSource], bin_dir: str, options: Options) options=options) -disallow_any_options = ['unimported', 'unannotated', 'decorator'] +disallow_any_options = ['unimported', 'unannotated', 'decorated'] def disallow_any_argument_type(raw_options: str) -> List[str]: diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 7431ca99dcf39..33d68c13ffd01 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -517,7 +517,7 @@ main:4: error: Type of variable becomes "Any" due to an unfollowed import main:6: error: A type on this line becomes "Any" due to an unfollowed import [case testDisallowAnyDecoratorUnannotatedDecorator] -# flags: --disallow-any=decorator +# flags: --disallow-any=decorated from typing import Any def d(f): @@ -531,7 +531,7 @@ def h(x): # E: Function is untyped after decorator transformation pass [builtins fixtures/list.pyi] [case testDisallowAnyDecoratorErrorIsReportedOnlyOnce] -# flags: --disallow-any=decorator +# flags: --disallow-any=decorated def d(f): return f @@ -544,7 +544,7 @@ def d2(f): @d def f(x: int) -> None: pass # E: Function is untyped after decorator transformation [case testDisallowAnyDecoratorReturnAny] -# flags: --disallow-any=decorator +# flags: --disallow-any=decorated from typing import Any def d(f) -> Any: @@ -554,7 +554,7 @@ def d(f) -> Any: def f() -> None: pass # E: Function is untyped after decorator transformation [builtins fixtures/list.pyi] [case testDisallowAnyDecoratorReturnCallable] -# flags: --disallow-any=decorator +# flags: --disallow-any=decorated from typing import Any, Callable def d(f) -> Callable[..., None]: @@ -565,7 +565,7 @@ def g(i: int, s: str) -> None: pass # E: Type of decorated function contains ty [builtins fixtures/list.pyi] [case testDisallowAnyDecoratorNonexistentDecorator] -# flags: --disallow-any=decorator --ignore-missing-imports +# flags: --disallow-any=decorated --ignore-missing-imports from nonexistent import d @d @@ -573,7 +573,7 @@ def f() -> None: pass # E: Function is untyped after decorator transformation [builtins fixtures/list.pyi] [case testDisallowAnyPartlyTypedCallable] -# flags: --disallow-any=decorator --ignore-missing-imports +# flags: --disallow-any=decorated --ignore-missing-imports from typing import Callable, Any, List def d(f) -> Callable[[int, Any], Any]: pass @@ -588,7 +588,7 @@ def g(i: int) -> None: # E: Type of decorated function contains type "Any" (Call [builtins fixtures/list.pyi] [case testDisallowAnyDecoratorReturnsCallableNoParams] -# flags: --disallow-any=decorator +# flags: --disallow-any=decorated from typing import Callable def d(p) -> Callable[[], int]: From ee0e7b6b702752f67391b26becdfdf2778b3e86c Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Wed, 14 Jun 2017 16:13:04 -0700 Subject: [PATCH 07/14] Fix lint error --- mypy/messages.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/messages.py b/mypy/messages.py index f21adf224d156..8c0f057b26669 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -904,13 +904,14 @@ def typeddict_item_name_not_found(self, def type_arguments_not_allowed(self, context: Context) -> None: self.fail('Parameterized generics cannot be used with class or instance checks', context) - def untyped_decorated_function(self, typ: Type, context: Context): + def untyped_decorated_function(self, typ: Type, context: Context) -> None: if isinstance(typ, AnyType): self.fail("Function is untyped after decorator transformation", context) else: self.fail('Type of decorated function contains type "Any" ({})'.format( self.format(typ)), context) + def capitalize(s: str) -> str: """Capitalize the first character of a string.""" if s == '': From 799522e23fdd025fcf5ec8d0b930eac45e44aa51 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Mon, 19 Jun 2017 14:09:28 -0700 Subject: [PATCH 08/14] Fix travis failure --- mypy/checker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 892981be204c0..2d3d671166630 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -28,7 +28,7 @@ AwaitExpr, PromoteExpr, Node, EnumCallExpr, ARG_POS, MDEF, CONTRAVARIANT, COVARIANT) -from mypy import nodes, checkexpr +from mypy import nodes from mypy.typeanal import has_any_from_unimported_type from mypy.types import ( Type, AnyType, CallableType, FunctionLike, Overloaded, TupleType, TypedDictType, @@ -2301,7 +2301,7 @@ def check_untyped_after_decorator(self, typ: Type, func: FuncDef) -> None: self.msg.untyped_decorated_function(typ, func) return - if checkexpr.has_any_type(typ): + if mypy.checkexpr.has_any_type(typ): self.msg.untyped_decorated_function(typ, func) def check_async_with_item(self, expr: Expression, target: Expression, From 84657e80ec3fb8ba028b971ef3c94f41bd0a0f78 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Mon, 19 Jun 2017 14:11:28 -0700 Subject: [PATCH 09/14] Fix test naming to be more consistent --- test-data/unit/check-flags.test | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 33d68c13ffd01..a2edc29a02b85 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -516,7 +516,7 @@ x, y = 1, 2 # type: Unchecked, Unchecked main:4: error: Type of variable becomes "Any" due to an unfollowed import main:6: error: A type on this line becomes "Any" due to an unfollowed import -[case testDisallowAnyDecoratorUnannotatedDecorator] +[case testDisallowAnyDecoratedUnannotatedDecorator] # flags: --disallow-any=decorated from typing import Any @@ -530,7 +530,7 @@ def f(x: Any) -> Any: # E: Function is untyped after decorator transformation def h(x): # E: Function is untyped after decorator transformation pass [builtins fixtures/list.pyi] -[case testDisallowAnyDecoratorErrorIsReportedOnlyOnce] +[case testDisallowAnyDecoratedErrorIsReportedOnlyOnce] # flags: --disallow-any=decorated def d(f): @@ -543,7 +543,7 @@ def d2(f): @d2 @d def f(x: int) -> None: pass # E: Function is untyped after decorator transformation -[case testDisallowAnyDecoratorReturnAny] +[case testDisallowAnyDecoratedReturnAny] # flags: --disallow-any=decorated from typing import Any @@ -553,7 +553,7 @@ def d(f) -> Any: @d def f() -> None: pass # E: Function is untyped after decorator transformation [builtins fixtures/list.pyi] -[case testDisallowAnyDecoratorReturnCallable] +[case testDisallowAnyDecoratedReturnCallable] # flags: --disallow-any=decorated from typing import Any, Callable @@ -564,7 +564,7 @@ def d(f) -> Callable[..., None]: def g(i: int, s: str) -> None: pass # E: Type of decorated function contains type "Any" (Callable[..., None]) [builtins fixtures/list.pyi] -[case testDisallowAnyDecoratorNonexistentDecorator] +[case testDisallowAnyDecoratedNonexistentDecorator] # flags: --disallow-any=decorated --ignore-missing-imports from nonexistent import d @@ -572,7 +572,7 @@ from nonexistent import d def f() -> None: pass # E: Function is untyped after decorator transformation [builtins fixtures/list.pyi] -[case testDisallowAnyPartlyTypedCallable] +[case testDisallowAnyDecoratedPartlyTypedCallable] # flags: --disallow-any=decorated --ignore-missing-imports from typing import Callable, Any, List @@ -587,7 +587,7 @@ def g(i: int) -> None: # E: Type of decorated function contains type "Any" (Call pass [builtins fixtures/list.pyi] -[case testDisallowAnyDecoratorReturnsCallableNoParams] +[case testDisallowAnyDecoratedReturnsCallableNoParams] # flags: --disallow-any=decorated from typing import Callable From 6f3aa9c71f1da56ee573ae8d80eb1d4778dc0c46 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Mon, 19 Jun 2017 14:16:14 -0700 Subject: [PATCH 10/14] Allow decorators return types that are not `Callable` --- mypy/checker.py | 3 --- test-data/unit/check-flags.test | 9 +++++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 2d3d671166630..b19a15b1af4bf 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2297,9 +2297,6 @@ def visit_with_stmt(self, s: WithStmt) -> None: def check_untyped_after_decorator(self, typ: Type, func: FuncDef) -> None: if 'decorated' not in self.options.disallow_any or self.is_stub: return - if not isinstance(typ, CallableType): - self.msg.untyped_decorated_function(typ, func) - return if mypy.checkexpr.has_any_type(typ): self.msg.untyped_decorated_function(typ, func) diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index a2edc29a02b85..c1d3a9a4ae018 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -598,3 +598,12 @@ def d(p) -> Callable[[], int]: def f(i): return i [builtins fixtures/list.pyi] + +[case testDisallowAnyDecoratedDecoratorReturnsNonCallable] +# flags: --disallow-any=decorated +def d(p) -> int: + return p(0) + +@d +def f(i): + return i From 005a71e31662a1a2e64c7542c20c18ca3929c65a Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Mon, 19 Jun 2017 14:24:38 -0700 Subject: [PATCH 11/14] Add documentation for `--disallow-any=decorated` --- docs/source/command_line.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/source/command_line.rst b/docs/source/command_line.rst index ad7555bb2759d..e4807b3b2d54b 100644 --- a/docs/source/command_line.rst +++ b/docs/source/command_line.rst @@ -278,7 +278,7 @@ Here are some more useful flags: - ``--disallow-any`` disallows various types of ``Any`` in a module. The option takes a comma-separated list of the following values: - ``unimported``, ``unannotated``. + ``unimported``, ``unannotated``, ``decorated``. ``unimported`` disallows usage of types that come from unfollowed imports (such types become aliases for ``Any``). Unfollowed imports occur either @@ -290,6 +290,9 @@ Here are some more useful flags: of the parameters or the return type). ``unannotated`` option is interchangeable with ``--disallow-untyped-defs``. + ``decorated`` disallows functions that have ``Any`` in their signature + after decorator transformation. + - ``--disallow-untyped-defs`` reports an error whenever it encounters a function definition without type annotations. From 2eb7b61d0a1d6eb142a55be16f827fae5939df42 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Tue, 20 Jun 2017 17:56:47 -0700 Subject: [PATCH 12/14] Remove unused import --- mypy/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/messages.py b/mypy/messages.py index 669aa75c47ba7..c4cb6569aca7b 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -6,7 +6,7 @@ import re import difflib -from typing import cast, List, Dict, Any, Sequence, Iterable, Tuple, Optional +from typing import cast, List, Dict, Any, Sequence, Iterable, Tuple from mypy.erasetype import erase_type from mypy.errors import Errors From 3b7a079d189cf82da3318d20b99eb8d7e0a68b7e Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Tue, 20 Jun 2017 18:13:53 -0700 Subject: [PATCH 13/14] Add extra test cases --- test-data/unit/check-flags.test | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index b8e922244dc73..260a26eef9569 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -578,12 +578,16 @@ from typing import Callable, Any, List def d(f) -> Callable[[int, Any], Any]: pass def d2(f) -> Callable[[int], List[Any]]: pass +def d3(f) -> Callable[[int], List[str]]: pass @d def f(i: int, s: str) -> None: # E: Type of decorated function contains type "Any" (Callable[[int, Any], Any]) pass @d2 -def g(i: int) -> None: # E: Type of decorated function contains type "Any" (Callable[[int], List[Any]]) +def g(i: int) -> None: # E: Type of decorated function contains type "Any" (Callable[[int], List[Any]]) + pass +@d3 +def h(i: int) -> None: # no error pass [builtins fixtures/list.pyi] @@ -608,6 +612,30 @@ def d(p) -> int: def f(i): return i +[case testDisallowAnyDecoratedUntypedUndecoratedFunction] +# flags: --disallow-any=decorated +from typing import Callable + +def f(i): # no error + return i + +[case testDisallowAnyDecoratedTwoDecorators] +# flags: --disallow-any=decorated +from typing import Callable + +def typed_dec(f) -> Callable[[], int]: pass +def untyped_dec(f): pass + +@typed_dec +@untyped_dec +def f(): # no error + return i + +@untyped_dec +@typed_dec +def g(): # E: Function is untyped after decorator transformation + return i + [case testDisallowAnyExprSimple] # flags: --disallow-any=expr from typing import Any From 730e61808fcb20027b4643578225777ae393f2c0 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Wed, 21 Jun 2017 14:16:19 -0700 Subject: [PATCH 14/14] Add test for decorator returning `Callable` with parameter that takes `Any` --- test-data/unit/check-flags.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 260a26eef9569..32ea0195cf69b 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -578,7 +578,7 @@ from typing import Callable, Any, List def d(f) -> Callable[[int, Any], Any]: pass def d2(f) -> Callable[[int], List[Any]]: pass -def d3(f) -> Callable[[int], List[str]]: pass +def d3(f) -> Callable[[Any], List[str]]: pass @d def f(i: int, s: str) -> None: # E: Type of decorated function contains type "Any" (Callable[[int, Any], Any]) @@ -587,7 +587,7 @@ def f(i: int, s: str) -> None: # E: Type of decorated function contains type "A def g(i: int) -> None: # E: Type of decorated function contains type "Any" (Callable[[int], List[Any]]) pass @d3 -def h(i: int) -> None: # no error +def h(i: int) -> None: # E: Type of decorated function contains type "Any" (Callable[[Any], List[str]]) pass [builtins fixtures/list.pyi]