Skip to content

Commit e402c13

Browse files
authored
Invalidate cache when previously missing stubs are added (#5465)
Fixes #1910 Fixes #5101
1 parent e236664 commit e402c13

File tree

2 files changed

+117
-1
lines changed

2 files changed

+117
-1
lines changed

mypy/build.py

+23-1
Original file line numberDiff line numberDiff line change
@@ -1884,6 +1884,25 @@ def __init__(self,
18841884
self.child_modules = set(self.meta.child_modules)
18851885
if temporary:
18861886
self.load_tree(temporary=True)
1887+
if not manager.use_fine_grained_cache():
1888+
# Special case: if there were a previously missing package imported here
1889+
# and it is not present, then we need to re-calculate dependencies.
1890+
# This is to support patterns like this:
1891+
# from missing_package import missing_module # type: ignore
1892+
# At first mypy doesn't know that `missing_module` is a module
1893+
# (it may be a variable, a class, or a function), so it is not added to
1894+
# suppressed dependencies. Therefore, when the package with module is added,
1895+
# we need to re-calculate dependencies.
1896+
# NOTE: see comment below for why we skip this in fine grained mode.
1897+
new_packages = False
1898+
for dep in self.suppressed:
1899+
path = manager.find_module_cache.find_module(dep, manager.search_paths,
1900+
manager.options.python_executable)
1901+
if path and '__init__.py' in path:
1902+
new_packages = True
1903+
if new_packages:
1904+
self.parse_file() # This is safe because the cache is anyway stale.
1905+
self.compute_dependencies()
18871906
else:
18881907
# When doing a fine-grained cache load, pretend we only
18891908
# know about modules that have cache information and defer
@@ -2696,12 +2715,15 @@ def load_graph(sources: List[BuildSource], manager: BuildManager,
26962715
# (since direct dependencies reflect the imports found in the source)
26972716
# but A's cached *indirect* dependency on C is wrong.
26982717
dependencies = [dep for dep in st.dependencies if st.priorities.get(dep) != PRI_INDIRECT]
2718+
added = [dep for dep in st.suppressed
2719+
if manager.find_module_cache.find_module(dep, manager.search_paths,
2720+
manager.options.python_executable)]
26992721
for dep in st.ancestors + dependencies + st.suppressed:
27002722
# We don't want to recheck imports marked with '# type: ignore'
27012723
# so we ignore any suppressed module not explicitly re-included
27022724
# from the command line.
27032725
ignored = dep in st.suppressed and dep not in entry_points
2704-
if ignored:
2726+
if ignored and dep not in added:
27052727
manager.missing_modules.add(dep)
27062728
elif dep not in graph:
27072729
try:

test-data/unit/check-incremental.test

+94
Original file line numberDiff line numberDiff line change
@@ -4790,3 +4790,97 @@ tmp/c.py:1: note: (Perhaps setting MYPYPATH or using the "--ignore-missing-impor
47904790
[out2]
47914791
tmp/c.py:1: error: Cannot find module named 'a.b.c'
47924792
tmp/c.py:1: note: (Perhaps setting MYPYPATH or using the "--ignore-missing-imports" flag would help)
4793+
4794+
[case testAddedMissingStubs]
4795+
# flags: --ignore-missing-imports
4796+
from missing import f
4797+
f(int())
4798+
[file missing.pyi.2]
4799+
def f(x: str) -> None: pass
4800+
[out]
4801+
[out2]
4802+
main:3: error: Argument 1 to "f" has incompatible type "int"; expected "str"
4803+
4804+
[case testAddedMissingStubsPackage]
4805+
# flags: --ignore-missing-imports
4806+
import package.missing
4807+
package.missing.f(int())
4808+
[file package/__init__.pyi.2]
4809+
[file package/missing.pyi.2]
4810+
def f(x: str) -> None: pass
4811+
[out]
4812+
[out2]
4813+
main:3: error: Argument 1 to "f" has incompatible type "int"; expected "str"
4814+
4815+
[case testAddedMissingStubsPackageFrom]
4816+
# flags: --ignore-missing-imports
4817+
from package import missing
4818+
missing.f(int())
4819+
[file package/__init__.pyi.2]
4820+
[file package/missing.pyi.2]
4821+
def f(x: str) -> None: pass
4822+
[out]
4823+
[out2]
4824+
main:3: error: Argument 1 to "f" has incompatible type "int"; expected "str"
4825+
4826+
[case testAddedMissingStubsPackagePartial]
4827+
# flags: --ignore-missing-imports
4828+
import package.missing
4829+
package.missing.f(int())
4830+
[file package/__init__.pyi]
4831+
[file package/missing.pyi.2]
4832+
def f(x: str) -> None: pass
4833+
[out]
4834+
[out2]
4835+
main:3: error: Argument 1 to "f" has incompatible type "int"; expected "str"
4836+
4837+
[case testAddedMissingStubsPackagePartialGetAttr]
4838+
import package.missing
4839+
package.missing.f(int())
4840+
[file package/__init__.pyi]
4841+
from typing import Any
4842+
def __getattr__(attr: str) -> Any: ...
4843+
[file package/missing.pyi.2]
4844+
def f(x: str) -> None: pass
4845+
[out]
4846+
[out2]
4847+
main:2: error: Argument 1 to "f" has incompatible type "int"; expected "str"
4848+
4849+
[case testAddedMissingStubsIgnore]
4850+
from missing import f # type: ignore
4851+
f(int())
4852+
[file missing.pyi.2]
4853+
def f(x: str) -> None: pass
4854+
[out]
4855+
[out2]
4856+
main:2: error: Argument 1 to "f" has incompatible type "int"; expected "str"
4857+
4858+
[case testAddedMissingStubsIgnorePackage]
4859+
import package.missing # type: ignore
4860+
package.missing.f(int())
4861+
[file package/__init__.pyi.2]
4862+
[file package/missing.pyi.2]
4863+
def f(x: str) -> None: pass
4864+
[out]
4865+
[out2]
4866+
main:2: error: Argument 1 to "f" has incompatible type "int"; expected "str"
4867+
4868+
[case testAddedMissingStubsIgnorePackageFrom]
4869+
from package import missing # type: ignore
4870+
missing.f(int())
4871+
[file package/__init__.pyi.2]
4872+
[file package/missing.pyi.2]
4873+
def f(x: str) -> None: pass
4874+
[out]
4875+
[out2]
4876+
main:2: error: Argument 1 to "f" has incompatible type "int"; expected "str"
4877+
4878+
[case testAddedMissingStubsIgnorePackagePartial]
4879+
import package.missing # type: ignore
4880+
package.missing.f(int())
4881+
[file package/__init__.pyi]
4882+
[file package/missing.pyi.2]
4883+
def f(x: str) -> None: pass
4884+
[out]
4885+
[out2]
4886+
main:2: error: Argument 1 to "f" has incompatible type "int"; expected "str"

0 commit comments

Comments
 (0)