Skip to content

Commit abab1cd

Browse files
authored
Fix caching of PEP 561 namespace packages with missing submodules (#12250)
Fixes #12232 Another "fail every 2nd run" bug `if not self.is_module(sub_id):`, the parent can come before another child, and we fail to resolve the parent. Co-authored-by: hauntsaninja <>
1 parent 8d02b47 commit abab1cd

File tree

5 files changed

+34
-18
lines changed

5 files changed

+34
-18
lines changed

mypy/build.py

+18-12
Original file line numberDiff line numberDiff line change
@@ -730,20 +730,19 @@ def correct_rel_imp(imp: Union[ImportFrom, ImportAll]) -> str:
730730
return new_id
731731

732732
res: List[Tuple[int, str, int]] = []
733+
delayed_res: List[Tuple[int, str, int]] = []
733734
for imp in file.imports:
734735
if not imp.is_unreachable:
735736
if isinstance(imp, Import):
736737
pri = import_priority(imp, PRI_MED)
737738
ancestor_pri = import_priority(imp, PRI_LOW)
738739
for id, _ in imp.ids:
739-
# We append the target (e.g. foo.bar.baz)
740-
# before the ancestors (e.g. foo and foo.bar)
741-
# so that, if FindModuleCache finds the target
742-
# module in a package marked with py.typed
743-
# underneath a namespace package installed in
744-
# site-packages, (gasp), that cache's
745-
# knowledge of the ancestors can be primed
746-
# when it is asked to find the target.
740+
# We append the target (e.g. foo.bar.baz) before the ancestors (e.g. foo
741+
# and foo.bar) so that, if FindModuleCache finds the target module in a
742+
# package marked with py.typed underneath a namespace package installed in
743+
# site-packages, (gasp), that cache's knowledge of the ancestors
744+
# (aka FindModuleCache.ns_ancestors) can be primed when it is asked to find
745+
# the parent.
747746
res.append((pri, id, imp.line))
748747
ancestor_parts = id.split(".")[:-1]
749748
ancestors = []
@@ -752,13 +751,15 @@ def correct_rel_imp(imp: Union[ImportFrom, ImportAll]) -> str:
752751
res.append((ancestor_pri, ".".join(ancestors), imp.line))
753752
elif isinstance(imp, ImportFrom):
754753
cur_id = correct_rel_imp(imp)
754+
any_are_submodules = False
755755
all_are_submodules = True
756756
# Also add any imported names that are submodules.
757757
pri = import_priority(imp, PRI_MED)
758758
for name, __ in imp.names:
759759
sub_id = cur_id + '.' + name
760760
if self.is_module(sub_id):
761761
res.append((pri, sub_id, imp.line))
762+
any_are_submodules = True
762763
else:
763764
all_are_submodules = False
764765
# Add cur_id as a dependency, even if all of the
@@ -768,14 +769,19 @@ def correct_rel_imp(imp: Union[ImportFrom, ImportAll]) -> str:
768769
# if all of the imports are submodules, do the import at a lower
769770
# priority.
770771
pri = import_priority(imp, PRI_HIGH if not all_are_submodules else PRI_LOW)
771-
# The imported module goes in after the
772-
# submodules, for the same namespace related
773-
# reasons discussed in the Import case.
774-
res.append((pri, cur_id, imp.line))
772+
# The imported module goes in after the submodules, for the same namespace
773+
# related reasons discussed in the Import case.
774+
# There is an additional twist: if none of the submodules exist,
775+
# we delay the import in case other imports of other submodules succeed.
776+
if any_are_submodules:
777+
res.append((pri, cur_id, imp.line))
778+
else:
779+
delayed_res.append((pri, cur_id, imp.line))
775780
elif isinstance(imp, ImportAll):
776781
pri = import_priority(imp, PRI_HIGH)
777782
res.append((pri, correct_rel_imp(imp), imp.line))
778783

784+
res.extend(delayed_res)
779785
return res
780786

781787
def is_module(self, id: str) -> bool:

test-data/unit/check-errorcodes.test

+3-3
Original file line numberDiff line numberDiff line change
@@ -596,10 +596,10 @@ if int() is str(): # E: Non-overlapping identity check (left operand type: "int
596596
[builtins fixtures/primitives.pyi]
597597

598598
[case testErrorCodeMissingModule]
599-
from defusedxml import xyz # E: Cannot find implementation or library stub for module named "defusedxml" [import] \
600-
# N: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
599+
from defusedxml import xyz # E: Cannot find implementation or library stub for module named "defusedxml" [import]
601600
from nonexistent import foobar # E: Cannot find implementation or library stub for module named "nonexistent" [import]
602-
import nonexistent2 # E: Cannot find implementation or library stub for module named "nonexistent2" [import]
601+
import nonexistent2 # E: Cannot find implementation or library stub for module named "nonexistent2" [import] \
602+
# N: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
603603
from nonexistent3 import * # E: Cannot find implementation or library stub for module named "nonexistent3" [import]
604604
from pkg import bad # E: Module "pkg" has no attribute "bad" [attr-defined]
605605
from pkg.bad2 import bad3 # E: Cannot find implementation or library stub for module named "pkg.bad2" [import]

test-data/unit/cmdline.test

+2-2
Original file line numberDiff line numberDiff line change
@@ -415,9 +415,9 @@ follow_imports = error
415415
[file a.py]
416416
/ # No error reported
417417
[out]
418-
main.py:1: error: Import of "a" ignored
419-
main.py:1: note: (Using --follow-imports=error, module not passed on command line)
420418
main.py:2: note: Revealed type is "Any"
419+
main.py:3: error: Import of "a" ignored
420+
main.py:3: note: (Using --follow-imports=error, module not passed on command line)
421421
main.py:4: note: Revealed type is "Any"
422422

423423
[case testConfigFollowImportsSelective]

test-data/unit/pep561.test

+10
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,13 @@ b.bf(1)
222222
[out]
223223
testNamespacePkgWStubsWithNamespacePackagesFlag.py:7: error: Argument 1 to "bf" has incompatible type "int"; expected "bool"
224224
testNamespacePkgWStubsWithNamespacePackagesFlag.py:8: error: Argument 1 to "bf" has incompatible type "int"; expected "bool"
225+
226+
227+
[case testTypedPkgNamespaceRegFromImportTwiceMissing]
228+
# pkgs: typedpkg_ns_a
229+
from typedpkg_ns import b # type: ignore
230+
from typedpkg_ns import a
231+
-- dummy should trigger a second iteration
232+
[file dummy.py.2]
233+
[out]
234+
[out2]

test-data/unit/semanal-errors.test

+1-1
Original file line numberDiff line numberDiff line change
@@ -293,8 +293,8 @@ from m.n import x
293293
from a.b import *
294294
[out]
295295
main:2: error: Cannot find implementation or library stub for module named "m.n"
296-
main:2: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
297296
main:3: error: Cannot find implementation or library stub for module named "a.b"
297+
main:3: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
298298

299299
[case testErrorInImportedModule]
300300
import m

0 commit comments

Comments
 (0)