@@ -41,12 +41,22 @@ class Frame:
41
41
"""A Frame represents a specific point in the execution of a program.
42
42
It carries information about the current types of expressions at
43
43
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.
46
50
47
51
This information is not copied into a new Frame when it is pushed
48
52
onto the stack, so a given Frame only has information about types
49
53
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.
50
60
"""
51
61
52
62
def __init__ (self , id : int , conditional_frame : bool = False ) -> None :
@@ -66,29 +76,29 @@ def __repr__(self) -> str:
66
76
class ConditionalTypeBinder :
67
77
"""Keep track of conditional types of variables.
68
78
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
85
94
"""
86
95
87
96
# Stored assignments for situations with tuple/list lvalue and rvalue of union type.
88
97
# This maps an expression to a list of bound types for every item in the union type.
89
98
type_assignments : Assigns | None = None
90
99
91
100
def __init__ (self ) -> None :
101
+ # Each frame gets an increasing, distinct id.
92
102
self .next_id = 1
93
103
94
104
# The stack of frames currently used. These map
@@ -116,6 +126,7 @@ def __init__(self) -> None:
116
126
# Whether the last pop changed the newly top frame on exit
117
127
self .last_pop_changed = False
118
128
129
+ # These are used to track control flow in try statements and loops.
119
130
self .try_frames : set [int ] = set ()
120
131
self .break_frames : list [int ] = []
121
132
self .continue_frames : list [int ] = []
@@ -151,6 +162,10 @@ def _get(self, key: Key, index: int = -1) -> CurrentType | None:
151
162
return None
152
163
153
164
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
+ """
154
169
if not isinstance (expr , (IndexExpr , MemberExpr , NameExpr )):
155
170
return
156
171
if not literal (expr ):
@@ -314,6 +329,13 @@ def accumulate_type_assignments(self) -> Iterator[Assigns]:
314
329
self .type_assignments = old_assignments
315
330
316
331
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
+ """
317
339
# We should erase last known value in binder, because if we are using it,
318
340
# it means that the target is not final, and therefore can't hold a literal.
319
341
type = remove_instance_last_known_values (type )
@@ -488,6 +510,11 @@ def top_frame_context(self) -> Iterator[Frame]:
488
510
489
511
490
512
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
+ """
491
518
if isinstance (expr , RefExpr ):
492
519
if isinstance (expr .node , Var ):
493
520
type = expr .node .type
0 commit comments