Skip to content

Commit b6585ff

Browse files
committed
speedup typechecking of nested if expressions
Deeply nested if/else expressions have a worst-case exponential behavior. This will for instance manifest when returning literal values which cause repeated analysis of conditional branches with subtly different type context for each literal. This can be optimized by observing that a simple literal context will yield the same analysis as its fallback type, and likewise, two literals of the same fallback type will yield the same analysis. In those case we can avoid the repeated analysis and prevent the worst-case exponential behavior. Fixes #9591
1 parent c6cf7cd commit b6585ff

File tree

1 file changed

+16
-1
lines changed

1 file changed

+16
-1
lines changed

mypy/checkexpr.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
try_expanding_sum_type_to_union, tuple_fallback, make_simplified_union,
7070
true_only, false_only, erase_to_union_or_bound, function_type,
7171
callable_type, try_getting_str_literals, custom_special_method,
72-
is_literal_type_like,
72+
is_literal_type_like, simple_literal_type,
7373
)
7474
from mypy.message_registry import ErrorMessage
7575
import mypy.errorcodes as codes
@@ -3894,9 +3894,15 @@ def visit_conditional_expr(self, e: ConditionalExpr, allow_none_return: bool = F
38943894
if_type = self.analyze_cond_branch(if_map, e.if_expr, context=ctx,
38953895
allow_none_return=allow_none_return)
38963896

3897+
# check for simple literals for fast path below
3898+
ctx_lit = simple_literal_type(get_proper_type(ctx)) if ctx is not None else None
3899+
if_lit = simple_literal_type(get_proper_type(if_type)) if if_type != ctx else None
3900+
38973901
# Analyze the right branch using full type context and store the type
38983902
full_context_else_type = self.analyze_cond_branch(else_map, e.else_expr, context=ctx,
38993903
allow_none_return=allow_none_return)
3904+
3905+
# print(f'{ctx} {if_type} {full_context_else_type}')
39003906
if not mypy.checker.is_valid_inferred_type(if_type):
39013907
# Analyze the right branch disregarding the left branch.
39023908
else_type = full_context_else_type
@@ -3910,6 +3916,15 @@ def visit_conditional_expr(self, e: ConditionalExpr, allow_none_return: bool = F
39103916
if_type = self.analyze_cond_branch(if_map, e.if_expr, context=else_type,
39113917
allow_none_return=allow_none_return)
39123918

3919+
elif if_type == ctx or (if_lit is not None and (if_lit == ctx or if_lit == ctx_lit)):
3920+
# There is no point re-running the analysis if if_type is equal to ctx.
3921+
# That would be an exact duplicate of the work we just did.
3922+
# Likewise, when simple literals are involved we can avoid duplicate work as the
3923+
# join below will turn them back into the relevant fallback type.
3924+
# This optimization is particularly important to avoid exponential blowup with nested
3925+
# if/else expressions: https://github.com/python/mypy/issues/9591
3926+
# TODO: would checking for is_proper_subtype also work and cover more cases?
3927+
else_type = full_context_else_type
39133928
else:
39143929
# Analyze the right branch in the context of the left
39153930
# branch's type.

0 commit comments

Comments
 (0)