32
32
if MYPY :
33
33
from typing import Deque
34
34
35
- from mypy .nodes import (MypyFile , Node , ImportBase , Import , ImportFrom , ImportAll )
35
+ from mypy .nodes import (MODULE_REF , MypyFile , Node , ImportBase , Import , ImportFrom , ImportAll )
36
36
from mypy .semanal_pass1 import SemanticAnalyzerPass1
37
37
from mypy .semanal import SemanticAnalyzerPass2
38
38
from mypy .semanal_pass3 import SemanticAnalyzerPass3
@@ -179,6 +179,8 @@ def build(sources: List[BuildSource],
179
179
# multiple builds, there could be a mix of files/modules, so its easier
180
180
# to just define the semantics that we always add the current director
181
181
# to the lib_path
182
+ # TODO: Don't do this in some cases; for motivation see see
183
+ # https://github.com/python/mypy/issues/4195#issuecomment-341915031
182
184
lib_path .insert (0 , os .getcwd ())
183
185
184
186
# Prepend a config-defined mypy path.
@@ -1617,7 +1619,6 @@ def mark_as_rechecked(self) -> None:
1617
1619
1618
1620
def mark_interface_stale (self , * , on_errors : bool = False ) -> None :
1619
1621
"""Marks this module as having a stale public interface, and discards the cache data."""
1620
- self .meta = None
1621
1622
self .externally_same = False
1622
1623
if not on_errors :
1623
1624
self .manager .stale_modules .add (self .id )
@@ -1899,6 +1900,7 @@ def write_cache(self) -> None:
1899
1900
is_errors = self .manager .errors .is_errors ()
1900
1901
if is_errors :
1901
1902
delete_cache (self .id , self .path , self .manager )
1903
+ self .meta = None
1902
1904
self .mark_interface_stale (on_errors = True )
1903
1905
return
1904
1906
dep_prios = [self .priorities .get (dep , PRI_HIGH ) for dep in self .dependencies ]
@@ -1916,6 +1918,7 @@ def write_cache(self) -> None:
1916
1918
1917
1919
1918
1920
def dispatch (sources : List [BuildSource ], manager : BuildManager ) -> Graph :
1921
+ set_orig = set (manager .saved_cache )
1919
1922
manager .log ()
1920
1923
manager .log ("Mypy version %s" % __version__ )
1921
1924
t0 = time .time ()
@@ -1936,7 +1939,17 @@ def dispatch(sources: List[BuildSource], manager: BuildManager) -> Graph:
1936
1939
if manager .options .warn_unused_ignores :
1937
1940
# TODO: This could also be a per-module option.
1938
1941
manager .errors .generate_unused_ignore_notes ()
1939
- manager .saved_cache .update (preserve_cache (graph ))
1942
+ updated = preserve_cache (graph )
1943
+ set_updated = set (updated )
1944
+ manager .saved_cache .clear ()
1945
+ manager .saved_cache .update (updated )
1946
+ set_final = set (manager .saved_cache )
1947
+ # These keys have numbers in them to force a sort order.
1948
+ manager .add_stats (saved_cache_1orig = len (set_orig ),
1949
+ saved_cache_2updated = len (set_updated & set_orig ),
1950
+ saved_cache_3added = len (set_final - set_orig ),
1951
+ saved_cache_4removed = len (set_orig - set_final ),
1952
+ saved_cache_5final = len (set_final ))
1940
1953
if manager .options .dump_deps :
1941
1954
# This speeds up startup a little when not using the daemon mode.
1942
1955
from mypy .server .deps import dump_all_dependencies
@@ -2035,7 +2048,19 @@ def load_graph(sources: List[BuildSource], manager: BuildManager) -> Graph:
2035
2048
while new :
2036
2049
st = new .popleft ()
2037
2050
assert st .ancestors is not None
2038
- for dep in st .ancestors + st .dependencies + st .suppressed :
2051
+ # Strip out indirect dependencies. These will be dealt with
2052
+ # when they show up as direct dependencies, and there's a
2053
+ # scenario where they hurt:
2054
+ # - Suppose A imports B and B imports C.
2055
+ # - Suppose on the next round:
2056
+ # - C is deleted;
2057
+ # - B is updated to remove the dependency on C;
2058
+ # - A is unchanged.
2059
+ # - In this case A's cached *direct* dependencies are still valid
2060
+ # (since direct dependencies reflect the imports found in the source)
2061
+ # but A's cached *indirect* dependency on C is wrong.
2062
+ dependencies = [dep for dep in st .dependencies if st .priorities .get (dep ) != PRI_INDIRECT ]
2063
+ for dep in st .ancestors + dependencies + st .suppressed :
2039
2064
# We don't want to recheck imports marked with '# type: ignore'
2040
2065
# so we ignore any suppressed module not explicitly re-included
2041
2066
# from the command line.
@@ -2117,7 +2142,7 @@ def process_graph(graph: Graph, manager: BuildManager) -> None:
2117
2142
for id in scc :
2118
2143
deps .update (graph [id ].dependencies )
2119
2144
deps -= ascc
2120
- stale_deps = {id for id in deps if not graph [id ].is_interface_fresh ()}
2145
+ stale_deps = {id for id in deps if id in graph and not graph [id ].is_interface_fresh ()}
2121
2146
if not manager .options .quick_and_dirty :
2122
2147
fresh = fresh and not stale_deps
2123
2148
undeps = set ()
@@ -2174,8 +2199,9 @@ def process_graph(graph: Graph, manager: BuildManager) -> None:
2174
2199
2175
2200
scc_str = " " .join (scc )
2176
2201
if fresh :
2177
- manager .trace ("Queuing %s SCC (%s)" % (fresh_msg , scc_str ))
2178
- fresh_scc_queue .append (scc )
2202
+ if not maybe_reuse_in_memory_tree (graph , scc , manager ):
2203
+ manager .trace ("Queuing %s SCC (%s)" % (fresh_msg , scc_str ))
2204
+ fresh_scc_queue .append (scc )
2179
2205
else :
2180
2206
if len (fresh_scc_queue ) > 0 :
2181
2207
manager .log ("Processing {} queued fresh SCCs" .format (len (fresh_scc_queue )))
@@ -2263,23 +2289,6 @@ def process_fresh_scc(graph: Graph, scc: List[str], manager: BuildManager) -> No
2263
2289
2264
2290
If the tree is loaded from memory ('saved_cache') it's even quicker.
2265
2291
"""
2266
- saved_cache = manager .saved_cache
2267
- # Check that all nodes are available for loading from memory.
2268
- if all (id in saved_cache for id in scc ):
2269
- deps = set (dep for id in scc for dep in graph [id ].dependencies if dep in graph )
2270
- # Check that all dependencies were loaded from memory.
2271
- # If not, some dependency was reparsed but the interface hash
2272
- # wasn't changed -- in that case we can't reuse the tree.
2273
- if all (graph [dep ].is_from_saved_cache for dep in deps ):
2274
- trees = {id : saved_cache [id ][1 ] for id in scc }
2275
- for id , tree in trees .items ():
2276
- manager .add_stats (reused_trees = 1 )
2277
- manager .trace ("Reusing saved tree %s" % id )
2278
- st = graph [id ]
2279
- st .tree = tree # This is never overwritten.
2280
- st .is_from_saved_cache = True
2281
- manager .modules [id ] = tree
2282
- return
2283
2292
for id in scc :
2284
2293
graph [id ].load_tree ()
2285
2294
for id in scc :
@@ -2290,6 +2299,70 @@ def process_fresh_scc(graph: Graph, scc: List[str], manager: BuildManager) -> No
2290
2299
graph [id ].patch_dependency_parents ()
2291
2300
2292
2301
2302
+ def maybe_reuse_in_memory_tree (graph : Graph , scc : List [str ], manager : BuildManager ) -> bool :
2303
+ """Set the trees for the given SCC from the in-memory cache, if all valid.
2304
+
2305
+ If any saved tree for this SCC is invalid, set the trees for all
2306
+ SCC members to None and mark as not-from-cache.
2307
+ """
2308
+ if not can_reuse_in_memory_tree (graph , scc , manager ):
2309
+ for id in scc :
2310
+ manager .add_stats (cleared_trees = 1 )
2311
+ manager .trace ("Clearing tree %s" % id )
2312
+ st = graph [id ]
2313
+ st .tree = None
2314
+ st .is_from_saved_cache = False
2315
+ if id in manager .modules :
2316
+ del manager .modules [id ]
2317
+ return False
2318
+ trees = {id : manager .saved_cache [id ][1 ] for id in scc }
2319
+ for id , tree in trees .items ():
2320
+ manager .add_stats (reused_trees = 1 )
2321
+ manager .trace ("Reusing saved tree %s" % id )
2322
+ st = graph [id ]
2323
+ st .tree = tree
2324
+ st .is_from_saved_cache = True
2325
+ manager .modules [id ] = tree
2326
+ # Delete any submodules from the module that aren't
2327
+ # dependencies of the module; they will be re-added once
2328
+ # imported. It's possible that the parent module is reused
2329
+ # but a submodule isn't; we don't want to accidentally link
2330
+ # into the old submodule's tree. See also
2331
+ # patch_dependency_parents() above. The exception for subname
2332
+ # in st.dependencies handles the case where 'import m'
2333
+ # guarantees that some submodule of m is also available
2334
+ # (e.g. 'os.path'); in those cases the submodule is an
2335
+ # explicit dependency of the parent.
2336
+ for name in list (tree .names ):
2337
+ sym = tree .names [name ]
2338
+ subname = id + '.' + name
2339
+ if (sym .kind == MODULE_REF
2340
+ and sym .node is not None
2341
+ and sym .node .fullname () == subname
2342
+ and subname not in st .dependencies ):
2343
+ manager .trace ("Purging %s" % subname )
2344
+ del tree .names [name ]
2345
+ return True
2346
+
2347
+
2348
+ def can_reuse_in_memory_tree (graph : Graph , scc : List [str ], manager : BuildManager ) -> bool :
2349
+ """Check whether the given SCC can safely reuse the trees from saved_cache.
2350
+
2351
+ Assumes the SCC is already considered fresh.
2352
+ """
2353
+ saved_cache = manager .saved_cache
2354
+ # Check that all nodes are available for loading from memory.
2355
+ if all (id in saved_cache for id in scc ):
2356
+ # Check that all dependencies were loaded from memory.
2357
+ # If not, some dependency was reparsed but the interface hash
2358
+ # wasn't changed -- in that case we can't reuse the tree.
2359
+ deps = set (dep for id in scc for dep in graph [id ].dependencies if dep in graph )
2360
+ deps -= set (scc ) # Subtract the SCC itself (else nothing will be safe)
2361
+ if all (graph [dep ].is_from_saved_cache for dep in deps ):
2362
+ return True
2363
+ return False
2364
+
2365
+
2293
2366
def process_stale_scc (graph : Graph , scc : List [str ], manager : BuildManager ) -> None :
2294
2367
"""Process the modules in one SCC from source code.
2295
2368
0 commit comments