-
-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Changes from 1 commit
d01d2f1
8966207
a99dfcb
e742453
d634539
06fe586
1f8adb8
98167cd
0dab466
6f4b29c
8e9ea44
1989243
201c744
208c16d
5f36c72
5925fd0
4dfc2c7
43e9f03
2396458
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,24 @@ | ||
from typing import (Any, Dict, List, Set, Iterator, Union) | ||
from typing import (Dict, List, Set, Iterator, Union) | ||
from contextlib import contextmanager | ||
|
||
from mypy.types import Type, AnyType, PartialType | ||
from mypy.nodes import (Node, Expression, Var, RefExpr, SymbolTableNode) | ||
from mypy.nodes import (Node as Expression, Var, RefExpr, SymbolTableNode) | ||
|
||
from mypy.subtypes import is_subtype | ||
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] | ||
|
||
class Frame(Dict[Any, Type]): | ||
pass | ||
|
||
Key = object | ||
|
||
class Key(AnyType): | ||
|
||
class Frame(Dict[object, Type]): | ||
pass | ||
|
||
|
||
|
@@ -85,7 +90,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: | ||
|
@@ -96,20 +101,24 @@ 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_if_bindable(self, expr: Expression, typ: Type) -> None: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like the change to Basically, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it adds no information only if you know exactly how the binder works. When I read this code I had no idea that some expressions can be simply ignored. I do not like long names, but shorter names feels misleading. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Of course it is the binder's business, but the reader should know that it is possible. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A docstring (or general documentation on the binder) seems like a better vehicle for that. |
||
if not isinstance(expr, BindableTypes): | ||
return | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not really sure about the need for this. |
||
if not expr.literal: | ||
return | ||
key = node.literal_hash | ||
key = expr.literal_hash | ||
if key not in self.declarations: | ||
self.declarations[key] = self.get_declaration(node) | ||
self.declarations[key] = self.get_declaration(expr) | ||
self._add_dependencies(key) | ||
self._push(key, typ) | ||
self._put(key, typ) | ||
|
||
def get(self, expr: Union[Expression, Var]) -> Type: | ||
def get(self, expr: Expression) -> Type: | ||
if not isinstance(expr, BindableTypes): | ||
return None | ||
return self._get(expr.literal_hash) | ||
|
||
def cleanse(self, expr: Expression) -> None: | ||
"""Remove all references to a Node from the binder.""" | ||
def cleanse(self, expr: BindableExpression) -> None: | ||
"""Remove all references to an Expression from the binder.""" | ||
self._cleanse_key(expr.literal_hash) | ||
|
||
def _cleanse_key(self, key: Key) -> None: | ||
|
@@ -144,7 +153,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 | ||
|
||
return changed | ||
|
@@ -165,19 +174,20 @@ def pop_frame(self, fall_through: int = 0) -> Frame: | |
|
||
return result | ||
|
||
def get_declaration(self, node: Node) -> Type: | ||
if isinstance(node, (RefExpr, SymbolTableNode)) and isinstance(node.node, Var): | ||
type = node.node.type | ||
if isinstance(type, PartialType): | ||
return None | ||
return type | ||
else: | ||
return None | ||
def get_declaration(self, expr: BindableExpression) -> Type: | ||
assert not isinstance(expr, SymbolTableNode) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This assert can't trigger given the definition of BindableExpression right? Or am I misunderstanding? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a leftover from an old check, |
||
if isinstance(expr, RefExpr) and isinstance(expr.node, Var): | ||
type = expr.node.type | ||
if not isinstance(type, PartialType): | ||
return type | ||
return None | ||
|
||
def assign_type(self, expr: Expression, | ||
type: Type, | ||
declared_type: Type, | ||
restrict_any: bool = False) -> None: | ||
def assign_type_if_bindable(self, expr: Expression, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, |
||
type: Type, | ||
declared_type: Type, | ||
restrict_any: bool = False) -> None: | ||
if not isinstance(expr, BindableTypes): | ||
return None | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This check doesn't seem necessary. |
||
if not expr.literal: | ||
return | ||
self.invalidate_dependencies(expr) | ||
|
@@ -202,16 +212,16 @@ def assign_type(self, expr: Expression, | |
and not restrict_any): | ||
pass | ||
elif isinstance(type, AnyType): | ||
self.push(expr, declared_type) | ||
self.put_if_bindable(expr, declared_type) | ||
else: | ||
self.push(expr, type) | ||
self.put_if_bindable(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. | ||
|
@@ -222,7 +232,7 @@ 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) | ||
key = expr.literal_hash | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -150,18 +150,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: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This part is related to #2222 - There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) -> Type: | ||
"""Type check a call expression.""" | ||
|
@@ -1201,7 +1196,7 @@ def check_boolean_op(self, e: OpExpr, context: Context) -> Type: | |
with self.chk.binder.frame_context(): | ||
if right_map: | ||
for var, type in right_map.items(): | ||
self.chk.binder.push(var, type) | ||
self.chk.binder.put_if_bindable(var, type) | ||
|
||
right_type = self.accept(e.right, left_type) | ||
|
||
|
@@ -1700,7 +1695,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_if_bindable(var, type) | ||
|
||
def visit_conditional_expr(self, e: ConditionalExpr) -> Type: | ||
cond_type = self.accept(e.cond) | ||
|
@@ -1739,7 +1734,7 @@ def analyze_cond_branch(self, map: Optional[Dict[Node, Type]], | |
with self.chk.binder.frame_context(): | ||
if map: | ||
for var, type in map.items(): | ||
self.chk.binder.push(var, type) | ||
self.chk.binder.put_if_bindable(var, type) | ||
return self.accept(node, context=context) | ||
|
||
def visit_backquote_expr(self, e: BackquoteExpr) -> Type: | ||
|
There was a problem hiding this comment.
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 whichExpression
s have (non-None
)literal
s. It should be fine to just useExpression
everywhere.There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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