8000 Move ConditionalTypeBinder into its own file · python/mypy@a8c1a9a · GitHub
[go: up one dir, main page]

Skip to content

Commit a8c1a9a

Browse files
committed
Move ConditionalTypeBinder into its own file
1 parent a9f8ced commit a8c1a9a

File tree

2 files changed

+319
-323
lines changed

2 files changed

+319
-323
lines changed

mypy/binder.py

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
from typing import (Any, Dict, List, cast)
2+
3+
from mypy.types import Type, AnyType, PartialType
4+
from mypy.nodes import (Node, Var)
5+
6+
from mypy.subtypes import is_subtype
7+
from mypy.join import join_simple, join_types
8+
from mypy.sametypes import is_same_type
9+
10+
11+
class Frame(Dict[Any, Type]):
12+
"""Frame for the ConditionalTypeBinder.
13+
14+
Frames actually in the binder must have
15+
binder.frames[i].parent == binder.frames[i-1].
16+
17+
options_on_return represents a list of possible frames that we
18+
will incorporate next time we are the top frame (e.g. frames from
19+
the end of each if/elif/else block).
20+
21+
After a frame is complete, 'changed' represents whether anything
22+
changed during the procedure, so a loop should be re-run.
23+
'breaking' represents whether frame was necessarily breaking out.
24+
"""
25+
26+
changed = False
27+
broken = False
28+
29+
def __init__(self, parent: 'Frame' = None) -> None:
30+
super().__init__()
31+
self.parent = parent
32+
self.options_on_return = [] # type: List[Frame]
33+
34+
def add_exit_option(self, frame: 'Frame', follow_parents: bool = False) -> None:
35+
"""When this frame __exit__'s, its parent may have state `frame`.
36+
37+
So self.add_exit_option(self) means that you can fall through
38+
to the outer frame, and self.add_exit_option(self.parent)
39+
means that the parent is allowed to continue unchanged by what
40+
happened in this frame.
41+
42+
If follow_parents is True, then frame must be a descendent of
43+
self, and we collapse the frame before adding it as an option.
44+
"""
45+
if follow_parents:
46+
frame_list = []
47+
while frame is not self.parent:
48+
frame_list.append(frame)
49+
frame = frame.parent
50+
frame = Frame()
51+
for f in frame_list[::-1]:
52+
frame.update(f)
53+
self.parent.options_on_return.append(frame)
54+
55+
56+
class Key(AnyType):
57+
pass
58+
59+
60+
class ConditionalTypeBinder:
61+
"""Keep track of conditional types of variables."""
62+
63+
def __init__(self) -> None:
64+
self.frames = [] # type: List[Frame]
65+
# The first frame is special: it's the declared types of variables.
66+
self.frames.append(Frame())
67+
# We want a second frame that we can update.
68+
self.frames.append(Frame())
69+
# Set of other keys to invalidate if a key is changed.
70+
self.dependencies = {} # type: Dict[Key, Set[Key]]
71+
# Set of keys with dependencies added already.
72+
self._added_dependencies = set() # type: Set[Key]
73+
74+
# Set to True on return/break/raise, False on blocks that can block any of them
75+
self.breaking_out = False
76+
77+
self.try_frames = set() # type: Set[int]
78+
self.loop_frames = [] # type: List[int]
79+
80+
def _add_dependencies(self, key: Key, value: Key = None) -> None:
81+
if value is None:
82+
value = key
83+
if value in self._added_dependencies:
84+
return
85+
self._added_dependencies.add(value)
86+
if isinstance(key, tuple):
87+
key = cast(Any, key) # XXX sad
88+
if key != value:
89+
self.dependencies[key] = set()
90+
self.dependencies.setdefault(key, set()).add(value)
91+
for elt in cast(Any, key):
92+
self._add_dependencies(elt, value)
93+
94+
def push_frame(self, fall_through: bool = False) -> Frame:
95+
"""Push a new frame into the binder.
96+
97+
If fall_through, then allow types to escape from the inner
98+
frame to the resulting frame. That is, the state of types at
99+
the end of the last frame are allowed to fall through into the
100+
enclosing frame.
101+
"""
102+
f = Frame(self.frames[-1])
103+
self.frames.append(f)
104+
if fall_through:
105+
f.add_exit_option(f)
106+
return f
107+
108+
def _push(self, key: Key, type: Type, index: int=-1) -> None:
109+
self._add_dependencies(key)
110+
self.frames[index][key] = type
111+
112+
def _get(self, key: Key, index: int=-1) -> Type:
113+
if index < 0:
114+
index += len(self.frames)
115+
for i in range(index, -1, -1):
116+
if key in self.frames[i]:
117+
return self.frames[i][key]
118+
return None
119+
120+
def push(self, expr: Node, typ: Type) -> None:
121+
if not expr.literal:
122+
return
123+
key = expr.literal_hash
124+
self.frames[0][key] = self.get_declaration(expr)
125+
self._push(key, typ)
126+
127+
def get(self, expr: Node) -> Type:
128+
return self._get(expr.literal_hash)
129+
130+
def cleanse(self, expr: Node) -> None:
131+
"""Remove all references to a Node from the binder."""
132+
key = expr.literal_hash
133+
for frame in self.frames:
134+
if key in frame:
135+
del frame[key]
136+
137+
def update_from_options(self, frames: List[Frame]) -> bool:
138+
"""Update the frame to reflect that each key will be updated
139+
as in one of the frames. Return whether any item changes.
140+
141+
If a key is declared as AnyType, only update it if all the
142+
options are the same.
143+
"""
144+
145+
changed = False
146+
keys = set(key for f in frames for key in f)
147+
148+
for key in keys:
149+
current_value = self._get(key)
150+
resulting_values = [f.get(key, current_value) for f in frames]
151+
if any(x is None for x in resulting_values):
152+
continue
153+
154+
if isinstance(self.frames[0].get(key), AnyType):
155+
type = resulting_values[0]
156+
if not all(is_same_type(type, t) for t in resulting_values[1:]):
157+
type = AnyType()
158+
else:
159+
type = resulting_values[0]
160+
for other in resulting_values[1:]:
161+
type = join_simple(self.frames[0][key], type, other)
162+
if not is_same_type(type, current_value):
163+
self._push(key, type)
164+
changed = True
165+
166+
return changed
167+
168+
def update_expand(self, frame: Frame, index: int = -1) -> bool:
169+
"""Update frame to include another one, if that other one is larger than the current value.
170+
171+
Return whether anything changed."""
172+
result = False
173+
174+
for key in frame:
175+
old_type = self._get(key, index)
176+
if old_type is None:
177+
continue
178+
replacement = join_simple(self.frames[0][key], old_type, frame[key])
179+
180+
if not is_same_type(replacement, old_type):
181+
self._push(key, replacement, index)
182+
result = True
183+
return result
184+
185+
def pop_frame(self) -> Frame:
186+
"""Pop a frame and return it.
187+
188+
frame.changed represents whether the newly innermost frame was
189+
modified since it was last on top.
190+
191+
frame.broken represents whether we always leave this frame
192+
through a construct other than falling off the end.
193+
"""
194+
result = self.frames.pop()
195+
196+
options = self.frames[-1].options_on_return
197+
self.frames[-1].options_on_return = []
198+
199+
result.changed = self.update_from_options(options)
200+
result.broken = self.breaking_out
201+
202+
return result
203+
204+
def get_declaration(self, expr: Any) -> Type:
205+
if hasattr(expr, 'node') and isinstance(expr.node, Var):
206+
type = expr.node.type
207+
if isinstance(type, PartialType):
208+
return None
209+
return type
210+
else:
211+
return self.frames[0].get(expr.literal_hash)
212+
213+
def assign_type(self, expr: Node,
214+
type: Type,
215+
declared_type: Type,
216+
restrict_any: bool = False) -> None:
217+
if not expr.literal:
218+
return
219+
self.invalidate_dependencies(expr)
220+
221+
if declared_type is None:
222+
# Not sure why this happens. It seems to mainly happen in
223+
# member initialization.
224+
return
225+
if not is_subtype(type, declared_type):
226+
# Pretty sure this is only happens when there's a type error.
227+
228+
# Ideally this function wouldn't be called if the
229+
# expression has a type error, though -- do other kinds of
230+
# errors cause this function to get called at invalid
231+
# times?
232+
return
233+
234+
# If x is Any and y is int, after x = y we do not infer that x is int.
235+
# This could be changed.
236+
# Eric: I'm changing it in weak typing mode, since Any is so common.
237+
238+
if (isinstance(self.most_recent_enclosing_type(expr, type), AnyType)
239+
and not restrict_any):
240+
pass
241+
elif isinstance(type, AnyType):
242+
self.push(expr, declared_type)
243+
else:
244+
self.push(expr, type)
245+
246+
for i in self.try_frames:
247+
# XXX This should probably not copy the entire frame, but
248+
# just copy this variable into a single stored frame.
249+
self.allow_jump(i)
250+
251+
def invalidate_dependencies(self, expr: Node) -> None:
252+
"""Invalidate knowledge of types that include expr, but not expr itself.
253+
254+
For example, when expr is foo.bar, invalidate foo.bar.baz and
255+
foo.bar[0].
256+
257+
It is overly conservative: it invalidates globally, including
258+
in code paths unreachable from here.
259+
"""
260+
for dep in self.dependencies.get(expr.literal_hash, set()):
261+
for f in self.frames:
262+
if dep in f:
263+
del f[dep]
264+
265+
def most_recent_enclosing_type(self, expr: Node, type: Type) -> Type:
266+
if isinstance(type, AnyType):
267+
return self.get_declaration(expr)
268+
key = expr.literal_hash
269+
enclosers = ([self.get_declaration(expr)] +
270+
[f[key] for f in self.frames
271+
if key in f and is_subtype(type, f[key])])
272+
return enclosers[-1]
273+
274+
def allow_jump(self, index: int) -> None:
275+
self.frames[index + 1].add_exit_option(self.frames[-1], True)
276+
277+
def push_loop_frame(self) -> None:
278+
self.loop_frames.append(len(self.frames) - 1)
279+
280+
def pop_loop_frame(self) -> None:
281+
self.loop_frames.pop()
282+
283+
def frame_context(self, fall_through: bool = False,
284+
clear_breaking: bool = False) -> 'FrameContextManager':
285+
"""Return a context manager that pushes/pops frames on enter/exit.
286+
287+
If fall_through, then allow types to escape from the inner
288+
frame to the resulting frame. That is, the state of types at
289+
the end of the last frame are allowed to fall through into the
290+
enclosing frame.
291+
292+
If clear_breaking, then on __exit__ the manager will clear the
293+
breaking_out flag, and if it was not set, will allow the frame
294+
to escape to its grandparent.
295+
"""
296+
return FrameContextManager(self, fall_through, clear_breaking)
297+
298+
299+
class FrameContextManager:
300+
"""This is a context manager returned by binder.frame_context().
301+
302+
Pushes a frame on __enter__, pops it on __exit__.
303+
"""
304+
def __init__(self, binder: ConditionalTypeBinder,
305+
fall_through: bool, clear_breaking: bool) -> None:
306+
self.binder = binder
307+
self.fall_through = fall_through
308+
self.clear_breaking = clear_breaking
309+
310+
def __enter__(self) -> Frame:
311+
return self.binder.push_frame(self.fall_through)
312+
313+
def __exit__(self, *args: Any) -> None:
314+
f = self.binder.pop_frame()
315+
if self.clear_breaking:
316+
self.binder.breaking_out = False
317+
if not f.broken:
318+
f.parent.add_exit_option(f, follow_parents=True)

0 commit comments

Comments
 (0)
0