Skip to content

Empty list unification #1403

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions mypy/checker.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Mypy type checker."""

import itertools
import contextlib

from typing import (
Any, Dict, Set, List, cast, Tuple, TypeVar, Union, Optional, NamedTuple
Expand Down Expand Up @@ -295,6 +296,12 @@ def push_loop_frame(self):
def pop_loop_frame(self):
self.loop_frames.pop()

def __enter__(self) -> None:
self.push_frame()

def __exit__(self, *args: Any) -> None:
self.pop_frame()


def meet_frames(*frames: Frame) -> Frame:
answer = Frame()
Expand Down
43 changes: 27 additions & 16 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Expression type checker. This file is conceptually part of TypeChecker."""

from typing import cast, List, Tuple, Callable, Union, Optional
from typing import cast, Dict, List, Tuple, Callable, Union, Optional

from mypy.types import (
Type, AnyType, CallableType, Overloaded, NoneTyp, Void, TypeVarDef,
Expand All @@ -27,7 +27,7 @@
from mypy import messages
from mypy.infer import infer_type_arguments, infer_function_type_arguments
from mypy import join
from mypy.subtypes import is_subtype
from mypy.subtypes import is_subtype, is_equivalent
from mypy import applytype
from mypy import erasetype
from mypy.checkmember import analyze_member_access, type_object_type
Expand Down Expand Up @@ -1406,6 +1406,7 @@ def check_for_comp(self, e: Union[GeneratorExpr, DictionaryComprehension]) -> No
def visit_conditional_expr(self, e: ConditionalExpr) -> Type:
cond_type = self.accept(e.cond)
self.check_not_void(cond_type, e)
ctx = self.chk.type_context[-1]

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

self.chk.binder.push_frame()

if if_map:
for var, type in if_map.items():
self.chk.binder.push(var, type)
if_type = self.analyze_cond_branch(if_map, e.if_expr, context=ctx)

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

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

if else_map:
for var, type in else_map.items():
self.chk.binder.push(var, type)
else:
# Analyze the right branch in the context of the left
# branch's type.
else_type = self.analyze_cond_branch(else_map, e.else_expr, context=if_type)

else_type = self.accept(e.else_expr, context=if_type)
res = join.join_types(if_type, else_type)

self.chk.binder.pop_frame()
return res

return join.join_types(if_type, else_type)
def analyze_cond_branch(self, map: Optional[Dict[Node, Type]],
node: Node, context: Optional[Type]) -> Type:
with self.chk.binder:
if map:
for var, type in map.items():
self.chk.binder.push(var, type)
return self.accept(node, context=context)

def visit_backquote_expr(self, e: BackquoteExpr) -> Type:
self.accept(e.expr)
Expand Down
57 changes: 57 additions & 0 deletions mypy/test/data/check-inference.test
Original file line number Diff line number Diff line change
Expand Up @@ -1639,3 +1639,60 @@ a1 = D1() if f() else D2()
a1.foo1()
a2 = D2() if f() else D1()
a2.foo2()

[case testUnificationEmptyListLeft]
def f(): pass
a = [] if f() else [0]
a() # E: List[int] not callable
[builtins fixtures/list.py]

[case testUnificationEmptyListRight]
def f(): pass
a = [0] if f() else []
a() # E: List[int] not callable
[builtins fixtures/list.py]

[case testUnificationEmptyListLeftInContext]
from typing import List
def f(): pass
a = [] if f() else [0] # type: List[int]
a() # E: List[int] not callable
[builtins fixtures/list.py]

[case testUnificationEmptyListRightInContext]
# TODO Find an example that really needs the context
from typing import List
def f(): pass
a = [0] if f() else [] # type: List[int]
a() # E: List[int] not callable
[builtins fixtures/list.py]

[case testUnificationEmptySetLeft]
def f(): pass
a = set() if f() else {0}
a() # E: Set[int] not callable
[builtins fixtures/set.py]

[case testUnificationEmptyDictLeft]
def f(): pass
a = {} if f() else {0: 0}
a() # E: Dict[int, int] not callable
[builtins fixtures/dict.py]

[case testUnificationEmptyDictRight]
def f(): pass
a = {0: 0} if f() else {}
a() # E: Dict[int, int] not callable
[builtins fixtures/dict.py]

[case testUnificationDictWithEmptyListLeft]
def f(): pass
a = {0: []} if f() else {0: [0]}
a() # E: Dict[int, List[int]] not callable
[builtins fixtures/dict.py]

[case testUnificationDictWithEmptyListRight]
def f(): pass
a = {0: [0]} if f() else {0: []}
a() # E: Dict[int, List[int]] not callable
[builtins fixtures/dict.py]