diff --git a/docs/source/command_line.rst b/docs/source/command_line.rst index 5b41a490102b4..dc1c38c85744a 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``, ``expr``. + ``unimported``, ``unannotated``, ``expr``, ``decorated``. ``unimported`` disallows usage of types that come from unfollowed imports (such types become aliases for ``Any``). Unfollowed imports occur either @@ -298,6 +298,9 @@ Here are some more useful flags: or casting to type ``Any`` is not allowed. Note that calling functions that take parameters of type ``Any`` is still allowed. + ``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. diff --git a/mypy/checker.py b/mypy/checker.py index 9562e5bc7fd8f..cb809cfed9dae 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2267,6 +2267,7 @@ def visit_decorator(self, e: Decorator) -> None: 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 @@ -2295,6 +2296,13 @@ 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_untyped_after_decorator(self, typ: Type, func: FuncDef) -> None: + if 'decorated' not in self.options.disallow_any or self.is_stub: + return + + if mypy.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: echk = self.expr_checker diff --git a/mypy/main.py b/mypy/main.py index b1c1529d06087..978db244b0895 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', 'expr', 'unannotated'] +disallow_any_options = ['unimported', 'expr', 'unannotated', 'decorated'] def disallow_any_argument_type(raw_options: str) -> List[str]: diff --git a/mypy/messages.py b/mypy/messages.py index 067cbd37f7447..c4cb6569aca7b 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -911,6 +911,13 @@ def disallowed_any_type(self, typ: Type, context: Context) -> None: message = 'Expression type contains "Any" (has type {})'.format(self.format(typ)) self.fail(message, 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.""" diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 08c5a40aaec37..32ea0195cf69b 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -516,6 +516,126 @@ 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 testDisallowAnyDecoratedUnannotatedDecorator] +# flags: --disallow-any=decorated +from typing import Any + +def d(f): + return f + +@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] +[case testDisallowAnyDecoratedErrorIsReportedOnlyOnce] +# flags: --disallow-any=decorated + +def d(f): + return f + +def d2(f): + return f + +@d +@d2 +@d +def f(x: int) -> None: pass # E: Function is untyped after decorator transformation +[case testDisallowAnyDecoratedReturnAny] +# flags: --disallow-any=decorated +from typing import Any + +def d(f) -> Any: + return f + +@d +def f() -> None: pass # E: Function is untyped after decorator transformation +[builtins fixtures/list.pyi] +[case testDisallowAnyDecoratedReturnCallable] +# flags: --disallow-any=decorated +from typing import Any, Callable + +def d(f) -> Callable[..., None]: + return f + +@d +def g(i: int, s: str) -> None: pass # E: Type of decorated function contains type "Any" (Callable[..., None]) + +[builtins fixtures/list.pyi] +[case testDisallowAnyDecoratedNonexistentDecorator] +# flags: --disallow-any=decorated --ignore-missing-imports +from nonexistent import d + +@d +def f() -> None: pass # E: Function is untyped after decorator transformation +[builtins fixtures/list.pyi] + +[case testDisallowAnyDecoratedPartlyTypedCallable] +# flags: --disallow-any=decorated --ignore-missing-imports +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[[Any], 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]]) + pass +@d3 +def h(i: int) -> None: # E: Type of decorated function contains type "Any" (Callable[[Any], List[str]]) + pass +[builtins fixtures/list.pyi] + +[case testDisallowAnyDecoratedReturnsCallableNoParams] +# flags: --disallow-any=decorated +from typing import Callable + +def d(p) -> Callable[[], int]: + return p + +@d +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 + +[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