diff --git a/docs/source/command_line.rst b/docs/source/command_line.rst index 3ada442c619e..d188b95e0e1b 100644 --- a/docs/source/command_line.rst +++ b/docs/source/command_line.rst @@ -366,6 +366,9 @@ Here are some more useful flags: also currently ignores functions with an empty body or a body that is just ellipsis (``...``), since these can be valid as abstract methods. +- ``--warn-return-any`` causes mypy to generate a warning when returning a value + with type ``Any`` from a function declared with a non- ``Any`` return type. + - ``--strict-boolean`` will make using non-boolean expressions in conditions an error. This means ``if x`` and ``while x`` are disallowed when ``x`` has any type other than ``bool``. Instead use explicit checks like ``if x > 0`` or diff --git a/docs/source/config_file.rst b/docs/source/config_file.rst index 001f6c9fb042..8eb61e2b9d91 100644 --- a/docs/source/config_file.rst +++ b/docs/source/config_file.rst @@ -161,6 +161,11 @@ overridden by the pattern sections matching the module name. - ``warn_no_return`` (Boolean, default False) shows errors for missing return statements on some execution paths. +- ``warn_return_any`` (Boolean, default False) shows a warning when + returning a value with type ``Any`` from a function declared with a + non- ``Any`` return type. + + Example ******* diff --git a/mypy/checker.py b/mypy/checker.py index 5e2c385dee66..5beb52a39e56 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1699,6 +1699,10 @@ def check_return_stmt(self, s: ReturnStmt) -> None: typ = self.expr_checker.accept(s.expr, return_type) # Returning a value of type Any is always fine. if isinstance(typ, AnyType): + # (Unless you asked to be warned in that case, and the + # function is not declared to return Any) + if not isinstance(return_type, AnyType) and self.options.warn_return_any: + self.warn(messages.RETURN_ANY.format(return_type), s) return if self.is_unusable_type(return_type): diff --git a/mypy/main.py b/mypy/main.py index f38ff950343d..4d66a99aed4f 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -221,6 +221,9 @@ def add_invertible_flag(flag: str, help="warn about casting an expression to its inferred type") add_invertible_flag('--no-warn-no-return', dest='warn_no_return', default=True, help="do not warn about functions that end without returning") + add_invertible_flag('--warn-return-any', default=False, strict_flag=True, + help="warn about returning values of type Any" + " from non-Any typed functions") add_invertible_flag('--warn-unused-ignores', default=False, strict_flag=True, help="warn about unneeded '# type: ignore' comments") add_invertible_flag('--show-error-context', default=True, diff --git a/mypy/messages.py b/mypy/messages.py index b32c2b282a51..c8b65047d3af 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -27,6 +27,7 @@ MISSING_RETURN_STATEMENT = 'Missing return statement' INVALID_IMPLICIT_RETURN = 'Implicit return in function which does not return' INCOMPATIBLE_RETURN_VALUE_TYPE = 'Incompatible return value type' +RETURN_ANY = 'Returning Any from function with declared return type "{}"' RETURN_VALUE_EXPECTED = 'Return value expected' NO_RETURN_EXPECTED = 'Return statement in function which does not return' INVALID_EXCEPTION = 'Exception must be derived from BaseException' diff --git a/mypy/options.py b/mypy/options.py index 5a8903286b22..d44f97995c25 100644 --- a/mypy/options.py +++ b/mypy/options.py @@ -26,6 +26,7 @@ class Options: "strict_optional_whitelist", "show_none_errors", "warn_no_return", + "warn_return_any", "ignore_errors", "strict_boolean", } @@ -65,6 +66,10 @@ def __init__(self) -> None: # Warn about falling off the end of a function returning non-None self.warn_no_return = True + # Warn about returning objects of type Any when the function is + # declared with a precise type + self.warn_return_any = False + # Warn about unused '# type: ignore' comments self.warn_unused_ignores = False diff --git a/test-data/unit/check-warnings.test b/test-data/unit/check-warnings.test index dc2da82d029b..2f2d592b13fd 100644 --- a/test-data/unit/check-warnings.test +++ b/test-data/unit/check-warnings.test @@ -130,3 +130,38 @@ def f() -> int: # This might be an @abstractmethod, for example pass [out] + + +-- Returning Any +-- ------------- + +[case testReturnAnyFromTypedFunction] +# flags: --warn-return-any +from typing import Any +def g() -> Any: pass +def f() -> int: return g() +[out] +main:4: warning: Returning Any from function with declared return type "builtins.int" + +[case testReturnAnySilencedFromTypedFunction] +# flags: --warn-return-any +from typing import Any +def g() -> Any: pass +def f() -> int: + result = g() # type: int + return result +[out] + +[case testReturnAnyFromUntypedFunction] +# flags: --warn-return-any +from typing import Any +def g() -> Any: pass +def f(): return g() +[out] + +[case testReturnAnyFromAnyTypedFunction] +# flags: --warn-return-any +from typing import Any +def g() -> Any: pass +def f() -> Any: return g() +[out]