diff --git a/mypy/checker.py b/mypy/checker.py index 02fd439ba7aa..231b95455586 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1217,7 +1217,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) self.accept(item.body) unreachable = self.binder.is_unreachable() - if self.options.warn_no_return and not unreachable: + if not unreachable and not body_is_trivial: if defn.is_generator or is_named_instance( self.return_types[-1], "typing.AwaitableGenerator" ): @@ -1228,17 +1228,29 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) return_type = self.get_coroutine_return_type(self.return_types[-1]) else: return_type = self.return_types[-1] - return_type = get_proper_type(return_type) - if not isinstance(return_type, (NoneType, AnyType)) and not body_is_trivial: - # Control flow fell off the end of a function that was - # declared to return a non-None type and is not - # entirely pass/Ellipsis/raise NotImplementedError. - if isinstance(return_type, UninhabitedType): - # This is a NoReturn function - self.fail(message_registry.INVALID_IMPLICIT_RETURN, defn) - else: - self.fail(message_registry.MISSING_RETURN_STATEMENT, defn) + + if self.options.warn_no_return: + if not isinstance(return_type, (NoneType, AnyType)): + # Control flow fell off the end of a function that was + # declared to return a non-None type and is not + # entirely pass/Ellipsis/raise NotImplementedError. + if isinstance(return_type, UninhabitedType): + # This is a NoReturn function + self.fail(message_registry.INVALID_IMPLICIT_RETURN, defn) + else: + self.fail(message_registry.MISSING_RETURN_STATEMENT, defn) + else: + # similar to code in check_return_stmt + self.check_subtype( + subtype_label="implicitly returns", + subtype=NoneType(), + supertype_label="expected", + supertype=return_type, + context=defn, + msg=message_registry.INCOMPATIBLE_RETURN_VALUE_TYPE, + code=codes.RETURN_VALUE, + ) self.return_types.pop() diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 0d3af684c9bb..ed4d2e72149b 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -410,6 +410,44 @@ async def h() -> NoReturn: # E: Implicit return in function which does not retu [builtins fixtures/dict.pyi] [typing fixtures/typing-async.pyi] +[case testNoWarnNoReturn] +# flags: --no-warn-no-return --strict-optional +import typing + +def implicit_optional_return(arg) -> typing.Optional[str]: + if arg: + return "false" + +def unsound_implicit_return(arg) -> str: # E: Incompatible return value type (implicitly returns "None", expected "str") + if arg: + return "false" + +def implicit_return_gen(arg) -> typing.Generator[int, None, typing.Optional[str]]: + yield 1 + +def unsound_implicit_return_gen(arg) -> typing.Generator[int, None, str]: # E: Incompatible return value type (implicitly returns "None", expected "str") + yield 1 +[builtins fixtures/dict.pyi] + +[case testNoWarnNoReturnNoStrictOptional] +# flags: --no-warn-no-return --no-strict-optional +import typing + +def implicit_optional_return(arg) -> typing.Optional[str]: + if arg: + return "false" + +def unsound_implicit_return(arg) -> str: + if arg: + return "false" + +def implicit_return_gen(arg) -> typing.Generator[int, None, typing.Optional[str]]: + yield 1 + +def unsound_implicit_return_gen(arg) -> typing.Generator[int, None, str]: + yield 1 +[builtins fixtures/dict.pyi] + [case testNoReturnImportFromTyping] from typing import NoReturn diff --git a/test-data/unit/check-inline-config.test b/test-data/unit/check-inline-config.test index 2eb41eade6fa..578d8eff7ff8 100644 --- a/test-data/unit/check-inline-config.test +++ b/test-data/unit/check-inline-config.test @@ -114,8 +114,8 @@ import a [file a.py] # mypy: no-warn-no-return -from typing import List -def foo() -> List: +from typing import Optional, List +def foo() -> Optional[List]: 20 [file b.py.2]