Skip to content

Commit 110842c

Browse files
committed
Merge pull request #1403 from python/empty-list-unification
Empty list unification
2 parents faeac0a + e20d469 commit 110842c

File tree

3 files changed

+91
-16
lines changed

3 files changed

+91
-16
lines changed

mypy/checker.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Mypy type checker."""
22

33
import itertools
4+
import contextlib
45

56
from typing import (
67
Any, Dict, Set, List, cast, Tuple, TypeVar, Union, Optional, NamedTuple
@@ -295,6 +296,12 @@ def push_loop_frame(self):
295296
def pop_loop_frame(self):
296297
self.loop_frames.pop()
297298

299+
def __enter__(self) -> None:
300+
self.push_frame()
301+
302+
def __exit__(self, *args: Any) -> None:
303+
self.pop_frame()
304+
298305

299306
def meet_frames(*frames: Frame) -> Frame:
300307
answer = Frame()

mypy/checkexpr.py

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Expression type checker. This file is conceptually part of TypeChecker."""
22

3-
from typing import cast, List, Tuple, Callable, Union, Optional
3+
from typing import cast, Dict, List, Tuple, Callable, Union, Optional
44

55
from mypy.types import (
66
Type, AnyType, CallableType, Overloaded, NoneTyp, Void, TypeVarDef,
@@ -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,36 @@ 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)
1418+
if_type = self.analyze_cond_branch(if_map, e.if_expr, context=ctx)
14221419

1423-
if_type = self.accept(e.if_expr)
1420+
if not mypy.checker.is_valid_inferred_type(if_type):
1421+
# Analyze the right branch disregarding the left branch.
1422+
else_type = self.analyze_cond_branch(else_map, e.else_expr, context=ctx)
14241423

1425-
self.chk.binder.pop_frame()
1426-
self.chk.binder.push_frame()
1424+
# If it would make a difference, re-analyze the left
1425+
# branch using the right branch's type as context.
1426+
if ctx is None or not is_equivalent(else_type, ctx):
1427+
# TODO: If it's possible that the previous analysis of
1428+
# the left branch produced errors that are avoided
1429+
# using this context, suppress those errors.
1430+
if_type = self.analyze_cond_branch(if_map, e.if_expr, context=else_type)
14271431

1428-
if else_map:
1429-
for var, type in else_map.items():
1430-
self.chk.binder.push(var, type)
1432+
else:
1433+
# Analyze the right branch in the context of the left
1434+
# branch's type.
1435+
else_type = self.analyze_cond_branch(else_map, e.else_expr, context=if_type)
14311436

1432-
else_type = self.accept(e.else_expr, context=if_type)
1437+
res = join.join_types(if_type, else_type)
14331438

1434-
self.chk.binder.pop_frame()
1439+
return res
14351440

1436-
return join.join_types(if_type, else_type)
1441+
def analyze_cond_branch(self, map: Optional[Dict[Node, Type]],
1442+
node: Node, context: Optional[Type]) -> Type:
1443+
with self.chk.binder:
1444+
if map:
1445+
for var, type in map.items():
1446+
self.chk.binder.push(var, type)
1447+
return self.accept(node, context=context)
14371448

14381449
def visit_backquote_expr(self, e: BackquoteExpr) -> Type:
14391450
self.accept(e.expr)

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() # E: List[int] not callable
1647+
[builtins fixtures/list.py]
1648+
1649+
[case testUnificationEmptyListRight]
1650+
def f(): pass
1651+
a = [0] if f() else []
1652+
a() # E: List[int] not callable
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() # E: List[int] not callable
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() # E: List[int] not callable
1668+
[builtins fixtures/list.py]
1669+
1670+
[case testUnificationEmptySetLeft]
1671+
def f(): pass
1672+
a = set() if f() else {0}
1673+
a() # E: Set[int] not callable
1674+
[builtins fixtures/set.py]
1675+
1676+
[case testUnificationEmptyDictLeft]
1677+
def f(): pass
1678+
a = {} if f() else {0: 0}
1679+
a() # E: Dict[int, int] not callable
1680+
[builtins fixtures/dict.py]
1681+
1682+
[case testUnificationEmptyDictRight]
1683+
def f(): pass
1684+
a = {0: 0} if f() else {}
1685+
a() # E: Dict[int, int] not callable
1686+
[builtins fixtures/dict.py]
1687+
1688+
[case testUnificationDictWithEmptyListLeft]
1689+
def f(): pass
1690+
a = {0: []} if f() else {0: [0]}
1691+
a() # E: Dict[int, List[int]] not callable
1692+
[builtins fixtures/dict.py]
1693+
1694+
[case testUnificationDictWithEmptyListRight]
1695+
def f(): pass
1696+
a = {0: [0]} if f() else {0: []}
1697+
a() # E: Dict[int, List[int]] not callable
1698+
[builtins fixtures/dict.py]

0 commit comments

Comments
 (0)