From 86f3e7aa59797dd3739ab7fd301209f12b6fcb01 Mon Sep 17 00:00:00 2001 From: Thibaut Decombe Date: Thu, 6 Aug 2026 11:08:28 +0200 Subject: [PATCH] Fix dmypy re-adding modules excluded by per-module follow_imports=skip Fixes #16190 --- mypy/build.py | 14 +++++++--- mypy/dmypy_server.py | 5 ++-- .../unit/fine-grained-follow-imports.test | 27 +++++++++++++++++++ 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 96a67105c816c..716a97cdcacaf 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -3806,6 +3806,16 @@ def find_module_and_diagnose( raise ModuleNotFound +def excluded_by_follow_imports(path: str, options: Options) -> bool: + """Check if the module at path is excluded from build by follow-imports=skip/error. + + Stubs are only excluded if follow_imports_for_stubs is set. + """ + return options.follow_imports in ("skip", "error") and ( + not path.endswith(".pyi") or options.follow_imports_for_stubs + ) + + def exist_added_packages(suppressed: list[str], manager: BuildManager) -> bool: """Find if there are any newly added packages that were previously suppressed. @@ -3829,9 +3839,7 @@ def exist_added_packages(suppressed: list[str], manager: BuildManager) -> bool: # follow-imports = normal # But such cases are extremely rare, and this allows us to avoid # massive performance impact in much more common situations. - if options.follow_imports in ("skip", "error") and ( - not path.endswith(".pyi") or options.follow_imports_for_stubs - ): + if excluded_by_follow_imports(path, options): continue if os.path.basename(path) in ("__init__.py", "__init__.pyi"): return True diff --git a/mypy/dmypy_server.py b/mypy/dmypy_server.py index 066fbf9bed2d6..6707ebbdce309 100644 --- a/mypy/dmypy_server.py +++ b/mypy/dmypy_server.py @@ -825,8 +825,9 @@ def find_added_suppressed( continue result = finder.find_module(module, fast_path=True) if isinstance(result, str) and module not in seen: - # When not following imports, we only follow imports to .pyi files. - if not self.following_imports() and not result.endswith(".pyi"): + if mypy.build.excluded_by_follow_imports( + result, self.options.clone_for_module(module) + ): continue found.append((module, result)) seen.add(module) diff --git a/test-data/unit/fine-grained-follow-imports.test b/test-data/unit/fine-grained-follow-imports.test index d716a57123dcc..aad3ef2ab427f 100644 --- a/test-data/unit/fine-grained-follow-imports.test +++ b/test-data/unit/fine-grained-follow-imports.test @@ -846,3 +846,30 @@ class A: ... [typing fixtures/typing-typeddict.pyi] [out] == + +[case testFollowImportsPerModuleSkipNotAddedBack] +# flags: --follow-imports=normal +# cmd: mypy main.py + +[file mypy.ini] +\[mypy] +follow_imports = normal +\[mypy-pkg.sub] +follow_imports = skip + +[file main.py] +import pkg.sub +reveal_type(pkg.sub.x) + +[file pkg/__init__.py] + +[file pkg/sub.py] +x = 1 + +[file unrelated.py.2] +# Trigger a second increment with no relevant changes. + +[out] +main.py:2: note: Revealed type is "Any" +== +main.py:2: note: Revealed type is "Any"