Skip to content

Commit 1061937

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 9b7b383 commit 1061937

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
@@ -1406,6 +1406,7 @@ def check_for_comp(self, e: Union[GeneratorExpr, DictionaryComprehension]) -> No
14061406
def visit_conditional_expr(self, e: ConditionalExpr) -> Type:
14071407
cond_type = self.accept(e.cond)
14081408
self.check_not_void(cond_type, e)
1409+
ctx = self.chk.type_context[-1]
14091410

14101411
# Gain type information from isinstance if it is there
14111412
# but only for the current expression
@@ -1414,26 +1415,44 @@ def visit_conditional_expr(self, e: ConditionalExpr) -> Type:
14141415
self.chk.type_map,
14151416
self.chk.typing_mode_weak())
14161417

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

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

1434-
self.chk.binder.pop_frame()
1453+
res = join.join_types(if_type, else_type)
14351454

1436-
return join.join_types(if_type, else_type)
1455+
return res
14371456

14381457
def visit_backquote_expr(self, e: BackquoteExpr) -> Type:
14391458
self.accept(e.expr)
@@ -1505,6 +1524,19 @@ def not_ready_callback(self, name: str, context: Context) -> None:
15051524
self.chk.handle_cannot_determine_type(name, context)
15061525

15071526

1527+
# TODO: What's a good name for this function?
1528+
def has_unfinished_types(t: Type) -> bool:
1529+
"""Check whether t has type variables replaced with 'None'.
1530+
1531+
This can happen when `[]` is evaluated without sufficient context.
1532+
"""
1533+
if isinstance(t, NoneTyp):
1534+
return True
1535+
if isinstance(t, Instance):
1536+
return any(has_unfinished_types(arg) for arg in t.args)
1537+
return False
1538+
1539+
15081540
def map_actuals_to_formals(caller_kinds: List[int],
15091541
caller_names: List[str],
15101542
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)