From bb5d43924930413129be55838c26ccdf2dfe2701 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Wed, 13 Sep 2023 00:19:54 -0700 Subject: [PATCH 1/4] Warn for missing returns with explicit Any return types As discussed in https://github.com/python/mypy/issues/7511, mypy's `--warn-no-return` isn't really a question of type safety. It does however enforce a rule that is "dear to Guido's heart", and I think we should enforce it for functions that explicitly return Any as well. Fixes #16095 --- mypy/checker.py | 6 +++++- test-data/unit/check-overloading.test | 3 +++ test-data/unit/check-warnings.test | 18 ++++++++++++++++++ test-data/unit/deps.test | 6 +++++- test-data/unit/fine-grained.test | 2 ++ 5 files changed, 33 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index fa7c645873d0..204f411775f6 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1336,7 +1336,11 @@ def check_func_def( if self.options.warn_no_return: if ( not self.current_node_deferred - and not isinstance(return_type, (NoneType, AnyType)) + and not isinstance(return_type, NoneType) + and ( + not isinstance(return_type, AnyType) + or return_type.type_of_any == TypeOfAny.explicit + ) and show_error ): # Control flow fell off the end of a function that was diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 4546c7171856..0bbdf4032f19 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -187,6 +187,7 @@ def f(x: Any) -> Any: foo = 1 if int(): foo = "bar" # E: Incompatible types in assignment (expression has type "str", variable has type "int") + return foo @overload def g(x: 'A') -> 'B': ... @@ -4906,6 +4907,7 @@ def f() -> None: def g(x: T) -> Dict[int, T]: ... def g(*args, **kwargs) -> Any: reveal_type(h(C())) # N: Revealed type is "builtins.dict[builtins.str, __main__.C]" + return args @overload def h() -> None: ... @@ -4913,6 +4915,7 @@ def f() -> None: def h(x: T) -> Dict[str, T]: ... def h(*args, **kwargs) -> Any: reveal_type(g(C())) # N: Revealed type is "builtins.dict[builtins.int, __main__.C]" + return args [builtins fixtures/dict.pyi] [out] diff --git a/test-data/unit/check-warnings.test b/test-data/unit/check-warnings.test index 90f40777d6b7..5c9829f6a814 100644 --- a/test-data/unit/check-warnings.test +++ b/test-data/unit/check-warnings.test @@ -140,6 +140,24 @@ def f() -> int: pass [out] +[case testNoReturnExplicitAny] +# flags: --warn-no-return +from typing import Any +from unknown import Mystery # type: ignore[import] + +def maybe() -> bool: ... + +def implicit(x: Any): + if maybe(): + return x + +def explicit(x: Any) -> Any: # E: Missing return statement + if maybe(): + return x + +def mystery(x: Any) -> Mystery: + if maybe(): + return x -- Returning Any -- ------------- diff --git a/test-data/unit/deps.test b/test-data/unit/deps.test index c3295b79e4ed..5a26e2468ead 100644 --- a/test-data/unit/deps.test +++ b/test-data/unit/deps.test @@ -917,6 +917,7 @@ def f(x: str) -> submod.B: pass def f(x) -> Any: y: submod.C y.x + return y [file submod.py] class A: pass class B: pass @@ -959,6 +960,7 @@ def f(x: int) -> None: pass def f(x: str) -> str: pass def f(x: Any) -> Any: mod.g() + return mod [file mod.py] def g() -> int: pass @@ -976,6 +978,7 @@ def outer() -> None: def f(x: str) -> mod.B: pass def f(x: Any) -> Any: mod.g() + return mod [file mod.py] def g() -> int: pass @@ -997,6 +1000,7 @@ def outer() -> None: def f(x: str) -> str: pass def f(x: Any) -> Any: mod.g(str()) + return mod [file mod.py] from typing import overload @overload @@ -1018,7 +1022,7 @@ class Outer: @overload def f(self, x: str, cb=mod.g) -> str: pass def f(self, *args: Any, **kwargs: Any) -> Any: - pass + return args [file mod.py] from typing import overload @overload diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 68f72a2aa992..598f1effb9c9 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -6995,6 +6995,7 @@ def outer() -> None: def f(x: str) -> str: pass def f(x: Any) -> Any: y: int = mod.f() + return y [file mod.py] def f() -> int: pass @@ -7257,6 +7258,7 @@ def f(x: str) -> submod.B: pass def f(x) -> Any: y: submod.C y.x = int() + return y [file submod.py] import other class A: pass From d01be41cc614b2303e7d4a97c5acc43e09d307d5 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Wed, 13 Sep 2023 01:02:28 -0700 Subject: [PATCH 2/4] . --- mypyc/test-data/run-primitives.test | 1 + 1 file changed, 1 insertion(+) diff --git a/mypyc/test-data/run-primitives.test b/mypyc/test-data/run-primitives.test index b95f742977be..a8f1d0bf0667 100644 --- a/mypyc/test-data/run-primitives.test +++ b/mypyc/test-data/run-primitives.test @@ -168,6 +168,7 @@ def get_item(o: Any, k: Any) -> Any: return o[k] def set_item(o: Any, k: Any, v: Any) -> Any: o[k] = v + return o [file driver.py] from native import * assert neg(6) == -6 From cadc3ae9f445a0a58cd47343d6a599f800c7b7cb Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Thu, 14 Sep 2023 02:04:01 -0700 Subject: [PATCH 3/4] Update test-data/unit/check-warnings.test Co-authored-by: Nikita Sobolev --- test-data/unit/check-warnings.test | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test-data/unit/check-warnings.test b/test-data/unit/check-warnings.test index 5c9829f6a814..dfdff3b99a19 100644 --- a/test-data/unit/check-warnings.test +++ b/test-data/unit/check-warnings.test @@ -154,6 +154,16 @@ def implicit(x: Any): def explicit(x: Any) -> Any: # E: Missing return statement if maybe(): return x + +def explicit_with_none(x: Any) -> Any: + if maybe(): + return x + return None + +def explicit_with_just_return(x: Any) -> Any: + if maybe(): + return x + return def mystery(x: Any) -> Mystery: if maybe(): From 86800e8fce0693710c97df6eec970946d143de57 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 14 Sep 2023 09:04:21 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- test-data/unit/check-warnings.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-warnings.test b/test-data/unit/check-warnings.test index dfdff3b99a19..bf900882d780 100644 --- a/test-data/unit/check-warnings.test +++ b/test-data/unit/check-warnings.test @@ -154,12 +154,12 @@ def implicit(x: Any): def explicit(x: Any) -> Any: # E: Missing return statement if maybe(): return x - + def explicit_with_none(x: Any) -> Any: if maybe(): return x return None - + def explicit_with_just_return(x: Any) -> Any: if maybe(): return x