2828from typing import TYPE_CHECKING , Any , Callable , ClassVar , Final , NoReturn , TextIO , TypedDict
2929from typing_extensions import TypeAlias as _TypeAlias
3030
31+ from librt .internal import cache_version
32+
3133import mypy .semanal_main
32- from mypy .cache import Buffer , CacheMeta
34+ from mypy .cache import CACHE_VERSION , Buffer , CacheMeta
3335from mypy .checker import TypeChecker
3436from mypy .error_formatter import OUTPUT_CHOICES , ErrorFormatter
3537from mypy .errors import CompileError , ErrorInfo , Errors , report_internal_error
@@ -601,6 +603,7 @@ def __init__(
601603 self .options = options
602604 self .version_id = version_id
603605 self .modules : dict [str , MypyFile ] = {}
606+ self .import_map : dict [str , set [str ]] = {}
604607 self .missing_modules : set [str ] = set ()
605608 self .fg_deps_meta : dict [str , FgDepMeta ] = {}
606609 # fg_deps holds the dependencies of every module that has been
@@ -621,6 +624,7 @@ def __init__(
621624 self .incomplete_namespaces ,
622625 self .errors ,
623626 self .plugin ,
627+ self .import_map ,
624628 )
625629 self .all_types : dict [Expression , Type ] = {} # Enabled by export_types
626630 self .indirection_detector = TypeIndirectionVisitor ()
@@ -740,6 +744,26 @@ def getmtime(self, path: str) -> int:
740744 else :
741745 return int (self .metastore .getmtime (path ))
742746
747+ def correct_rel_imp (self , file : MypyFile , imp : ImportFrom | ImportAll ) -> str :
748+ """Function to correct for relative imports."""
749+ file_id = file .fullname
750+ rel = imp .relative
751+ if rel == 0 :
752+ return imp .id
753+ if os .path .basename (file .path ).startswith ("__init__." ):
754+ rel -= 1
755+ if rel != 0 :
756+ file_id = "." .join (file_id .split ("." )[:- rel ])
757+ new_id = file_id + "." + imp .id if imp .id else file_id
758+
759+ if not new_id :
760+ self .errors .set_file (file .path , file .name , self .options )
761+ self .errors .report (
762+ imp .line , 0 , "No parent module -- cannot perform relative import" , blocker = True
763+ )
764+
765+ return new_id
766+
743767 def all_imported_modules_in_file (self , file : MypyFile ) -> list [tuple [int , str , int ]]:
744768 """Find all reachable import statements in a file.
745769
@@ -748,27 +772,6 @@ def all_imported_modules_in_file(self, file: MypyFile) -> list[tuple[int, str, i
748772
749773 Can generate blocking errors on bogus relative imports.
750774 """
751-
752- def correct_rel_imp (imp : ImportFrom | ImportAll ) -> str :
753- """Function to correct for relative imports."""
754- file_id = file .fullname
755- rel = imp .relative
756- if rel == 0 :
757- return imp .id
758- if os .path .basename (file .path ).startswith ("__init__." ):
759- rel -= 1
760- if rel != 0 :
761- file_id = "." .join (file_id .split ("." )[:- rel ])
762- new_id = file_id + "." + imp .id if imp .id else file_id
763-
764- if not new_id :
765- self .errors .set_file (file .path , file .name , self .options )
766- self .errors .report (
767- imp .line , 0 , "No parent module -- cannot perform relative import" , blocker = True
768- )
769-
770- return new_id
771-
772775 res : list [tuple [int , str , int ]] = []
773776 for imp in file .imports :
774777 if not imp .is_unreachable :
@@ -783,7 +786,7 @@ def correct_rel_imp(imp: ImportFrom | ImportAll) -> str:
783786 ancestors .append (part )
784787 res .append ((ancestor_pri , "." .join (ancestors ), imp .line ))
785788 elif isinstance (imp , ImportFrom ):
786- cur_id = correct_rel_imp (imp )
789+ cur_id = self . correct_rel_imp (file , imp )
787790 all_are_submodules = True
788791 # Also add any imported names that are submodules.
789792 pri = import_priority (imp , PRI_MED )
@@ -803,7 +806,7 @@ def correct_rel_imp(imp: ImportFrom | ImportAll) -> str:
803806 res .append ((pri , cur_id , imp .line ))
804807 elif isinstance (imp , ImportAll ):
805808 pri = import_priority (imp , PRI_HIGH )
806- res .append ((pri , correct_rel_imp (imp ), imp .line ))
809+ res .append ((pri , self . correct_rel_imp (file , imp ), imp .line ))
807810
808811 # Sort such that module (e.g. foo.bar.baz) comes before its ancestors (e.g. foo
809812 # and foo.bar) so that, if FindModuleCache finds the target module in a
@@ -1334,12 +1337,18 @@ def find_cache_meta(id: str, path: str, manager: BuildManager) -> CacheMeta | No
13341337 return None
13351338 t1 = time .time ()
13361339 if isinstance (meta , bytes ):
1337- data_io = Buffer (meta )
1340+ # If either low-level buffer format or high-level cache layout changed, we
1341+ # cannot use the cache files, even with --skip-version-check.
1342+ # TODO: switch to something like librt.internal.read_byte() if this is slow.
1343+ if meta [0 ] != cache_version () or meta [1 ] != CACHE_VERSION :
1344+ manager .log (f"Metadata abandoned for { id } : incompatible cache format" )
1345+ return None
1346+ data_io = Buffer (meta [2 :])
13381347 m = CacheMeta .read (data_io , data_file )
13391348 else :
13401349 m = CacheMeta .deserialize (meta , data_file )
13411350 if m is None :
1342- manager .log (f"Metadata abandoned for { id } : attributes are missing " )
1351+ manager .log (f"Metadata abandoned for { id } : cannot deserialize data " )
13431352 return None
13441353 t2 = time .time ()
13451354 manager .add_stats (
@@ -1671,7 +1680,9 @@ def write_cache_meta(meta: CacheMeta, manager: BuildManager, meta_file: str) ->
16711680 if manager .options .fixed_format_cache :
16721681 data_io = Buffer ()
16731682 meta .write (data_io )
1674- meta_bytes = data_io .getvalue ()
1683+ # Prefix with both low- and high-level cache format versions for future validation.
1684+ # TODO: switch to something like librt.internal.write_byte() if this is slow.
1685+ meta_bytes = bytes ([cache_version (), CACHE_VERSION ]) + data_io .getvalue ()
16751686 else :
16761687 meta_dict = meta .serialize ()
16771688 meta_bytes = json_dumps (meta_dict , manager .options .debug_cache )
@@ -2888,6 +2899,9 @@ def dispatch(sources: list[BuildSource], manager: BuildManager, stdout: TextIO)
28882899 manager .cache_enabled = False
28892900 graph = load_graph (sources , manager )
28902901
2902+ for id in graph :
2903+ manager .import_map [id ] = set (graph [id ].dependencies + graph [id ].suppressed )
2904+
28912905 t1 = time .time ()
28922906 manager .add_stats (
28932907 graph_size = len (graph ),
0 commit comments