From d6824348450560c0b2dcd5e930d75d916f67b06d Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Thu, 24 Feb 2022 01:09:28 -0800 Subject: [PATCH] Fix caching of PEP 561 namespace packages with missing submodules Fixes #12232 Another "fail every 2nd run" bug --- mypy/build.py | 30 ++++++++++++++++++------------ test-data/unit/pep561.test | 10 ++++++++++ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 24cef0b83bd3..c486f8e8a68f 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -730,20 +730,19 @@ def correct_rel_imp(imp: Union[ImportFrom, ImportAll]) -> str: return new_id res: List[Tuple[int, str, int]] = [] + delayed_res: List[Tuple[int, str, int]] = [] for imp in file.imports: if not imp.is_unreachable: if isinstance(imp, Import): pri = import_priority(imp, PRI_MED) ancestor_pri = import_priority(imp, PRI_LOW) for id, _ in imp.ids: - # We append the target (e.g. foo.bar.baz) - # before the ancestors (e.g. foo and foo.bar) - # so that, if FindModuleCache finds the target - # module in a package marked with py.typed - # underneath a namespace package installed in - # site-packages, (gasp), that cache's - # knowledge of the ancestors can be primed - # when it is asked to find the target. + # We append the target (e.g. foo.bar.baz) before the ancestors (e.g. foo + # and foo.bar) so that, if FindModuleCache finds the target module in a + # package marked with py.typed underneath a namespace package installed in + # site-packages, (gasp), that cache's knowledge of the ancestors + # (aka FindModuleCache.ns_ancestors) can be primed when it is asked to find + # the parent. res.append((pri, id, imp.line)) ancestor_parts = id.split(".")[:-1] ancestors = [] @@ -752,6 +751,7 @@ 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) + any_are_submodules = False all_are_submodules = True # Also add any imported names that are submodules. pri = import_priority(imp, PRI_MED) @@ -759,6 +759,7 @@ def correct_rel_imp(imp: Union[ImportFrom, ImportAll]) -> str: sub_id = cur_id + '.' + name if self.is_module(sub_id): res.append((pri, sub_id, imp.line)) + any_are_submodules = True else: all_are_submodules = False # Add cur_id as a dependency, even if all of the @@ -768,14 +769,19 @@ 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) - # 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)) + # The imported module goes in after the submodules, for the same namespace + # related reasons discussed in the Import case. + # There is an additional twist: if none of the submodules exist, + # we delay the import in case other imports of other submodules succeed. + if any_are_submodules: + res.append((pri, cur_id, imp.line)) + else: + delayed_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)) + res.extend(delayed_res) return res def is_module(self, id: str) -> bool: diff --git a/test-data/unit/pep561.test b/test-data/unit/pep561.test index 364414b202e1..290cf73b7829 100644 --- a/test-data/unit/pep561.test +++ b/test-data/unit/pep561.test @@ -222,3 +222,13 @@ b.bf(1) [out] testNamespacePkgWStubsWithNamespacePackagesFlag.py:7: error: Argument 1 to "bf" has incompatible type "int"; expected "bool" testNamespacePkgWStubsWithNamespacePackagesFlag.py:8: error: Argument 1 to "bf" has incompatible type "int"; expected "bool" + + +[case testTypedPkgNamespaceRegFromImportTwiceMissing] +# pkgs: typedpkg_ns_a +from typedpkg_ns import b # type: ignore +from typedpkg_ns import a +-- dummy should trigger a second iteration +[file dummy.py.2] +[out] +[out2]