Skip to content

Commit 12b3982

Browse files
authored
Store MROs in cache files instead of recomputing them (#4921)
This is a prerequisite for loading .data.json files on demand (#4910), since if MROs are computed on-demand when a tree is loaded, it is impossible to detect changes in the MRO caused by a change in some other file that triggered an on-demand load.
1 parent 66bf773 commit 12b3982

File tree

3 files changed

+42
-40
lines changed

3 files changed

+42
-40
lines changed

mypy/build.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
from mypy.util import DecodeError
4848
from mypy.report import Reports
4949
from mypy import moduleinfo
50-
from mypy.fixup import fixup_module_pass_one, fixup_module_pass_two
50+
from mypy.fixup import fixup_module
5151
from mypy.nodes import Expression
5252
from mypy.options import Options
5353
from mypy.parse import parse
@@ -1719,13 +1719,9 @@ def fix_cross_refs(self) -> None:
17191719
assert self.tree is not None, "Internal error: method must be called on parsed file only"
17201720
# We need to set quick_and_dirty when doing a fine grained
17211721
# cache load because we need to gracefully handle missing modules.
1722-
fixup_module_pass_one(self.tree, self.manager.modules,
1723-
self.manager.options.quick_and_dirty or
1724-
self.manager.use_fine_grained_cache())
1725-
1726-
def calculate_mros(self) -> None:
1727-
assert self.tree is not None, "Internal error: method must be called on parsed file only"
1728-
fixup_module_pass_two(self.tree, self.manager.modules)
1722+
fixup_module(self.tree, self.manager.modules,
1723+
self.manager.options.quick_and_dirty or
1724+
self.manager.use_fine_grained_cache())
17291725

17301726
def patch_dependency_parents(self) -> None:
17311727
"""
@@ -2625,8 +2621,6 @@ def process_fresh_scc(graph: Graph, scc: List[str], manager: BuildManager) -> No
26252621
graph[id].load_tree()
26262622
for id in scc:
26272623
graph[id].fix_cross_refs()
2628-
for id in scc:
2629-
graph[id].calculate_mros()
26302624
for id in scc:
26312625
graph[id].patch_dependency_parents()
26322626

@@ -2660,8 +2654,6 @@ def process_stale_scc(graph: Graph, scc: List[str], manager: BuildManager) -> No
26602654
graph[id].semantic_analysis()
26612655
for id in stale:
26622656
graph[id].semantic_analysis_pass_three()
2663-
for id in fresh:
2664-
graph[id].calculate_mros()
26652657
for id in stale:
26662658
graph[id].semantic_analysis_apply_patches()
26672659
for id in stale:

mypy/fixup.py

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,12 @@
1919
# N.B: we do a quick_and_dirty fixup in both quick_and_dirty mode and
2020
# when fixing up a fine-grained incremental cache load (since there may
2121
# be cross-refs into deleted modules)
22-
def fixup_module_pass_one(tree: MypyFile, modules: Dict[str, MypyFile],
23-
quick_and_dirty: bool) -> None:
22+
def fixup_module(tree: MypyFile, modules: Dict[str, MypyFile],
23+
quick_and_dirty: bool) -> None:
2424
node_fixer = NodeFixer(modules, quick_and_dirty)
2525
node_fixer.visit_symbol_table(tree.names)
2626

2727

28-
def fixup_module_pass_two(tree: MypyFile, modules: Dict[str, MypyFile]) -> None:
29-
compute_all_mros(tree.names, modules)
30-
31-
32-
def compute_all_mros(symtab: SymbolTable, modules: Dict[str, MypyFile]) -> None:
33-
for key, value in symtab.items():
34-
if value.kind in (LDEF, MDEF, GDEF) and isinstance(value.node, TypeInfo):
35-
info = value.node
36-
info.calculate_mro()
37-
assert info.mro, "No MRO calculated for %s" % (info.fullname(),)
38-
compute_all_mros(info.names, modules)
39-
40-
4128
# TODO: Fix up .info when deserializing, i.e. much earlier.
4229
class NodeFixer(NodeVisitor[None]):
4330
current_info = None # type: Optional[TypeInfo]
@@ -69,6 +56,10 @@ def visit_type_info(self, info: TypeInfo) -> None:
6956
info.declared_metaclass.accept(self.type_fixer)
7057
if info.metaclass_type:
7158
info.metaclass_type.accept(self.type_fixer)
59+
if info._mro_refs:
60+
info.mro = [lookup_qualified_typeinfo(self.modules, name, self.quick_and_dirty)
61+
for name in info._mro_refs]
62+
info._mro_refs = None
7263
finally:
7364
self.current_info = save_info
7465

@@ -162,18 +153,12 @@ def visit_instance(self, inst: Instance) -> None:
162153
if type_ref is None:
163154
return # We've already been here.
164155
del inst.type_ref
165-
node = lookup_qualified(self.modules, type_ref, self.quick_and_dirty)
166-
if isinstance(node, TypeInfo):
167-
inst.type = node
168-
# TODO: Is this needed or redundant?
169-
# Also fix up the bases, just in case.
170-
for base in inst.type.bases:
171-
if base.type is NOT_READY:
172-
base.accept(self)
173-
else:
174-
# Looks like a missing TypeInfo in quick mode, put something there
175-
assert self.quick_and_dirty, "Should never get here in normal mode"
176-
inst.type = stale_info(self.modules)
156+
inst.type = lookup_qualified_typeinfo(self.modules, type_ref, self.quick_and_dirty)
157+
# TODO: Is this needed or redundant?
158+
# Also fix up the bases, just in case.
159+
for base in inst.type.bases:
160+
if base.type is NOT_READY:
161+
base.accept(self)
177162
for a in inst.args:
178163
a.accept(self)
179164

@@ -251,6 +236,17 @@ def visit_type_type(self, t: TypeType) -> None:
251236
t.item.accept(self)
252237

253238

239+
def lookup_qualified_typeinfo(modules: Dict[str, MypyFile], name: str,
240+
quick_and_dirty: bool) -> TypeInfo:
241+
node = lookup_qualified(modules, name, quick_and_dirty)
242+
if isinstance(node, TypeInfo):
243+
return node
244+
else:
245+
# Looks like a missing TypeInfo in quick mode, put something there
246+
assert quick_and_dirty, "Should never get here in normal mode"
247+
return stale_info(modules)
248+
249+
254250
def lookup_qualified(modules: Dict[str, MypyFile], name: str,
255251
quick_and_dirty: bool) -> Optional[SymbolNode]:
256252
stnode = lookup_qualified_stnode(modules, name, quick_and_dirty)

mypy/nodes.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1935,6 +1935,9 @@ class is generic then it will be a type constructor of higher kind.
19351935
# Method Resolution Order: the order of looking up attributes. The first
19361936
# value always to refers to this class.
19371937
mro = None # type: List[TypeInfo]
1938+
# Used to stash the names of the mro classes temporarily between
1939+
# deserialization and fixup. See deserialize() for why.
1940+
_mro_refs = None # type: Optional[List[str]]
19381941

19391942
declared_metaclass = None # type: Optional[mypy.types.Instance]
19401943
metaclass_type = None # type: Optional[mypy.types.Instance]
@@ -2251,6 +2254,7 @@ def serialize(self) -> JsonDict:
22512254
'protocol_members': self.protocol_members,
22522255
'type_vars': self.type_vars,
22532256
'bases': [b.serialize() for b in self.bases],
2257+
'mro': [c.fullname() for c in self.mro],
22542258
'_promote': None if self._promote is None else self._promote.serialize(),
22552259
'declared_metaclass': (None if self.declared_metaclass is None
22562260
else self.declared_metaclass.serialize()),
@@ -2282,7 +2286,17 @@ def deserialize(cls, data: JsonDict) -> 'TypeInfo':
22822286
else mypy.types.Instance.deserialize(data['declared_metaclass']))
22832287
ti.metaclass_type = (None if data['metaclass_type'] is None
22842288
else mypy.types.Instance.deserialize(data['metaclass_type']))
2285-
# NOTE: ti.mro will be set in the fixup phase.
2289+
# NOTE: ti.mro will be set in the fixup phase based on these
2290+
# names. The reason we need to store the mro instead of just
2291+
# recomputing it from base classes has to do with a subtle
2292+
# point about fine-grained incremental: the cache files might
2293+
# not be loaded until after a class in the mro has changed its
2294+
# bases, which causes the mro to change. If we recomputed our
2295+
# mro, we would compute the *new* mro, which leaves us with no
2296+
# way to detact that the mro has changed! Thus we need to make
2297+
# sure to load the original mro so that once the class is
2298+
# rechecked, it can tell that the mro has changed.
2299+
ti._mro_refs = data['mro']
22862300
ti.tuple_type = (None if data['tuple_type'] is None
22872301
else mypy.types.TupleType.deserialize(data['tuple_type']))
22882302
ti.typeddict_type = (None if data['typeddict_type'] is None

0 commit comments

Comments
 (0)