8000 Update comments and docstrings related to binder (#18611) · python/mypy@fc991a0 · GitHub
[go: up one dir, main page]

Skip to content

Commit fc991a0

Browse files
authored
Update comments and docstrings related to binder (#18611)
Hopefully this makes this a little less confusing.
1 parent e9a813c commit fc991a0

File tree

3 files changed

+73
-28
lines changed

3 files changed

+73
-28
lines changed

mypy/binder.py

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,22 @@ class Frame:
4141
"""A Frame represents a specific point in the execution of a program.
4242
It carries information about the current types of expressions at
4343
that point, arising either from assignments to those expressions
44-
or the result of isinstance checks. It also records whether it is
45-
possible to reach that point at all.
44+
or the result of isinstance checks and other type narrowing
45+
operations. It also records whether it is possible to reach that
46+
point at all.
47+
48+
We add a new frame wherenever there is a new scope or control flow
49+
branching.
4650
4751
This information is not copied into a new Frame when it is pushed
4852
onto the stack, so a given Frame only has information about types
4953
that were assigned in that frame.
54+
55+
Expressions are stored in dicts using 'literal hashes' as keys (type
56+
"Key"). These are hashable values derived from expression AST nodes
57+
(only those that can be narrowed). literal_hash(expr) is used to
58+
calculate the hashes. Note that this isn't directly related to literal
59+
types -- the concept predates literal types.
5060
"""
5161

5262
def __init__(self, id: int, conditional_frame: bool = False) -> None:
@@ -66,29 +76,29 @@ def __repr__(self) -> str:
6676
class ConditionalTypeBinder:
6777
"""Keep track of conditional types of variables.
6878
69-
NB: Variables are tracked by literal expression, so it is possible
70-
to confuse the binder; for example,
71-
72-
```
73-
class A:
74-
a: Union[int, str] = None
75-
x = A()
76-
lst = [x]
77-
reveal_type(x.a) # Union[int, str]
78-
x.a = 1
79-
reveal_type(x.a) # int
80-
reveal_type(lst[0].a) # Union[int, str]
81-
lst[0].a = 'a'
82-
reveal_type(x.a) # int
83-
reveal_type(lst[0].a) # str
84-
```
79+
NB: Variables are tracked by literal hashes of expressions, so it is
80+
possible to confuse the binder when there is aliasing. Example:
81+
82+
class A:
83+
a: int | str
84+
85+
x = A()
86+
lst = [x]
87+
reveal_type(x.a) # int | str
88+
x.a = 1
89+
reveal_type(x.a) # int
90+
reveal_type(lst[0].a) # int | str
91+
lst[0].a = 'a'
92+
reveal_type(x.a) # int
93+
reveal_type(lst[0].a) # str
8594
"""
8695

8796
# Stored assignments for situations with tuple/list lvalue and rvalue of union type.
8897
# This maps an expression to a list of bound types for every item in the union type.
8998
type_assignments: Assigns | None = None
9099

91100
def __init__(self) -> None:
101+
# Each frame gets an increasing, distinct id.
92102
self.next_id = 1
93103

94104
# The stack of frames currently used. These map
@@ -116,6 +126,7 @@ def __init__(self) -> None:
116126
# Whether the last pop changed the newly top frame on exit
117127
self.last_pop_changed = False
118128

129+
# These are used to track control flow in try statements and loops.
119130
self.try_frames: set[int] = set()
120131
self.break_frames: list[int] = []
121132
self.continue_frames: list[int] = []
@@ -151,6 +162,10 @@ def _get(self, key: Key, index: int = -1) -> CurrentType | None:
151162
return None
152163

153164
def put(self, expr: Expression, typ: Type, *, from_assignment: bool = True) -> None:
165+
"""Directly set the narrowed type of expression (if it supports it).
166+
167+
This is used for isinstance() etc. Assignments should go through assign_type().
168+
"""
154169
if not isinstance(expr, (IndexExpr, MemberExpr, NameExpr)):
155170
return
156171
if not literal(expr):
@@ -314,6 +329,13 @@ def accumulate_type_assignments(self) -> Iterator[Assigns]:
314329
self.type_assignments = old_assignments
315330

316331
def assign_type(self, expr: Expression, type: Type, declared_type: Type | None) -> None:
332+
"""Narrow type of expression through an assignment.
333+
334+
Do nothing if the expression doesn't support narrowing.
335+
336+
When not narrowing though an assignment (isinstance() etc.), use put()
337+
directly. This omits some special-casing logic for assignments.
338+
"""
317339
# We should erase last known value in binder, because if we are using it,
318340
# it means that the target is not final, and therefore can't hold a literal.
319341
type = remove_instance_last_known_values(type)
@@ -488,6 +510,11 @@ def top_frame_context(self) -> Iterator[Frame]:
488510

489511

490512
def get_declaration(expr: BindableExpression) -> Type | None:
513+
"""Get the declared or inferred type of a RefExpr expression.
514+
515+
Return None if there is no type or the expression is not a RefExpr.
516+
This can return None if the type hasn't been inferred yet.
517+
"""
491518
if isinstance(expr, RefExpr):
492519
if isinstance(expr.node, Var):
493520
type = expr.node.type

mypy/literals.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,27 @@
9696
# of an index expression, or the operands of an operator expression).
9797

9898

99+
Key: _TypeAlias = tuple[Any, ...]
100+
101+
102+
def literal_hash(e: Expression) -> Key | None:
103+
"""Generate a hashable, (mostly) opaque key for expressions supported by the binder.
104+
105+
These allow using expressions as dictionary keys based on structural/value
106+
matching (instead of based on expression identity).
107+
108+
Return None if the expression type is not supported (it cannot be narrowed).
109+
110+
See the comment above for more information.
111+
112+
NOTE: This is not directly related to literal types.
113+
"""
114+
return e.accept(_hasher)
115+
116+
99117
def literal(e: Expression) -> int:
118+
"""Return the literal kind for an expression."""
119+
100120
if isinstance(e, ComparisonExpr):
101121
return min(literal(o) for o in e.operands)
102122

@@ -129,17 +149,10 @@ def literal(e: Expression) -> int:
129149
return LITERAL_NO
130150

131151

132-
Key: _TypeAlias = tuple[Any, ...]
133-
134-
135152
def subkeys(key: Key) -> Iterable[Key]:
136153
return [elt for elt in key if isinstance(elt, tuple)]
137154

138155

139-
def literal_hash(e: Expression) -> Key | None:
140-
return e.accept(_hasher)
141-
142-
143156
def extract_var_from_li C70C teral_hash(key: Key) -> Var | None:
144157
"""If key refers to a Var node, return it.
145158

mypy/nodes.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,14 @@ def set_line(
8989
REVEAL_TYPE: Final = 0
9090
REVEAL_LOCALS: Final = 1
9191

92-
LITERAL_YES: Final = 2
93-
LITERAL_TYPE: Final = 1
94-
LITERAL_NO: Final = 0
92+
# Kinds of 'literal' expressions.
93+
#
94+
# Use the function mypy.literals.literal to calculate these.
95+
#
96+
# TODO: Can we make these less confusing?
97+
LITERAL_YES: Final = 2 # Value of expression known statically
98+
LITERAL_TYPE: Final = 1 # Type of expression can be narrowed (e.g. variable reference)
99+
LITERAL_NO: Final = 0 # None of the above
95100

96101
node_kinds: Final = {LDEF: "Ldef", GDEF: "Gdef", MDEF: "Mdef", UNBOUND_IMPORTED: "UnboundImported"}
97102
inverse_node_kinds: Final = {_kind: _name for _name, _kind in node_kinds.items()}

0 commit comments

Comments
 (0)
0