From 09a97fa8186de95881dd7d45b3a0f481bb907b86 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Thu, 5 Aug 2021 16:22:26 -0700 Subject: [PATCH 1/2] Fix caching behavior of PEP561-installed namespace packages Since PEP561-installed namespace packages only show up in FindModuleCache as a side-effect, the submodules need to be listed first in the dependencies for things to work. This was handled already in the Import case but not the ImportFrom case. Also fix the cache handling of namespace packages so it doesn't always get reported stale. Fixes #9852. --- mypy/build.py | 19 ++++++++++++++----- test-data/unit/check-incremental.test | 22 ++++++++++++++++++++++ test-data/unit/pep561.test | 5 ++--- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index b8831b21a4ad..fbebcc380ff8 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -751,7 +751,6 @@ def correct_rel_imp(imp: Union[ImportFrom, ImportAll]) -> str: res.append((ancestor_pri, ".".join(ancestors), imp.line)) elif isinstance(imp, ImportFrom): cur_id = correct_rel_imp(imp) - pos = len(res) all_are_submodules = True # Also add any imported names that are submodules. pri = import_priority(imp, PRI_MED) @@ -768,7 +767,10 @@ def correct_rel_imp(imp: Union[ImportFrom, ImportAll]) -> str: # if all of the imports are submodules, do the import at a lower # priority. pri = import_priority(imp, PRI_HIGH if not all_are_submodules else PRI_LOW) - res.insert(pos, ((pri, cur_id, imp.line))) + # The imported module goes in after the + # submodules, for the same namespace related + # reasons discussed in the Import case. + res.append((pri, cur_id, imp.line)) elif isinstance(imp, ImportAll): pri = import_priority(imp, PRI_HIGH) res.append((pri, correct_rel_imp(imp), imp.line)) @@ -1317,7 +1319,7 @@ def validate_meta(meta: Optional[CacheMeta], id: str, path: Optional[str], st = manager.get_stat(path) except OSError: return None - if not stat.S_ISREG(st.st_mode): + if not (stat.S_ISREG(st.st_mode) or stat.S_ISDIR(st.st_mode)): manager.log('Metadata abandoned for {}: file {} does not exist'.format(id, path)) return None @@ -1360,7 +1362,11 @@ def validate_meta(meta: Optional[CacheMeta], id: str, path: Optional[str], t0 = time.time() try: - source_hash = manager.fscache.hash_digest(path) + # dir means it is a namespace package + if stat.S_ISDIR(st.st_mode): + source_hash = '' + else: + source_hash = manager.fscache.hash_digest(path) except (OSError, UnicodeDecodeError, DecodeError): return None manager.add_stats(validate_hash_time=time.time() - t0) @@ -1838,7 +1844,7 @@ def __init__(self, if path and source is None and self.manager.fscache.isdir(path): source = '' self.source = source - if path and source is None and self.manager.cache_enabled: + if path and self.manager.cache_enabled: self.meta = find_cache_meta(self.id, path, manager) # TODO: Get mtime if not cached. if self.meta is not None: @@ -2038,6 +2044,9 @@ def parse_file(self) -> None: else: err = "mypy: can't decode file '{}': {}".format(self.path, str(decodeerr)) raise CompileError([err], module_with_blocker=self.id) from decodeerr + elif self.path and self.manager.fscache.isdir(self.path): + source = '' + self.source_hash = '' else: assert source is not None self.source_hash = compute_hash(source) diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 058575979c9d..0baea1c978b1 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -5563,3 +5563,25 @@ class D: [out2] tmp/a.py:2: note: Revealed type is "builtins.list[builtins.int]" tmp/a.py:3: note: Revealed type is "builtins.list[builtins.str]" + +[case testIncrementalNamespacePackage1] +# flags: --namespace-packages +import m +[file m.py] +from foo.bar import x +x + 0 +[file foo/bar.py] +x = 0 +[rechecked] +[stale] + +[case testIncrementalNamespacePackage2] +# flags: --namespace-packages +import m +[file m.py] +from foo import bar +bar.x + 0 +[file foo/bar.py] +x = 0 +[rechecked] +[stale] diff --git a/test-data/unit/pep561.test b/test-data/unit/pep561.test index bd3fa024c5d8..033d0c52c5ae 100644 --- a/test-data/unit/pep561.test +++ b/test-data/unit/pep561.test @@ -187,9 +187,8 @@ import a [out2] a.py:1: error: Unsupported operand types for + ("int" and "str") --- Test for issue #9852, among others -[case testTypedPkgNamespaceRegFromImportTwice-xfail] -# pkgs: typedpkg, typedpkg_ns +[case testTypedPkgNamespaceRegFromImportTwice] +# pkgs: typedpkg_ns from typedpkg_ns import ns -- dummy should trigger a second iteration [file dummy.py.2] From 878e32782337e73ee1b847aca7a0004f5212b17e Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Fri, 6 Aug 2021 12:24:20 -0700 Subject: [PATCH 2/2] restore the old test and sink the test --- mypy/build.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index fbebcc380ff8..9c77adba8d30 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -1841,15 +1841,15 @@ def __init__(self, if path: self.abspath = os.path.abspath(path) self.xpath = path or '' - if path and source is None and self.manager.fscache.isdir(path): - source = '' - self.source = source - if path and self.manager.cache_enabled: + if path and source is None and self.manager.cache_enabled: self.meta = find_cache_meta(self.id, path, manager) # TODO: Get mtime if not cached. if self.meta is not None: self.interface_hash = self.meta.interface_hash self.meta_source_hash = self.meta.hash + if path and source is None and self.manager.fscache.isdir(path): + source = '' + self.source = source self.add_ancestors() t0 = time.time() self.meta = validate_meta(self.meta, self.id, self.path, self.ignore_all, manager)