From 50952aa93c7dd00aa888ad9c20101a295b4c47a0 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 13 Feb 2026 13:29:25 +0000 Subject: [PATCH] Add back support for warn_unused_configs --- mypy/build.py | 22 +++++++++--- mypy/ipc.py | 5 ++- mypy/main.py | 27 +------------- test-data/unit/check-incremental.test | 52 +++++++++++++++++++++++++++ test-data/unit/cmdline.test | 3 +- 5 files changed, 74 insertions(+), 35 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index e12db1a701b14..37cf423ccccde 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -131,7 +131,7 @@ from mypy.report import Reports # Avoid unconditional slow import from mypy import errorcodes as codes -from mypy.config_parser import parse_mypy_comments +from mypy.config_parser import get_config_module_names, parse_mypy_comments from mypy.fixup import fixup_module from mypy.freetree import free_tree from mypy.fscache import FileSystemCache @@ -472,6 +472,7 @@ def build_inner( dump_timing_stats(options.timing_stats, graph) if options.line_checking_stats is not None: dump_line_checking_stats(options.line_checking_stats, graph) + warn_unused_configs(options, flush_errors) return BuildResult(manager, graph) finally: t0 = time.time() @@ -496,6 +497,19 @@ def build_inner( record_missing_stub_packages(options.cache_dir, manager.missing_stub_packages) +def warn_unused_configs( + options: Options, flush_errors: Callable[[str | None, list[str], bool], None] +) -> None: + if options.warn_unused_configs and options.unused_configs and not options.non_interactive: + unused = get_config_module_names( + options.config_file, + [glob for glob in options.per_module_options.keys() if glob in options.unused_configs], + ) + flush_errors( + None, ["{}: note: unused section(s): {}".format(options.config_file, unused)], False + ) + + def default_data_dir() -> str: """Returns directory containing typeshed directory.""" return os.path.dirname(__file__) @@ -3783,7 +3797,7 @@ def load_graph( manager.missing_modules.add(dep) # TODO: for now we skip this in the daemon as a performance optimization. # This however creates a correctness issue, see #7777 and State.is_fresh(). - if not manager.use_fine_grained_cache(): + if not manager.use_fine_grained_cache() or manager.options.warn_unused_configs: manager.import_options[dep] = manager.options.clone_for_module( dep ).dep_import_options() @@ -3841,8 +3855,8 @@ def load_graph( graph[newst.id] = newst new.append(newst) # There are two things we need to do after the initial load loop. One is up-suppress - # modules that are back in graph. We need to do this after the loop to cover an edge - # case where a namespace package ancestor is shared by a typed and an untyped package. + # modules that are back in graph. We need to do this after the loop to cover edge cases + # like where a namespace package ancestor is shared by a typed and an untyped package. for st in graph.values(): for dep in st.suppressed: if dep in graph: diff --git a/mypy/ipc.py b/mypy/ipc.py index e21171d46b0f5..dd4637d3989c8 100644 --- a/mypy/ipc.py +++ b/mypy/ipc.py @@ -48,9 +48,8 @@ class IPCBase: This contains logic shared between the client and server, such as reading and writing. We want to be able to send multiple "messages" over a single connection and - to be able to separate the messages. We do this by encoding the messages - in an alphabet that does not contain spaces, then adding a space for - separation. The last framed message is also followed by a space. + to be able to separate the messages. We do this by prefixing each message + with its size in a fixed format. """ connection: _IPCHandle diff --git a/mypy/main.py b/mypy/main.py index 23953c606d6ad..acc27f1806b78 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -23,12 +23,7 @@ sys.exit(2) from mypy import build, defaults, state, util -from mypy.config_parser import ( - get_config_module_names, - parse_config_file, - parse_version, - validate_package_allow_list, -) +from mypy.config_parser import parse_config_file, parse_version, validate_package_allow_list from mypy.defaults import RECURSION_LIMIT from mypy.error_formatter import OUTPUT_CHOICES from mypy.errors import CompileError @@ -221,26 +216,6 @@ def flush_errors(filename: str | None, new_messages: list[str], serious: bool) - blockers = True if not e.use_stdout: serious = True - if ( - options.warn_unused_configs - and options.unused_configs - and not options.incremental - and not options.non_interactive - ): - print( - "Warning: unused section(s) in {}: {}".format( - options.config_file, - get_config_module_names( - options.config_file, - [ - glob - for glob in options.per_module_options.keys() - if glob in options.unused_configs - ], - ), - ), - file=stderr, - ) maybe_write_junit_xml(time.time() - t0, serious, messages, messages_by_file, options) return res, messages, blockers diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 4deb005063265..ee68a91635497 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -7993,3 +7993,55 @@ from a import b # type: ignore[attr-defined] [out] main:2: error: Unused "type: ignore" comment [out2] + +[case testConfigWarnUnusedSectionIncremental] +# flags: --config-file=tmp/mypy.ini +import m +[file m.py] +import a +[file m.py.2] +import a # touch +[file a.py] +import foo # type: ignore +[file mypy.ini] +\[mypy] +warn_unused_configs = true +\[mypy-bar] +\[mypy-foo] +[file mypy.ini.3] +\[mypy] +warn_unused_configs = true +\[mypy-foo] +[out] +tmp/mypy.ini: note: unused section(s): [mypy-bar] +[out2] +tmp/mypy.ini: note: unused section(s): [mypy-bar] +[out3] + +[case testConfigWarnUnusedSectionIncrementalTOML] +# flags: --config-file tmp/pyproject.toml +import m +[file m.py] +import a +[file m.py.2] +import a # touch +[file a.py] +import foo # type: ignore +[file pyproject.toml] +\[tool.mypy] +warn_unused_configs = true +\[[tool.mypy.overrides]] +module = "bar" +\[[tool.mypy.overrides]] +module = "foo" +warn_unreachable = true +[file pyproject.toml.3] +\[tool.mypy] +warn_unused_configs = true +\[[tool.mypy.overrides]] +module = "foo" +[out] +tmp/pyproject.toml: note: unused section(s): module = ['bar'] +[out2] +tmp/pyproject.toml: note: unused section(s): module = ['bar'] +[out3] diff --git a/test-data/unit/cmdline.test b/test-data/unit/cmdline.test index 180f6e2efec97..1a678a4f540ea 100644 --- a/test-data/unit/cmdline.test +++ b/test-data/unit/cmdline.test @@ -529,7 +529,6 @@ variable has type "bytes") [file mypy.ini] \[mypy] warn_unused_configs = True -incremental = False \[mypy-bar] \[mypy-foo] \[mypy-baz.*] @@ -549,7 +548,7 @@ incremental = False [file spam/__init__.py] [file spam/eggs.py] [out] -Warning: unused section(s) in mypy.ini: [mypy-bar], [mypy-baz.*], [mypy-emarg.*], [mypy-emarg.hatch], [mypy-a.*.c], [mypy-a.x.b] +mypy.ini: note: unused section(s): [mypy-bar], [mypy-baz.*], [mypy-emarg.*], [mypy-emarg.hatch], [mypy-a.*.c], [mypy-a.x.b] == Return code: 0 [case testPackageRootEmpty]