@@ -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:
6676class 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
490512def 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
0 commit comments