Skip to content

Commit

Permalink
Check implicit None return is valid when using --no-warn-no-return
Browse files Browse the repository at this point in the history
  • Loading branch information
hauntsaninja committed Jul 23, 2022
1 parent b0e59b2 commit c7a3fc4
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 11 deletions.
33 changes: 22 additions & 11 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1040,7 +1040,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')):
return_type = self.get_generator_return_type(self.return_types[-1],
Expand All @@ -1049,17 +1049,28 @@ 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)
elif not self.options.warn_no_return:
# 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()

Expand Down
38 changes: 38 additions & 0 deletions test-data/unit/check-flags.test
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit c7a3fc4

Please sign in to comment.