From e44b4700955bee6cd3da10cb9322a4a3330084d6 Mon Sep 17 00:00:00 2001 From: Lincoln Quirk Date: Sun, 12 Feb 2017 14:46:52 +0300 Subject: [PATCH 1/7] Warn if you try to return Any from a function which is declared to return a more specific type (and the warning is enabled). --- mypy/checker.py | 4 ++++ mypy/main.py | 2 ++ mypy/messages.py | 1 + mypy/options.py | 5 +++++ test-data/unit/check-warnings.test | 8 ++++++++ 5 files changed, 20 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index 5e2c385dee66f..b355ae9107e47 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, s) return if self.is_unusable_type(return_type): diff --git a/mypy/main.py b/mypy/main.py index f38ff950343da..2c9b4e06cac4e 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -221,6 +221,8 @@ 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 objects of type Any") 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 b32c2b282a515..7c21e5ab53a44 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 an object of type "Any"' 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 5a8903286b226..d44f97995c257 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 dc2da82d029b7..de39ba026efce 100644 --- a/test-data/unit/check-warnings.test +++ b/test-data/unit/check-warnings.test @@ -130,3 +130,11 @@ def f() -> int: # This might be an @abstractmethod, for example pass [out] + +[case testReturnAny] +# flags: --warn-return-any +from typing import Any +def g() -> Any: pass +def f() -> int: return g() +[out] +main:4: warning: Returning an object of type "Any" From 2dae3551a9a50d235fb6f539bfe702d5edb01258 Mon Sep 17 00:00:00 2001 From: Lincoln Quirk Date: Sun, 12 Feb 2017 14:57:08 +0300 Subject: [PATCH 2/7] improve 'returning Any' warning to indicate function type --- mypy/checker.py | 2 +- mypy/messages.py | 2 +- test-data/unit/check-warnings.test | 15 +++++++++++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index b355ae9107e47..5beb52a39e564 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1702,7 +1702,7 @@ def check_return_stmt(self, s: ReturnStmt) -> None: # (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, s) + self.warn(messages.RETURN_ANY.format(return_type), s) return if self.is_unusable_type(return_type): diff --git a/mypy/messages.py b/mypy/messages.py index 7c21e5ab53a44..a357c1516826d 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -27,7 +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 an object of type "Any"' +RETURN_ANY = 'Returning Any from function declared with 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/test-data/unit/check-warnings.test b/test-data/unit/check-warnings.test index de39ba026efce..b51f16e4fe5cd 100644 --- a/test-data/unit/check-warnings.test +++ b/test-data/unit/check-warnings.test @@ -131,10 +131,21 @@ def f() -> int: pass [out] -[case testReturnAny] + +-- 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 an object of type "Any" +main:4: warning: Returning Any from function declared with return type builtins.int + +[case testReturnAnyFromUntypedFunction] +# flags: --warn-return-any +from typing import Any +def g() -> Any: pass +def f(): return g() +[out] From 4d30bff7e2d693f9ff446e9eb4a4ef63e3842164 Mon Sep 17 00:00:00 2001 From: Lincoln Quirk Date: Sun, 12 Feb 2017 15:19:11 +0300 Subject: [PATCH 3/7] Document the warn-return-any option --- docs/source/command_line.rst | 3 +++ docs/source/config_file.rst | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/docs/source/command_line.rst b/docs/source/command_line.rst index 3ada442c619e5..d188b95e0e1b9 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 001f6c9fb0428..8eb61e2b9d91c 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 ******* From e47009346710952e826f269d450f12348b696586 Mon Sep 17 00:00:00 2001 From: Lincoln Quirk Date: Sun, 12 Feb 2017 16:47:30 +0300 Subject: [PATCH 4/7] add one test case for silenced 'return any' the way I recommend --- test-data/unit/check-warnings.test | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test-data/unit/check-warnings.test b/test-data/unit/check-warnings.test index b51f16e4fe5cd..c10736629edb6 100644 --- a/test-data/unit/check-warnings.test +++ b/test-data/unit/check-warnings.test @@ -143,6 +143,15 @@ def f() -> int: return g() [out] main:4: warning: Returning Any from function declared with 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 From 5163d0dc73284b76052f472e6bbd33a1cdeee71b Mon Sep 17 00:00:00 2001 From: Lincoln Quirk Date: Mon, 13 Feb 2017 08:48:35 +0300 Subject: [PATCH 5/7] Add quotes around type in RETURN_ANY message --- mypy/messages.py | 2 +- test-data/unit/check-warnings.test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index a357c1516826d..7b5406f397d0e 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -27,7 +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 declared with return type {}' +RETURN_ANY = 'Returning Any from function declared with 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/test-data/unit/check-warnings.test b/test-data/unit/check-warnings.test index c10736629edb6..3cd19a856b20d 100644 --- a/test-data/unit/check-warnings.test +++ b/test-data/unit/check-warnings.test @@ -141,7 +141,7 @@ from typing import Any def g() -> Any: pass def f() -> int: return g() [out] -main:4: warning: Returning Any from function declared with return type builtins.int +main:4: warning: Returning Any from function declared with return type "builtins.int" [case testReturnAnySilencedFromTypedFunction] # flags: --warn-return-any From b982e89d19dbdf667bb32c9c329e4d5dd077c5b6 Mon Sep 17 00:00:00 2001 From: Lincoln Quirk Date: Tue, 14 Feb 2017 08:25:15 +0300 Subject: [PATCH 6/7] Improve messages & docs for warn-return-any --- mypy/main.py | 2 +- mypy/messages.py | 2 +- test-data/unit/check-warnings.test | 9 ++++++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/mypy/main.py b/mypy/main.py index 2c9b4e06cac4e..1376e9fbda6a5 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -222,7 +222,7 @@ def add_invertible_flag(flag: str, 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 objects of type Any") + 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 7b5406f397d0e..c8b65047d3afb 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -27,7 +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 declared with return 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/test-data/unit/check-warnings.test b/test-data/unit/check-warnings.test index 3cd19a856b20d..2f2d592b13fd1 100644 --- a/test-data/unit/check-warnings.test +++ b/test-data/unit/check-warnings.test @@ -141,7 +141,7 @@ from typing import Any def g() -> Any: pass def f() -> int: return g() [out] -main:4: warning: Returning Any from function declared with return type "builtins.int" +main:4: warning: Returning Any from function with declared return type "builtins.int" [case testReturnAnySilencedFromTypedFunction] # flags: --warn-return-any @@ -158,3 +158,10 @@ 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] From d2f50201c166bfc893d1d9b1c4da5e69d3a45355 Mon Sep 17 00:00:00 2001 From: Lincoln Quirk Date: Tue, 14 Feb 2017 08:31:12 +0300 Subject: [PATCH 7/7] shorten line length for return-any cmdline option --- mypy/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/main.py b/mypy/main.py index 1376e9fbda6a5..4d66a99aed4fc 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -222,7 +222,8 @@ def add_invertible_flag(flag: str, 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") + 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,