Skip to content

Commit 675b3b6

Browse files
committed
Use empty context as fallback for return statements
1 parent bf70dab commit 675b3b6

File tree

2 files changed

+59
-4
lines changed

2 files changed

+59
-4
lines changed

mypy/checker.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
ContinueStmt,
9191
Decorator,
9292
DelStmt,
93+
DictExpr,
9394
EllipsisExpr,
9495
Expression,
9596
ExpressionStmt,
@@ -124,6 +125,7 @@
124125
RaiseStmt,
125126
RefExpr,
126127
ReturnStmt,
128+
SetExpr,
127129
StarExpr,
128130
Statement,
129131
StrExpr,
@@ -4859,6 +4861,38 @@ def visit_return_stmt(self, s: ReturnStmt) -> None:
48594861
self.check_return_stmt(s)
48604862
self.binder.unreachable()
48614863

4864+
def infer_context_dependent(
4865+
self, expr: Expression, type_ctx: Type, allow_none_func_call: bool
4866+
) -> ProperType:
4867+
"""Infer type of an expression with fallback to empty type context."""
4868+
with self.msg.filter_errors(filter_errors=True, save_filtered_errors=True) as msg:
4869+
with self.local_type_map as type_map:
4870+
typ = get_proper_type(
4871+
self.expr_checker.accept(
4872+
expr, type_ctx, allow_none_return=allow_none_func_call
4873+
)
4874+
)
4875+
if not msg.has_new_errors():
4876+
self.store_types(type_map)
4877+
return typ
4878+
4879+
# If there are errors with the original type context, try re-inferring in empty context.
4880+
original_messages = msg.filtered_errors()
4881+
original_type_map = type_map
4882+
with self.msg.filter_errors(filter_errors=True, save_filtered_errors=True) as msg:
4883+
with self.local_type_map as type_map:
4884+
alt_typ = get_proper_type(
4885+
self.expr_checker.accept(expr, None, allow_none_return=allow_none_func_call)
4886+
)
4887+
if not msg.has_new_errors() and is_proper_subtype(alt_typ, typ):
4888+
self.store_types(type_map)
4889+
return alt_typ
4890+
4891+
# If empty fallback didn't work, use results from the original type context.
4892+
self.msg.add_errors(original_messages)
4893+
self.store_types(original_type_map)
4894+
return typ
4895+
48624896
def check_return_stmt(self, s: ReturnStmt) -> None:
48634897
defn = self.scope.current_function()
48644898
if defn is not None:
@@ -4891,11 +4925,18 @@ def check_return_stmt(self, s: ReturnStmt) -> None:
48914925
allow_none_func_call = is_lambda or declared_none_return or declared_any_return
48924926

48934927
# Return with a value.
4894-
typ = get_proper_type(
4895-
self.expr_checker.accept(
4896-
s.expr, return_type, allow_none_return=allow_none_func_call
4928+
if isinstance(s.expr, (CallExpr, ListExpr, TupleExpr, DictExpr, SetExpr, OpExpr)):
4929+
# For expressions that (strongly) depend on type context (i.e. those that
4930+
# are handled like a function call), we allow fallback to empty type context
4931+
# in case of errors, this improves user experience in some cases,
4932+
# see e.g. testReturnFallbackInference.
4933+
typ = self.infer_context_dependent(s.expr, return_type, allow_none_func_call)
4934+
else:
4935+
typ = get_proper_type(
4936+
self.expr_checker.accept(
4937+
s.expr, return_type, allow_none_return=allow_none_func_call
4938+
)
48974939
)
4898-
)
48994940
# Treat NotImplemented as having type Any, consistent with its
49004941
# definition in typeshed prior to python/typeshed#4222.
49014942
if (

test-data/unit/check-inference-context.test

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1540,3 +1540,17 @@ def f(x: dict[str, Union[str, None, int]]) -> None:
15401540
def g(x: Optional[dict[str, Any]], s: Optional[str]) -> None:
15411541
f(x or {'x': s})
15421542
[builtins fixtures/dict.pyi]
1543+
1544+
[case testReturnFallbackInference]
1545+
from typing import TypeVar, Union
1546+
1547+
T = TypeVar("T")
1548+
def foo(x: list[T]) -> tuple[T, ...]: ...
1549+
1550+
def bar(x: list[int]) -> tuple[Union[str, int], ...]:
1551+
return foo(x)
1552+
1553+
def bar2(x: list[int]) -> tuple[Union[str, int], ...]:
1554+
y = foo(x)
1555+
return y
1556+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)