15
15
16
16
import collections
17
17
import contextlib
18
- import errno
19
18
import gc
20
19
import json
21
20
import os
@@ -337,6 +336,7 @@ class CacheMeta(NamedTuple):
337
336
dep_lines : list [int ]
338
337
dep_hashes : dict [str , str ]
339
338
interface_hash : str # hash representing the public interface
339
+ error_lines : list [str ]
340
340
version_id : str # mypy version for cache invalidation
341
341
ignore_all : bool # if errors were ignored
342
342
plugin_data : Any # config data from plugins
@@ -376,6 +376,7 @@ def cache_meta_from_dict(meta: dict[str, Any], data_json: str) -> CacheMeta:
376
376
meta .get ("dep_lines" , []),
377
377
meta .get ("dep_hashes" , {}),
378
378
meta .get ("interface_hash" , "" ),
379
+ meta .get ("error_lines" , []),
379
380
meta .get ("version_id" , sentinel ),
380
381
meta .get ("ignore_all" , True ),
381
382
meta .get ("plugin_data" , None ),
@@ -1502,6 +1503,7 @@ def validate_meta(
1502
1503
"dep_lines" : meta .dep_lines ,
1503
1504
"dep_hashes" : meta .dep_hashes ,
1504
1505
"interface_hash" : meta .interface_hash ,
1506
+ "error_lines" : meta .error_lines ,
1505
1507
"version_id" : manager .version_id ,
1506
1508
"ignore_all" : meta .ignore_all ,
1507
1509
"plugin_data" : meta .plugin_data ,
@@ -1678,28 +1680,6 @@ def write_cache_meta(
1678
1680
return cache_meta_from_dict (meta , data_json )
1679
1681
1680
1682
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
-
1703
1683
"""Dependency manager.
1704
1684
1705
1685
Design
@@ -1875,6 +1855,9 @@ class State:
1875
1855
# Map from dependency id to its last observed interface hash
1876
1856
dep_hashes : dict [str , str ] = {}
1877
1857
1858
+ # List of errors reported for this file last time.
1859
+ error_lines : list [str ] = []
1860
+
1878
1861
# Parent package, its parent, etc.
1879
1862
ancestors : list [str ] | None = None
1880
1863
@@ -1896,9 +1879,6 @@ class State:
1896
1879
# Whether to ignore all errors
1897
1880
ignore_all = False
1898
1881
1899
- # Whether the module has an error or any of its dependencies have one.
1900
- transitive_error = False
1901
-
1902
1882
# Errors reported before semantic analysis, to allow fine-grained
1903
1883
# mode to keep reporting them.
1904
1884
early_errors : list [ErrorInfo ]
@@ -2000,6 +1980,7 @@ def __init__(
2000
1980
assert len (all_deps ) == len (self .meta .dep_lines )
2001
1981
self .dep_line_map = {id : line for id , line in zip (all_deps , self .meta .dep_lines )}
2002
1982
self .dep_hashes = self .meta .dep_hashes
1983
+ self .error_lines = self .meta .error_lines
2003
1984
if temporary :
2004
1985
self .load_tree (temporary = True )
2005
1986
if not manager .use_fine_grained_cache ():
@@ -2517,11 +2498,6 @@ def write_cache(self) -> tuple[dict[str, Any], str, str] | None:
2517
2498
print (f"Error serializing { self .id } " , file = self .manager .stdout )
2518
2499
raise # Propagate to display traceback
2519
2500
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
2525
2501
dep_prios = self .dependency_priorities ()
2526
2502
dep_lines = self .dependency_lines ()
2527
2503
assert self .source_hash is not None
@@ -3315,15 +3291,14 @@ def process_graph(graph: Graph, manager: BuildManager) -> None:
3315
3291
else :
3316
3292
fresh_msg = f"stale due to deps ({ ' ' .join (sorted (stale_deps ))} )"
3317
3293
3318
- # Initialize transitive_error for all SCC members from union
3319
- # of transitive_error of dependencies.
3320
- if any (graph [dep ].transitive_error for dep in deps if dep in graph ):
3321
- for id in scc :
3322
- graph [id ].transitive_error = True
3323
-
3324
3294
scc_str = " " .join (scc )
3325
3295
if fresh :
3326
3296
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
+ )
3327
3302
fresh_scc_queue .append (scc )
3328
3303
else :
3329
3304
if fresh_scc_queue :
@@ -3335,11 +3310,6 @@ def process_graph(graph: Graph, manager: BuildManager) -> None:
3335
3310
# single fresh SCC. This is intentional -- we don't need those modules
3336
3311
# loaded if there are no more stale SCCs to be rechecked.
3337
3312
#
3338
- # Also note we shouldn't have to worry about transitive_error here,
3339
- # since modules with transitive errors aren't written to the cache,
3340
- # and if any dependencies were changed, this SCC would be stale.
3341
- # (Also, in quick_and_dirty mode we don't care about transitive errors.)
3342
- #
3343
3313
# TODO: see if it's possible to determine if we need to process only a
3344
3314
# _subset_ of the past SCCs instead of having to process them all.
3345
3315
if (
@@ -3491,16 +3461,17 @@ def process_stale_scc(graph: Graph, scc: list[str], manager: BuildManager) -> No
3491
3461
for id in stale :
3492
3462
graph [id ].generate_unused_ignore_notes ()
3493
3463
graph [id ].generate_ignore_without_code_notes ()
3494
- if any (manager .errors .is_errors_for_file (graph [id ].xpath ) for id in stale ):
3495
- for id in stale :
3496
- graph [id ].transitive_error = True
3464
+
3465
+ # Flush errors, and write cache in two phases: first data files, then meta files.
3497
3466
meta_tuples = {}
3467
+ errors_by_id = {}
3498
3468
for id in stale :
3499
3469
if graph [id ].xpath not in manager .errors .ignored_files :
3500
3470
errors = manager .errors .file_messages (
3501
3471
graph [id ].xpath , formatter = manager .error_formatter
3502
3472
)
3503
3473
manager .flush_errors (manager .errors .simplify_path (graph [id ].xpath ), errors , False )
3474
+ errors_by_id [id ] = errors
3504
3475
meta_tuples [id ] = graph [id ].write_cache ()
3505
3476
graph [id ].mark_as_rechecked ()
3506
3477
for id in stale :
@@ -3512,6 +3483,7 @@ def process_stale_scc(graph: Graph, scc: list[str], manager: BuildManager) -> No
3512
3483
meta ["dep_hashes" ] = {
3513
3484
dep : graph [dep ].interface_hash for dep in graph [id ].dependencies if dep in graph
3514
3485
}
3486
+ meta ["error_lines" ] = errors_by_id .get (id , [])
3515
3487
graph [id ].meta = write_cache_meta (meta , manager , meta_json , data_json )
3516
3488
3517
3489
0 commit comments