Skip to content

Commit 1c218ea

Browse files
authored
Fix daemon false positives related to module-level __getattr__ (#16292)
In some cases, mypy daemon could generate false positives about imports targeting packages with a module-level `__getattr__` methods. The root cause was that the `mypy.build.in_partial_package` function would leave a partially initialized module in the `modules` dictionary of `BuildManager`, which could probably cause all sorts of confusion. I fixed this by making sure that ASTs related to temporary `State` objects don't get persisted. Also updated a test case to properly delete a package -- an empty directory is now actually a valid namespace package, so to delete a package we should delete the directory, not just the files inside it.
1 parent e1f6d6b commit 1c218ea

File tree

3 files changed

+35
-8
lines changed

3 files changed

+35
-8
lines changed

mypy/build.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,7 +1991,7 @@ def __init__(
19911991
raise ModuleNotFound
19921992

19931993
# Parse the file (and then some) to get the dependencies.
1994-
self.parse_file()
1994+
self.parse_file(temporary=temporary)
19951995
self.compute_dependencies()
19961996

19971997
@property
@@ -2109,7 +2109,7 @@ def fix_cross_refs(self) -> None:
21092109

21102110
# Methods for processing modules from source code.
21112111

2112-
def parse_file(self) -> None:
2112+
def parse_file(self, *, temporary: bool = False) -> None:
21132113
"""Parse file and run first pass of semantic analysis.
21142114
21152115
Everything done here is local to the file. Don't depend on imported
@@ -2194,12 +2194,14 @@ def parse_file(self) -> None:
21942194
else:
21952195
self.early_errors = manager.ast_cache[self.id][1]
21962196

2197-
modules[self.id] = self.tree
2197+
if not temporary:
2198+
modules[self.id] = self.tree
21982199

21992200
if not cached:
22002201
self.semantic_analysis_pass1()
22012202

2202-
self.check_blockers()
2203+
if not temporary:
2204+
self.check_blockers()
22032205

22042206
manager.ast_cache[self.id] = (self.tree, self.early_errors)
22052207

test-data/unit/fine-grained-modules.test

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -837,15 +837,13 @@ p.a.f(1)
837837
[file p/__init__.py]
838838
[file p/a.py]
839839
def f(x: str) -> None: pass
840-
[delete p/__init__.py.2]
841-
[delete p/a.py.2]
842-
def f(x: str) -> None: pass
840+
[delete p.2]
843841
[out]
844842
main:2: error: Argument 1 to "f" has incompatible type "int"; expected "str"
845843
==
846844
main:1: error: Cannot find implementation or library stub for module named "p.a"
847845
main:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
848-
main:2: error: "object" has no attribute "a"
846+
main:1: error: Cannot find implementation or library stub for module named "p"
849847

850848
[case testDeletePackage2]
851849
import p

test-data/unit/fine-grained.test

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10337,3 +10337,30 @@ b.py:1: note: Use "-> None" if function does not return a value
1033710337
==
1033810338
a.py:1: error: Function is missing a return type annotation
1033910339
a.py:1: note: Use "-> None" if function does not return a value
10340+
10341+
[case testModuleLevelGetAttrInStub]
10342+
import stub
10343+
import a
10344+
import b
10345+
10346+
[file stub/__init__.pyi]
10347+
s: str
10348+
def __getattr__(self): pass
10349+
10350+
[file a.py]
10351+
10352+
[file a.py.2]
10353+
from stub import x
10354+
from stub.pkg import y
10355+
from stub.pkg.sub import z
10356+
10357+
[file b.py]
10358+
10359+
[file b.py.3]
10360+
from stub import s
10361+
reveal_type(s)
10362+
10363+
[out]
10364+
==
10365+
==
10366+
b.py:2: note: Revealed type is "builtins.str"

0 commit comments

Comments
 (0)