Skip to content

Commit

Permalink
Fix daemon false positives related to module-level __getattr__ (#16292)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
JukkaL authored Oct 19, 2023
1 parent e1f6d6b commit 1c218ea
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 8 deletions.
10 changes: 6 additions & 4 deletions mypy/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -1991,7 +1991,7 @@ def __init__(
raise ModuleNotFound

# Parse the file (and then some) to get the dependencies.
self.parse_file()
self.parse_file(temporary=temporary)
self.compute_dependencies()

@property
Expand Down Expand Up @@ -2109,7 +2109,7 @@ def fix_cross_refs(self) -> None:

# Methods for processing modules from source code.

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

modules[self.id] = self.tree
if not temporary:
modules[self.id] = self.tree

if not cached:
self.semantic_analysis_pass1()

self.check_blockers()
if not temporary:
self.check_blockers()

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

Expand Down
6 changes: 2 additions & 4 deletions test-data/unit/fine-grained-modules.test
Original file line number Diff line number Diff line change
Expand Up @@ -837,15 +837,13 @@ p.a.f(1)
[file p/__init__.py]
[file p/a.py]
def f(x: str) -> None: pass
[delete p/__init__.py.2]
[delete p/a.py.2]
def f(x: str) -> None: pass
[delete p.2]
[out]
main:2: error: Argument 1 to "f" has incompatible type "int"; expected "str"
==
main:1: error: Cannot find implementation or library stub for module named "p.a"
main:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
main:2: error: "object" has no attribute "a"
main:1: error: Cannot find implementation or library stub for module named "p"

[case testDeletePackage2]
import p
Expand Down
27 changes: 27 additions & 0 deletions test-data/unit/fine-grained.test
Original file line number Diff line number Diff line change
Expand Up @@ -10337,3 +10337,30 @@ b.py:1: note: Use "-> None" if function does not return a value
==
a.py:1: error: Function is missing a return type annotation
a.py:1: note: Use "-> None" if function does not return a value

[case testModuleLevelGetAttrInStub]
import stub
import a
import b

[file stub/__init__.pyi]
s: str
def __getattr__(self): pass

[file a.py]

[file a.py.2]
from stub import x
from stub.pkg import y
from stub.pkg.sub import z

[file b.py]

[file b.py.3]
from stub import s
reveal_type(s)

[out]
==
==
b.py:2: note: Revealed type is "builtins.str"

0 comments on commit 1c218ea

Please sign in to comment.