1515
1616import collections
1717import contextlib
18- import errno
1918import gc
2019import json
2120import os
@@ -337,6 +336,7 @@ class CacheMeta(NamedTuple):
337336 dep_lines : list [int ]
338337 dep_hashes : dict [str , str ]
339338 interface_hash : str # hash representing the public interface
339+ error_lines : list [str ]
340340 version_id : str # mypy version for cache invalidation
341341 ignore_all : bool # if errors were ignored
342342 plugin_data : Any # config data from plugins
@@ -376,6 +376,7 @@ def cache_meta_from_dict(meta: dict[str, Any], data_json: str) -> CacheMeta:
376376 meta .get ("dep_lines" , []),
377377 meta .get ("dep_hashes" , {}),
378378 meta .get ("interface_hash" , "" ),
379+ meta .get ("error_lines" , []),
379380 meta .get ("version_id" , sentinel ),
380381 meta .get ("ignore_all" , True ),
381382 meta .get ("plugin_data" , None ),
@@ -1502,6 +1503,7 @@ def validate_meta(
15021503 "dep_lines" : meta .dep_lines ,
15031504 "dep_hashes" : meta .dep_hashes ,
15041505 "interface_hash" : meta .interface_hash ,
1506+ "error_lines" : meta .error_lines ,
15051507 "version_id" : manager .version_id ,
15061508 "ignore_all" : meta .ignore_all ,
15071509 "plugin_data" : meta .plugin_data ,
@@ -1678,28 +1680,6 @@ def write_cache_meta(
16781680 return cache_meta_from_dict (meta , data_json )
16791681
16801682
1681- def delete_cache (id : str , path : str , manager : BuildManager ) -> None :
1682- """Delete cache files for a module.
1683-
1684- The cache files for a module are deleted when mypy finds errors there.
1685- This avoids inconsistent states with cache files from different mypy runs,
1686- see #4043 for an example.
1687- """
1688- # We don't delete .deps files on errors, since the dependencies
1689- # are mostly generated from other files and the metadata is
1690- # tracked separately.
1691- meta_path , data_path , _ = get_cache_names (id , path , manager .options )
1692- cache_paths = [meta_path , data_path ]
1693- manager .log (f"Deleting { id } { path } { ' ' .join (x for x in cache_paths if x )} " )
1694-
1695- for filename in cache_paths :
1696- try :
1697- manager .metastore .remove (filename )
1698- except OSError as e :
1699- if e .errno != errno .ENOENT :
1700- manager .log (f"Error deleting cache file { filename } : { e .strerror } " )
1701-
1702-
17031683"""Dependency manager.
17041684
17051685Design
@@ -1875,6 +1855,9 @@ class State:
18751855 # Map from dependency id to its last observed interface hash
18761856 dep_hashes : dict [str , str ] = {}
18771857
1858+ # List of errors reported for this file last time.
1859+ error_lines : list [str ] = []
1860+
18781861 # Parent package, its parent, etc.
18791862 ancestors : list [str ] | None = None
18801863
@@ -1896,9 +1879,6 @@ class State:
18961879 # Whether to ignore all errors
18971880 ignore_all = False
18981881
1899- # Whether the module has an error or any of its dependencies have one.
1900- transitive_error = False
1901-
19021882 # Errors reported before semantic analysis, to allow fine-grained
19031883 # mode to keep reporting them.
19041884 early_errors : list [ErrorInfo ]
@@ -2000,6 +1980,7 @@ def __init__(
20001980 assert len (all_deps ) == len (self .meta .dep_lines )
20011981 self .dep_line_map = {id : line for id , line in zip (all_deps , self .meta .dep_lines )}
20021982 self .dep_hashes = self .meta .dep_hashes
1983+ self .error_lines = self .meta .error_lines
20031984 if temporary :
20041985 self .load_tree (temporary = True )
20051986 if not manager .use_fine_grained_cache ():
@@ -2517,11 +2498,6 @@ def write_cache(self) -> tuple[dict[str, Any], str, str] | None:
25172498 print (f"Error serializing { self .id } " , file = self .manager .stdout )
25182499 raise # Propagate to display traceback
25192500 return None
2520- is_errors = self .transitive_error
2521- if is_errors :
2522- delete_cache (self .id , self .path , self .manager )
2523- self .meta = None
2524- return None
25252501 dep_prios = self .dependency_priorities ()
25262502 dep_lines = self .dependency_lines ()
25272503 assert self .source_hash is not None
@@ -3303,34 +3279,7 @@ def process_graph(graph: Graph, manager: BuildManager) -> None:
33033279 if undeps :
33043280 fresh = False
33053281 if fresh :
3306- # All cache files are fresh. Check that no dependency's
3307- # cache file is newer than any scc node's cache file.
3308- oldest_in_scc = min (graph [id ].xmeta .data_mtime for id in scc )
3309- viable = {id for id in stale_deps if graph [id ].meta is not None }
3310- newest_in_deps = (
3311- 0 if not viable else max (graph [dep ].xmeta .data_mtime for dep in viable )
3312- )
3313- if manager .options .verbosity >= 3 : # Dump all mtimes for extreme debugging.
3314- all_ids = sorted (ascc | viable , key = lambda id : graph [id ].xmeta .data_mtime )
3315- for id in all_ids :
3316- if id in scc :
3317- if graph [id ].xmeta .data_mtime < newest_in_deps :
3318- key = "*id:"
3319- else :
3320- key = "id:"
3321- else :
3322- if graph [id ].xmeta .data_mtime > oldest_in_scc :
3323- key = "+dep:"
3324- else :
3325- key = "dep:"
3326- manager .trace (" %5s %.0f %s" % (key , graph [id ].xmeta .data_mtime , id ))
3327- # If equal, give the benefit of the doubt, due to 1-sec time granularity
3328- # (on some platforms).
3329- if oldest_in_scc < newest_in_deps :
3330- fresh = False
3331- fresh_msg = f"out of date by { newest_in_deps - oldest_in_scc :.0f} seconds"
3332- else :
3333- fresh_msg = "fresh"
3282+ fresh_msg = "fresh"
33343283 elif undeps :
33353284 fresh_msg = f"stale due to changed suppression ({ ' ' .join (sorted (undeps ))} )"
33363285 elif stale_scc :
@@ -3342,15 +3291,14 @@ def process_graph(graph: Graph, manager: BuildManager) -> None:
33423291 else :
33433292 fresh_msg = f"stale due to deps ({ ' ' .join (sorted (stale_deps ))} )"
33443293
3345- # Initialize transitive_error for all SCC members from union
3346- # of transitive_error of dependencies.
3347- if any (graph [dep ].transitive_error for dep in deps if dep in graph ):
3348- for id in scc :
3349- graph [id ].transitive_error = True
3350-
33513294 scc_str = " " .join (scc )
33523295 if fresh :
33533296 manager .trace (f"Queuing { fresh_msg } SCC ({ scc_str } )" )
3297+ for id in scc :
3298+ if graph [id ].error_lines :
3299+ manager .flush_errors (
3300+ manager .errors .simplify_path (graph [id ].xpath ), graph [id ].error_lines , False
3301+ )
33543302 fresh_scc_queue .append (scc )
33553303 else :
33563304 if fresh_scc_queue :
@@ -3362,11 +3310,6 @@ def process_graph(graph: Graph, manager: BuildManager) -> None:
33623310 # single fresh SCC. This is intentional -- we don't need those modules
33633311 # loaded if there are no more stale SCCs to be rechecked.
33643312 #
3365- # Also note we shouldn't have to worry about transitive_error here,
3366- # since modules with transitive errors aren't written to the cache,
3367- # and if any dependencies were changed, this SCC would be stale.
3368- # (Also, in quick_and_dirty mode we don't care about transitive errors.)
3369- #
33703313 # TODO: see if it's possible to determine if we need to process only a
33713314 # _subset_ of the past SCCs instead of having to process them all.
33723315 if (
@@ -3518,16 +3461,17 @@ def process_stale_scc(graph: Graph, scc: list[str], manager: BuildManager) -> No
35183461 for id in stale :
35193462 graph [id ].generate_unused_ignore_notes ()
35203463 graph [id ].generate_ignore_without_code_notes ()
3521- if any (manager .errors .is_errors_for_file (graph [id ].xpath ) for id in stale ):
3522- for id in stale :
3523- graph [id ].transitive_error = True
3464+
3465+ # Flush errors, and write cache in two phases: first data files, then meta files.
35243466 meta_tuples = {}
3467+ errors_by_id = {}
35253468 for id in stale :
35263469 if graph [id ].xpath not in manager .errors .ignored_files :
35273470 errors = manager .errors .file_messages (
35283471 graph [id ].xpath , formatter = manager .error_formatter
35293472 )
35303473 manager .flush_errors (manager .errors .simplify_path (graph [id ].xpath ), errors , False )
3474+ errors_by_id [id ] = errors
35313475 meta_tuples [id ] = graph [id ].write_cache ()
35323476 graph [id ].mark_as_rechecked ()
35333477 for id in stale :
@@ -3539,6 +3483,7 @@ def process_stale_scc(graph: Graph, scc: list[str], manager: BuildManager) -> No
35393483 meta ["dep_hashes" ] = {
35403484 dep : graph [dep ].interface_hash for dep in graph [id ].dependencies if dep in graph
35413485 }
3486+ meta ["error_lines" ] = errors_by_id .get (id , [])
35423487 graph [id ].meta = write_cache_meta (meta , manager , meta_json , data_json )
35433488
35443489
0 commit comments