Skip to content

Commit 1d2a30d

Browse files
author
Guido van Rossum
committed
Improve unification of conditional expressions when the left branch has an empty list/set/dict.
Fix #1094.
1 parent f4de7a5 commit 1d2a30d

File tree

2 files changed

+107
-18
lines changed

2 files changed

+107
-18
lines changed

mypy/checkexpr.py

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from mypy import messages
2828
from mypy.infer import infer_type_arguments, infer_function_type_arguments
2929
from mypy import join
30-
from mypy.subtypes import is_subtype
30+
from mypy.subtypes import is_subtype, is_equivalent
3131
from mypy import applytype
3232
from mypy import erasetype
3333
from mypy.checkmember import analyze_member_access, type_object_type
@@ -1403,6 +1403,7 @@ def check_for_comp(self, e: Union[GeneratorExpr, DictionaryComprehension]) -> No
14031403
def visit_conditional_expr(self, e: ConditionalExpr) -> Type:
14041404
cond_type = self.accept(e.cond)
14051405
self.check_not_void(cond_type, e)
1406+
ctx = self.chk.type_context[-1]
14061407

14071408
# Gain type information from isinstance if it is there
14081409
# but only for the current expression
@@ -1411,26 +1412,44 @@ def visit_conditional_expr(self, e: ConditionalExpr) -> Type:
14111412
self.chk.type_map,
14121413
self.chk.typing_mode_weak())
14131414

1414-
self.chk.binder.push_frame()
1415-
1416-
if if_map:
1417-
for var, type in if_map.items():
1418-
self.chk.binder.push(var, type)
1419-
1420-
if_type = self.accept(e.if_expr)
1421-
1422-
self.chk.binder.pop_frame()
1423-
self.chk.binder.push_frame()
1424-
1425-
if else_map:
1426-
for var, type in else_map.items():
1427-
self.chk.binder.push(var, type)
1415+
with self.chk.binder:
1416+
if if_map:
1417+
for var, type in if_map.items():
1418+
self.chk.binder.push(var, type)
1419+
if_type = self.accept(e.if_expr, context=ctx)
1420+
1421+
if has_unfinished_types(if_type):
1422+
# Analyze the right branch disregarding the left branch.
1423+
with self.chk.binder:
1424+
if else_map:
1425+
for var, type in else_map.items():
1426+
self.chk.binder.push(var, type)
1427+
else_type = self.accept(e.else_expr, context=ctx)
1428+
1429+
# If it would make a difference, re-analyze the left
1430+
# branch using the right branch's type as context.
1431+
if ctx is None or not is_equivalent(else_type, ctx):
1432+
# TODO: If it's possible that the previous analysis of
1433+
# the left branch produced errors that are avoided
1434+
# using this context, suppress those errors.
1435+
with self.chk.binder:
1436+
if if_map:
1437+
for var, type in if_map.items():
1438+
self.chk.binder.push(var, type)
1439+
if_type = self.accept(e.if_expr, context=else_type)
14281440

1429-
else_type = self.accept(e.else_expr, context=if_type)
1441+
else:
1442+
# Analyze the right branch in the context of the left
1443+
# branch's type.
1444+
with self.chk.binder:
1445+
if else_map:
1446+
for var, type in else_map.items():
1447+
self.chk.binder.push(var, type)
1448+
else_type = self.accept(e.else_expr, context=if_type)
14301449

1431-
self.chk.binder.pop_frame()
1450+
res = join.join_types(if_type, else_type)
14321451

1433-
return join.join_types(if_type, else_type)
1452+
return res
14341453

14351454
def visit_backquote_expr(self, e: BackquoteExpr) -> Type:
14361455
self.accept(e.expr)
@@ -1502,6 +1521,19 @@ def not_ready_callback(self, name: str, context: Context) -> None:
15021521
self.chk.handle_cannot_determine_type(name, context)
15031522

15041523

1524+
# TODO: What's a good name for this function?
1525+
def has_unfinished_types(t: Type) -> bool:
1526+
"""Check whether t has type variables replaced with 'None'.
1527+
1528+
This can happen when `[]` is evaluated without sufficient context.
1529+
"""
1530+
if isinstance(t, NoneTyp):
1531+
return True
1532+
if isinstance(t, Instance):
1533+
return any(has_unfinished_types(arg) for arg in t.args)
1534+
return False
1535+
1536+
15051537
def map_actuals_to_formals(caller_kinds: List[int],
15061538
caller_names: List[str],
15071539
callee_kinds: List[int],

mypy/test/data/check-inference.test

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1639,3 +1639,60 @@ a1 = D1() if f() else D2()
16391639
a1.foo1()
16401640
a2 = D2() if f() else D1()
16411641
a2.foo2()
1642+
1643+
[case testUnificationEmptyListLeft]
1644+
def f(): pass
1645+
a = [] if f() else [0]
1646+
a.append(0)
1647+
[builtins fixtures/list.py]
1648+
1649+
[case testUnificationEmptyListRight]
1650+
def f(): pass
1651+
a = [0] if f() else []
1652+
a.append(0)
1653+
[builtins fixtures/list.py]
1654+
1655+
[case testUnificationEmptyListLeftInContext]
1656+
from typing import List
1657+
def f(): pass
1658+
a = [] if f() else [0] # type: List[int]
1659+
a.append(0)
1660+
[builtins fixtures/list.py]
1661+
1662+
[case testUnificationEmptyListRightInContext]
1663+
# TODO Find an example that really needs the context
1664+
from typing import List
1665+
def f(): pass
1666+
a = [0] if f() else [] # type: List[int]
1667+
a.append(0)
1668+
[builtins fixtures/list.py]
1669+
1670+
[case testUnificationEmptySetLeft]
1671+
def f(): pass
1672+
a = set() if f() else {0}
1673+
a.add(0)
1674+
[builtins fixtures/set.py]
1675+
1676+
[case testUnificationEmptyDictLeft]
1677+
def f(): pass
1678+
a = {} if f() else {0: 0}
1679+
a.update({0: 0})
1680+
[builtins fixtures/dict.py]
1681+
1682+
[case testUnificationEmptyDictRight]
1683+
def f(): pass
1684+
a = {0: 0} if f() else {}
1685+
a.update({0: 0})
1686+
[builtins fixtures/dict.py]
1687+
1688+
[case testUnificationDictWithEmptyListLeft]
1689+
def f(): pass
1690+
a = {0: []} if f() else {0: [0]}
1691+
a.update({0: [0]})
1692+
[builtins fixtures/dict.py]
1693+
1694+
[case testUnificationDictWithEmptyListRight]
1695+
def f(): pass
1696+
a = {0: [0]} if f() else {0: []}
1697+
a.update({0: [0]})
1698+
[builtins fixtures/dict.py]

0 commit comments

Comments
 (0)