Skip to content

Explicit types for bindables ("Tighten Types" continued) #2238

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 19 commits into from
Mar 27, 2017
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
58 changes: 35 additions & 23 deletions mypy/binder.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
from mypy.join import join_simple
from mypy.sametypes import is_same_type

from mypy.nodes import IndexExpr, MemberExpr, NameExpr


BindableTypes = (IndexExpr, MemberExpr, NameExpr)
BindableExpression = Union[IndexExpr, MemberExpr, NameExpr]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the point of introducing this BindableExpression. It seems to duplicate information with which Expressions have (non-None) literals. It should be fine to just use Expression everywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lists of literals have non-None literals.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's the branch with literal() and literal_hash() functions:
https://github.com/elazarg/mypy/blob/literal_hash/mypy/nodes.py#L2351-L2421


class Frame(Dict[Key, Type]):
"""A Frame represents a specific point in the execution of a program.
Expand Down Expand Up @@ -92,7 +98,7 @@ def push_frame(self) -> Frame:
self.options_on_return.append([])
return f

def _push(self, key: Key, type: Type, index: int=-1) -> None:
def _put(self, key: Key, type: Type, index: int=-1) -> None:
self.frames[index][key] = type

def _get(self, key: Key, index: int=-1) -> Type:
Expand All @@ -103,19 +109,23 @@ def _get(self, key: Key, index: int=-1) -> Type:
return self.frames[i][key]
return None

def push(self, node: Node, typ: Type) -> None:
if not node.literal:
def put(self, expr: Expression, typ: Type) -> None:
# TODO: replace with isinstance(expr, BindableTypes)
if not isinstance(expr, (IndexExpr, MemberExpr, NameExpr)):
return
key = node.literal_hash
if not expr.literal:
return
key = expr.literal_hash
if key not in self.declarations:
self.declarations[key] = self.get_declaration(node)
assert isinstance(expr, BindableTypes)
self.declarations[key] = get_declaration(expr)
self._add_dependencies(key)
self._push(key, typ)
self._put(key, typ)

def unreachable(self) -> None:
self.frames[-1].unreachable = True

def get(self, expr: Union[Expression, Var]) -> Type:
def get(self, expr: Expression) -> Type:
return self._get(expr.literal_hash)

def is_unreachable(self) -> bool:
Expand Down Expand Up @@ -163,7 +173,7 @@ def update_from_options(self, frames: List[Frame]) -> bool:
for other in resulting_values[1:]:
type = join_simple(self.declarations[key], type, other)
if not is_same_type(type, current_value):
self._push(key, type)
self._put(key, type)
changed = True

self.frames[-1].unreachable = not frames
Expand All @@ -189,19 +199,13 @@ def pop_frame(self, can_skip: bool, fall_through: int) -> Frame:

return result

def get_declaration(self, expr: Node) -> Type:
if isinstance(expr, RefExpr) and isinstance(expr.node, Var):
type = expr.node.type
if isinstance(type, PartialType):
return None
return type
else:
return None

def assign_type(self, expr: Expression,
type: Type,
declared_type: Type,
restrict_any: bool = False) -> None:
# TODO: replace with isinstance(expr, BindableTypes)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you file an issue for that regression? What does reveal_type(BindableTypes) show at this point?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filed: #3068. It will show Expression.

if not isinstance(expr, (IndexExpr, MemberExpr, NameExpr)):
return None
if not expr.literal:
return
self.invalidate_dependencies(expr)
Expand All @@ -226,16 +230,16 @@ def assign_type(self, expr: Expression,
and not restrict_any):
pass
elif isinstance(type, AnyType):
self.push(expr, declared_type)
self.put(expr, declared_type)
else:
self.push(expr, type)
self.put(expr, type)

for i in self.try_frames:
# XXX This should probably not copy the entire frame, but
# just copy this variable into a single stored frame.
self.allow_jump(i)

def invalidate_dependencies(self, expr: Expression) -> None:
def invalidate_dependencies(self, expr: BindableExpression) -> None:
"""Invalidate knowledge of types that include expr, but not expr itself.

For example, when expr is foo.bar, invalidate foo.bar.baz.
Expand All @@ -246,11 +250,11 @@ def invalidate_dependencies(self, expr: Expression) -> None:
for dep in self.dependencies.get(expr.literal_hash, set()):
self._cleanse_key(dep)

def most_recent_enclosing_type(self, expr: Expression, type: Type) -> Type:
def most_recent_enclosing_type(self, expr: BindableExpression, type: Type) -> Type:
if isinstance(type, AnyType):
return self.get_declaration(expr)
return get_declaration(expr)
key = expr.literal_hash
enclosers = ([self.get_declaration(expr)] +
enclosers = ([get_declaration(expr)] +
[f[key] for f in self.frames
if key in f and is_subtype(type, f[key])])
return enclosers[-1]
Expand Down Expand Up @@ -334,3 +338,11 @@ def top_frame_context(self) -> Iterator[Frame]:
assert len(self.frames) == 1
yield self.push_frame()
self.pop_frame(True, 0)


def get_declaration(expr: BindableExpression) -> Type:
if isinstance(expr, RefExpr) and isinstance(expr.node, Var):
type = expr.node.type
if not isinstance(type, PartialType):
return type
return None
16 changes: 5 additions & 11 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
from mypy.visitor import StatementVisitor
from mypy.join import join_types
from mypy.treetransform import TransformVisitor
from mypy.binder import ConditionalTypeBinder, get_declaration
from mypy.meet import is_overlapping_types
from mypy.binder import ConditionalTypeBinder
from mypy.options import Options

from mypy import experiments
Expand Down Expand Up @@ -1221,10 +1221,7 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type
" to a variable of type '{}'".format(lvalue_type), rvalue)
return
if rvalue_type and infer_lvalue_type:
self.binder.assign_type(lvalue,
rvalue_type,
lvalue_type,
False)
self.binder.assign_type(lvalue, rvalue_type, lvalue_type, False)
elif index_lvalue:
self.check_indexed_assignment(index_lvalue, rvalue, lvalue)

Expand Down Expand Up @@ -1927,7 +1924,6 @@ def visit_assert_stmt(self, s: AssertStmt) -> None:

# If this is asserting some isinstance check, bind that type in the following code
true_map, _ = self.find_isinstance_check(s.expr)

self.push_type_map(true_map)

def visit_raise_stmt(self, s: RaiseStmt) -> None:
Expand Down Expand Up @@ -2171,10 +2167,8 @@ def visit_del_stmt(self, s: DelStmt) -> None:
s.expr.accept(self.expr_checker)
for elt in flatten(s.expr):
if isinstance(elt, NameExpr):
self.binder.assign_type(elt,
DeletedType(source=elt.name),
self.binder.get_declaration(elt),
False)
self.binder.assign_type(elt, DeletedType(source=elt.name),
get_declaration(elt), False)

def visit_decorator(self, e: Decorator) -> None:
for d in e.decorators:
Expand Down Expand Up @@ -2463,7 +2457,7 @@ def push_type_map(self, type_map: Optional[Dict[Expression, Type]]) -> None:
self.binder.unreachable()
else:
for expr, type in type_map.items():
self.binder.push(expr, type)
self.binder.put(expr, type)

# Data structure returned by find_isinstance_check representing
# information learned from the truth or falsehood of a condition. The
Expand Down
13 changes: 4 additions & 9 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,18 +160,13 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type:
return result

def analyze_var_ref(self, var: Var, context: Context) -> Type:
if not var.type:
if var.type:
return var.type
else:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just double-checking that what you changed here isn't an accidental merge conflict? You've done more here than just revers the sense of the check and swap the then/else clauses.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part is related to #2222 - binder.get() does not accept Var; val is always None.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, I would like to extract literal-handling to a couple of functions, and avoid putting them in the Expression classes. I already have the branch ready. Will you consider it? It does not solve anything, but I think it's nice to have this information in a single place, instead of scattered around.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be worth calling out this "change" explicitly in the commit message, since it does not obviously look like a mere refactoring.

if not var.is_ready and self.chk.in_checked_function():
self.chk.handle_cannot_determine_type(var.name(), context)
# Implicit 'Any' type.
return AnyType()
else:
# Look up local type of variable with type (inferred or explicit).
val = self.chk.binder.get(var)
if val is None:
return var.type
else:
return val

def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type:
"""Type check a call expression."""
Expand Down Expand Up @@ -1954,7 +1949,7 @@ def check_for_comp(self, e: Union[GeneratorExpr, DictionaryComprehension]) -> No

if true_map:
for var, type in true_map.items():
self.chk.binder.push(var, type)
self.chk.binder.put(var, type)

def visit_conditional_expr(self, e: ConditionalExpr) -> Type:
cond_type = self.accept(e.cond)
Expand Down
8 changes: 3 additions & 5 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,6 @@ class Node(Context):
line = -1
column = -1

# TODO: Move to Expression
# See [Note Literals and literal_hash] below
literal = LITERAL_NO
literal_hash = None # type: Key

def __str__(self) -> str:
ans = self.accept(mypy.strconv.StrConv())
if ans is None:
Expand Down Expand Up @@ -161,6 +156,9 @@ def accept(self, visitor: StatementVisitor[T]) -> T:

class Expression(Node):
"""An expression node."""
literal = LITERAL_NO
literal_hash = None # type: Key

def accept(self, visitor: ExpressionVisitor[T]) -> T:
raise RuntimeError('Not implemented')

Expand Down