diff --git a/mypy/build.py b/mypy/build.py index 11d826cf9e9f..a0f80f365722 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 @@ -478,6 +478,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() @@ -502,6 +503,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__) @@ -3774,7 +3788,7 @@ def load_graph( manager.missing_modules[dep] = SuppressionReason.NOT_FOUND # 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() @@ -3824,8 +3838,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.copy(): if dep in graph: diff --git a/mypy/ipc.py b/mypy/ipc.py index 0bafc2f7a474..29710cd57a91 100644 --- a/mypy/ipc.py +++ b/mypy/ipc.py @@ -53,9 +53,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 23953c606d6a..acc27f1806b7 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 18df73a95764..bc9a61bae782 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -7929,6 +7929,58 @@ from a import b # type: ignore[attr-defined] 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] + [case testAddedMissingModuleSkip] # flags: --follow-imports=skip import mod diff --git a/test-data/unit/cmdline.test b/test-data/unit/cmdline.test index 2b3f48fec4a0..0acba23abea0 100644 --- a/test-data/unit/cmdline.test +++ b/test-data/unit/cmdline.test @@ -535,7 +535,6 @@ variable has type "bytes") [file mypy.ini] \[mypy] warn_unused_configs = True -incremental = False \[mypy-bar] \[mypy-foo] \[mypy-baz.*] @@ -555,7 +554,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]