Skip to content

Commit 91a3b0b

Browse files
Fix: Infer return types from return statements (fixes #20631)
1 parent 3703273 commit 91a3b0b

2 files changed

Lines changed: 56 additions & 0 deletions

File tree

mypy/checker.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@
169169
unify_generic_callable,
170170
)
171171
from mypy.traverser import TraverserVisitor, all_return_statements, has_return_statement
172+
from mypy.suggestions import get_return_types
172173
from mypy.treetransform import TransformVisitor
173174
from mypy.typeanal import check_for_explicit_any, has_any_from_unimported_type, make_optional_type
174175
from mypy.typeops import (
@@ -1600,6 +1601,38 @@ def check_func_def(
16001601
):
16011602
self.note(message_registry.EMPTY_BODY_ABSTRACT, defn)
16021603

1604+
# Infer return type from return statements if function has no explicit return type annotation
1605+
if isinstance(item, FuncDef) and isinstance(typ, CallableType):
1606+
def is_unannotated_any(t: Type) -> bool:
1607+
if not isinstance(t, ProperType):
1608+
return False
1609+
return isinstance(t, AnyType) and t.type_of_any == TypeOfAny.unannotated
1610+
1611+
ret_type_proper = get_proper_type(typ.ret_type)
1612+
# Only infer for functions without explicit return type annotations
1613+
# Skip generators and coroutines as they have special return type handling
1614+
if (
1615+
is_unannotated_any(ret_type_proper)
1616+
and not defn.is_generator
1617+
and not defn.is_coroutine
1618+
and not self.dynamic_funcs[-1]
1619+
):
1620+
# Collect return types from return statements
1621+
# Merge types from all type maps to get complete picture
1622+
all_return_types: list[Type] = []
1623+
for type_map in self._type_maps:
1624+
return_types_list = get_return_types(type_map, item)
1625+
all_return_types.extend(return_types_list)
1626+
1627+
if all_return_types:
1628+
# Create union of all return types
1629+
inferred_ret_type = make_simplified_union(all_return_types)
1630+
# Update the function's return type
1631+
typ = typ.copy_modified(ret_type=inferred_ret_type)
1632+
item.type = typ
1633+
# Update the return_types stack as well
1634+
self.return_types[-1] = inferred_ret_type
1635+
16031636
self.return_types.pop()
16041637

16051638
self.binder = old_binder

test-data/unit/check-statements.test

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,29 @@ def f() -> Iterator[int]:
8888
return "foo" # E: No return value expected
8989
[out]
9090

91+
[case testInferReturnTypeFromReturnStatements]
92+
# Test that mypy infers return type from return statements when function has no explicit return type annotation
93+
def f(x: int):
94+
if x > 0:
95+
return "positive"
96+
else:
97+
return 0
98+
99+
reveal_type(f(1)) # N: Revealed type is "builtins.str | builtins.int"
100+
101+
def g(x: bool):
102+
return x
103+
104+
reveal_type(g(True)) # N: Revealed type is "builtins.bool"
105+
106+
def h(x: int):
107+
if x > 0:
108+
return "positive"
109+
return None
110+
111+
reveal_type(h(1)) # N: Revealed type is "builtins.str | None"
112+
113+
[out]
91114

92115
-- If statement
93116
-- ------------

0 commit comments

Comments
 (0)