Skip to content

Commit c0b3530

Browse files
authored
Fix caching of PEP 561 namespace packages (#13124)
Fixes #13085. Hopefully more robust than previous fixes along these lines. Co-authored-by: hauntsaninja <>
1 parent 962d7b4 commit c0b3530

7 files changed

+30
-30
lines changed

mypy/build.py

+8-18
Original file line numberDiff line numberDiff line change
@@ -708,19 +708,12 @@ def correct_rel_imp(imp: Union[ImportFrom, ImportAll]) -> str:
708708
return new_id
709709

710710
res: List[Tuple[int, str, int]] = []
711-
delayed_res: List[Tuple[int, str, int]] = []
712711
for imp in file.imports:
713712
if not imp.is_unreachable:
714713
if isinstance(imp, Import):
715714
pri = import_priority(imp, PRI_MED)
716715
ancestor_pri = import_priority(imp, PRI_LOW)
717716
for id, _ in imp.ids:
718-
# We append the target (e.g. foo.bar.baz) before the ancestors (e.g. foo
719-
# and foo.bar) so that, if FindModuleCache finds the target module in a
720-
# package marked with py.typed underneath a namespace package installed in
721-
# site-packages, (gasp), that cache's knowledge of the ancestors
722-
# (aka FindModuleCache.ns_ancestors) can be primed when it is asked to find
723-
# the parent.
724717
res.append((pri, id, imp.line))
725718
ancestor_parts = id.split(".")[:-1]
726719
ancestors = []
@@ -729,15 +722,13 @@ def correct_rel_imp(imp: Union[ImportFrom, ImportAll]) -> str:
729722
res.append((ancestor_pri, ".".join(ancestors), imp.line))
730723
elif isinstance(imp, ImportFrom):
731724
cur_id = correct_rel_imp(imp)
732-
any_are_submodules = False
733725
all_are_submodules = True
734726
# Also add any imported names that are submodules.
735727
pri = import_priority(imp, PRI_MED)
736728
for name, __ in imp.names:
737729
sub_id = cur_id + '.' + name
738730
if self.is_module(sub_id):
739731
res.append((pri, sub_id, imp.line))
740-
any_are_submodules = True
741732
else:
742733
all_are_submodules = False
743734
# Add cur_id as a dependency, even if all of the
@@ -747,19 +738,18 @@ def correct_rel_imp(imp: Union[ImportFrom, ImportAll]) -> str:
747738
# if all of the imports are submodules, do the import at a lower
748739
# priority.
749740
pri = import_priority(imp, PRI_HIGH if not all_are_submodules else PRI_LOW)
750-
# The imported module goes in after the submodules, for the same namespace
751-
# related reasons discussed in the Import case.
752-
# There is an additional twist: if none of the submodules exist,
753-
# we delay the import in case other imports of other submodules succeed.
754-
if any_are_submodules:
755-
res.append((pri, cur_id, imp.line))
756-
else:
757-
delayed_res.append((pri, cur_id, imp.line))
741+
res.append((pri, cur_id, imp.line))
758742
elif isinstance(imp, ImportAll):
759743
pri = import_priority(imp, PRI_HIGH)
760744
res.append((pri, correct_rel_imp(imp), imp.line))
761745

762-
res.extend(delayed_res)
746+
# Sort such that module (e.g. foo.bar.baz) comes before its ancestors (e.g. foo
747+
# and foo.bar) so that, if FindModuleCache finds the target module in a
748+
# package marked with py.typed underneath a namespace package installed in
749+
# site-packages, (gasp), that cache's knowledge of the ancestors
750+
# (aka FindModuleCache.ns_ancestors) can be primed when it is asked to find
751+
# the parent.
752+
res.sort(key=lambda x: -x[1].count("."))
763753
return res
764754

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

test-data/unit/check-errorcodes.test

+3-3
Original file line numberDiff line numberDiff line change
@@ -598,11 +598,11 @@ if int() is str(): # E: Non-overlapping identity check (left operand type: "int
598598
[case testErrorCodeMissingModule]
599599
from defusedxml import xyz # E: Cannot find implementation or library stub for module named "defusedxml" [import]
600600
from nonexistent import foobar # E: Cannot find implementation or library stub for module named "nonexistent" [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
601+
import nonexistent2 # E: Cannot find implementation or library stub for module named "nonexistent2" [import]
603602
from nonexistent3 import * # E: Cannot find implementation or library stub for module named "nonexistent3" [import]
604603
from pkg import bad # E: Module "pkg" has no attribute "bad" [attr-defined]
605-
from pkg.bad2 import bad3 # E: Cannot find implementation or library stub for module named "pkg.bad2" [import]
604+
from pkg.bad2 import bad3 # E: Cannot find implementation or library stub for module named "pkg.bad2" [import] \
605+
# N: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
606606
[file pkg/__init__.py]
607607

608608
[case testErrorCodeAlreadyDefined]

test-data/unit/check-modules.test

+2-2
Original file line numberDiff line numberDiff line change
@@ -913,10 +913,10 @@ a.b.c.value
913913
[file a/b/c.py]
914914
value = 3.2
915915
[out]
916-
tmp/a/b/__init__.py:2: error: Name "c" is not defined
917-
tmp/a/b/__init__.py:3: error: Name "a" is not defined
918916
tmp/a/__init__.py:2: error: Name "b" is not defined
919917
tmp/a/__init__.py:3: error: Name "a" is not defined
918+
tmp/a/b/__init__.py:2: error: Name "c" is not defined
919+
tmp/a/b/__init__.py:3: error: Name "a" is not defined
920920

921921
[case testSubmoduleMixingLocalAndQualifiedNames]
922922
from a.b import MyClass

test-data/unit/cmdline.test

+4-4
Original file line numberDiff line numberDiff line change
@@ -431,19 +431,19 @@ main.py:4: note: Revealed type is "Any"
431431
[case testConfigFollowImportsError]
432432
# cmd: mypy main.py
433433
[file main.py]
434-
from a import x
434+
from a import x # Error reported here
435435
reveal_type(x) # Expect Any
436-
import a # Error reported here
436+
import a
437437
reveal_type(a.x) # Expect Any
438438
[file mypy.ini]
439439
\[mypy]
440440
follow_imports = error
441441
[file a.py]
442442
/ # No error reported
443443
[out]
444+
main.py:1: error: Import of "a" ignored
445+
main.py:1: note: (Using --follow-imports=error, module not passed on command line)
444446
main.py:2: note: Revealed type is "Any"
445-
main.py:3: error: Import of "a" ignored
446-
main.py:3: note: (Using --follow-imports=error, module not passed on command line)
447447
main.py:4: note: Revealed type is "Any"
448448

449449
[case testConfigFollowImportsSelective]

test-data/unit/fine-grained-follow-imports.test

+1-1
Original file line numberDiff line numberDiff line change
@@ -530,8 +530,8 @@ def f() -> None: pass
530530
[out]
531531
main.py:1: error: Cannot find implementation or library stub for module named "p1.s1.m"
532532
main.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
533-
main.py:1: error: Cannot find implementation or library stub for module named "p1"
534533
main.py:1: error: Cannot find implementation or library stub for module named "p1.s1"
534+
main.py:1: error: Cannot find implementation or library stub for module named "p1"
535535
main.py:2: error: Cannot find implementation or library stub for module named "p2.s2"
536536
==
537537
main.py:2: error: Cannot find implementation or library stub for module named "p2.s2"

test-data/unit/pep561.test

+11-1
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,19 @@ testNamespacePkgWStubsWithNamespacePackagesFlag.py:8: error: Argument 1 to "bf"
207207

208208
[case testTypedPkgNamespaceRegFromImportTwiceMissing]
209209
# pkgs: typedpkg_ns_a
210-
from typedpkg_ns import b # type: ignore
210+
from typedpkg_ns import does_not_exist # type: ignore
211211
from typedpkg_ns import a
212212
-- dummy should trigger a second iteration
213213
[file dummy.py.2]
214214
[out]
215215
[out2]
216+
217+
218+
[case testTypedPkgNamespaceRegFromImportTwiceMissing2]
219+
# pkgs: typedpkg_ns_a
220+
from typedpkg_ns import does_not_exist # type: ignore
221+
from typedpkg_ns.a.bbb import bf
222+
-- dummy should trigger a second iteration
223+
[file dummy.py.2]
224+
[out]
225+
[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
296297
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)