diff --git a/mypy/build.py b/mypy/build.py index 8a558b0e7b70..c7e307fdfbed 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -1884,6 +1884,25 @@ def __init__(self, self.child_modules = set(self.meta.child_modules) if temporary: self.load_tree(temporary=True) + if not manager.use_fine_grained_cache(): + # Special case: if there were a previously missing package imported here + # and it is not present, then we need to re-calculate dependencies. + # This is to support patterns like this: + # from missing_package import missing_module # type: ignore + # At first mypy doesn't know that `missing_module` is a module + # (it may be a variable, a class, or a function), so it is not added to + # suppressed dependencies. Therefore, when the package with module is added, + # we need to re-calculate dependencies. + # NOTE: see comment below for why we skip this in fine grained mode. + new_packages = False + for dep in self.suppressed: + path = manager.find_module_cache.find_module(dep, manager.search_paths, + manager.options.python_executable) + if path and '__init__.py' in path: + new_packages = True + if new_packages: + self.parse_file() # This is safe because the cache is anyway stale. + self.compute_dependencies() else: # When doing a fine-grained cache load, pretend we only # know about modules that have cache information and defer @@ -2696,12 +2715,15 @@ def load_graph(sources: List[BuildSource], manager: BuildManager, # (since direct dependencies reflect the imports found in the source) # but A's cached *indirect* dependency on C is wrong. dependencies = [dep for dep in st.dependencies if st.priorities.get(dep) != PRI_INDIRECT] + added = [dep for dep in st.suppressed + if manager.find_module_cache.find_module(dep, manager.search_paths, + manager.options.python_executable)] for dep in st.ancestors + dependencies + st.suppressed: # We don't want to recheck imports marked with '# type: ignore' # so we ignore any suppressed module not explicitly re-included # from the command line. ignored = dep in st.suppressed and dep not in entry_points - if ignored: + if ignored and dep not in added: manager.missing_modules.add(dep) elif dep not in graph: try: diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index dfe03dcfbf76..926e05d249da 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -4790,3 +4790,97 @@ tmp/c.py:1: note: (Perhaps setting MYPYPATH or using the "--ignore-missing-impor [out2] tmp/c.py:1: error: Cannot find module named 'a.b.c' tmp/c.py:1: note: (Perhaps setting MYPYPATH or using the "--ignore-missing-imports" flag would help) + +[case testAddedMissingStubs] +# flags: --ignore-missing-imports +from missing import f +f(int()) +[file missing.pyi.2] +def f(x: str) -> None: pass +[out] +[out2] +main:3: error: Argument 1 to "f" has incompatible type "int"; expected "str" + +[case testAddedMissingStubsPackage] +# flags: --ignore-missing-imports +import package.missing +package.missing.f(int()) +[file package/__init__.pyi.2] +[file package/missing.pyi.2] +def f(x: str) -> None: pass +[out] +[out2] +main:3: error: Argument 1 to "f" has incompatible type "int"; expected "str" + +[case testAddedMissingStubsPackageFrom] +# flags: --ignore-missing-imports +from package import missing +missing.f(int()) +[file package/__init__.pyi.2] +[file package/missing.pyi.2] +def f(x: str) -> None: pass +[out] +[out2] +main:3: error: Argument 1 to "f" has incompatible type "int"; expected "str" + +[case testAddedMissingStubsPackagePartial] +# flags: --ignore-missing-imports +import package.missing +package.missing.f(int()) +[file package/__init__.pyi] +[file package/missing.pyi.2] +def f(x: str) -> None: pass +[out] +[out2] +main:3: error: Argument 1 to "f" has incompatible type "int"; expected "str" + +[case testAddedMissingStubsPackagePartialGetAttr] +import package.missing +package.missing.f(int()) +[file package/__init__.pyi] +from typing import Any +def __getattr__(attr: str) -> Any: ... +[file package/missing.pyi.2] +def f(x: str) -> None: pass +[out] +[out2] +main:2: error: Argument 1 to "f" has incompatible type "int"; expected "str" + +[case testAddedMissingStubsIgnore] +from missing import f # type: ignore +f(int()) +[file missing.pyi.2] +def f(x: str) -> None: pass +[out] +[out2] +main:2: error: Argument 1 to "f" has incompatible type "int"; expected "str" + +[case testAddedMissingStubsIgnorePackage] +import package.missing # type: ignore +package.missing.f(int()) +[file package/__init__.pyi.2] +[file package/missing.pyi.2] +def f(x: str) -> None: pass +[out] +[out2] +main:2: error: Argument 1 to "f" has incompatible type "int"; expected "str" + +[case testAddedMissingStubsIgnorePackageFrom] +from package import missing # type: ignore +missing.f(int()) +[file package/__init__.pyi.2] +[file package/missing.pyi.2] +def f(x: str) -> None: pass +[out] +[out2] +main:2: error: Argument 1 to "f" has incompatible type "int"; expected "str" + +[case testAddedMissingStubsIgnorePackagePartial] +import package.missing # type: ignore +package.missing.f(int()) +[file package/__init__.pyi] +[file package/missing.pyi.2] +def f(x: str) -> None: pass +[out] +[out2] +main:2: error: Argument 1 to "f" has incompatible type "int"; expected "str"