From 6858cbead66f11a5d588358d7baaf45c836b10ac Mon Sep 17 00:00:00 2001 From: Simon Breidenbach Date: Tue, 4 Aug 2026 14:04:22 +0200 Subject: [PATCH 1/2] fix(iwyu): suggest C headers instead of C++ ones for C files For C files (.c/.cu or LINT_C_FILE), build/include_what_you_use suggested the C++ C-library header (e.g. for printf). It now suggests the C equivalent (). The C-file determination is factored into _IsCFile (single source, shared by ProcessGlobalSuppressions and the IWYU check) and threaded into CheckForIncludeWhatYouUse as an is_c_file parameter rather than a module global. A whitelist maps the C++ C-library headers to their C counterparts, so non-C-library headers and C++ files are unaffected. Fixes #399 --- CHANGELOG.rst | 1 + cpplint.py | 61 ++++++++++++++++++++++++++++++++++++++++----- cpplint_unittest.py | 32 +++++++++++++++++++++--- 3 files changed, 84 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c8429e9..343d0be 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,7 @@ TBA * Fixed a whitespace/newline false positive for control conditions containing lambdas. (#410) * We now error on relative include paths (``./``, ``../``). (#432) +* For C files, build/include_what_you_use now suggests the C header (e.g. ````) instead of its C++ counterpart (e.g. ````). (#399) * This makes ``#include "./foo.h"`` produce two separate errors: that foo.cpp should include foo.h and that relative paths are not allowed. 2.0.2 (2025-04-08) diff --git a/cpplint.py b/cpplint.py index 41c3bb0..2fc5ccd 100755 --- a/cpplint.py +++ b/cpplint.py @@ -916,6 +916,45 @@ r"vim?:\s*.*(\s*|:)filetype=c(\s*|:|$))" ) +# Maps the C++ C-library headers to their C equivalents so that, for C files, +# build/include_what_you_use suggests the C header (e.g. rather than +# ). See https://github.com/cpplint/cpplint/issues/399. +_C_LIBRARY_CPP_HEADERS = { + "": "", + "": "", + "": "", + "": "", + "": "", + "": "", + "": "", + "": "", + "": "", + "": "", + "": "", + "": "", + "": "", + "": "", + "": "", + "": "", + "": "", + "": "", + "": "", + "": "", + "": "", +} + + +def _IsCFile(filename: str, lines: list[str]) -> bool: + """Whether the file is a C file: a .c/.cu extension or a LINT_C_FILE marker. + + Single source of truth shared by the C-specific error suppression and the + C-vs-C++ header suggestion, so the two never drift. See #399. + """ + return filename.lower().endswith((".c", ".cu")) or any( + _SEARCH_C_FILE.search(line) for line in lines + ) + + # Match string that indicates we're working on a Linux Kernel file. _SEARCH_KERNEL_FILE = re.compile(r"\b(?:LINT_KERNEL_FILE)") @@ -1206,10 +1245,10 @@ def ProcessGlobalSuppressions(filename: str, lines: list[str]) -> None: last element being empty if the file is terminated with a newline. filename: str, the name of the input file. """ + if _IsCFile(filename, lines): + for category in _DEFAULT_C_SUPPRESSED_CATEGORIES: + _error_suppressions.AddGlobalSuppression(category) for line in lines: - if _SEARCH_C_FILE.search(line) or filename.lower().endswith((".c", ".cu")): - for category in _DEFAULT_C_SUPPRESSED_CATEGORIES: - _error_suppressions.AddGlobalSuppression(category) if _SEARCH_KERNEL_FILE.search(line): for category in _DEFAULT_KERNEL_SUPPRESSED_CATEGORIES: _error_suppressions.AddGlobalSuppression(category) @@ -7119,7 +7158,7 @@ def FilesBelongToSameModule(filename_cc, filename_h): return files_belong_to_same_module, common_path -def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, io=codecs): +def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, io=codecs, is_c_file=False): """Reports for missing stl includes. This function will output warnings to make sure you are including the headers @@ -7183,12 +7222,19 @@ def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, io=co if header_stripped not in include_dict and not ( header_stripped[0] == "c" and (header_stripped[1:] + ".h") in include_dict ): + # For C files, suggest the C header (e.g. ) rather than + # the C++ one (e.g. ). See #399. + suggested = ( + _C_LIBRARY_CPP_HEADERS[header] + if is_c_file and header in _C_LIBRARY_CPP_HEADERS + else header + ) error( filename, required[header][0], "build/include_what_you_use", 4, - "Add #include " + header + " for " + template, + "Add #include " + suggested + " for " + template, ) @@ -7578,7 +7624,10 @@ def ProcessFileData(filename, file_extension, lines, error, extra_check_function "NONLINT block never ended", ) - CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error) + is_c_file = _IsCFile(filename, clean_lines.raw_lines) + CheckForIncludeWhatYouUse( + filename, clean_lines, include_state, error, is_c_file=is_c_file + ) # Check that the .cc file has included its header if it exists. if _IsSourceExtension(file_extension): diff --git a/cpplint_unittest.py b/cpplint_unittest.py index 032ecc5..3814ac8 100755 --- a/cpplint_unittest.py +++ b/cpplint_unittest.py @@ -223,7 +223,8 @@ def PerformIncludeWhatYouUse(self, code, filename="foo.h", io=codecs): error_collector = ErrorCollector(self.assertTrue) include_state = cpplint._IncludeState() nesting_state = cpplint.NestingState() - lines = code.split("\n") + raw_lines = code.split("\n") + lines = raw_lines cpplint.RemoveMultiLineComments(filename, lines, error_collector) lines = cpplint.CleansedLines(lines) for i in range(lines.NumLines()): @@ -235,7 +236,10 @@ def PerformIncludeWhatYouUse(self, code, filename="foo.h", io=codecs): # have language problems. # Second, look for missing includes. - cpplint.CheckForIncludeWhatYouUse(filename, lines, include_state, error_collector, io) + is_c_file = cpplint._IsCFile(filename, raw_lines) + cpplint.CheckForIncludeWhatYouUse( + filename, lines, include_state, error_collector, io, is_c_file=is_c_file + ) return error_collector.Results() # Perform lint and make sure one of the errors is what we want @@ -259,8 +263,8 @@ def TestMultiLineLintRE(self, code, expected_message_re): def TestLanguageRulesCheck(self, file_name, code, expected_message): assert expected_message == self.PerformLanguageRulesCheck(file_name, code) - def TestIncludeWhatYouUse(self, code, expected_message): - assert expected_message == self.PerformIncludeWhatYouUse(code) + def TestIncludeWhatYouUse(self, code, expected_message, filename="foo.h"): + assert expected_message == self.PerformIncludeWhatYouUse(code, filename=filename) def TestBlankLinesCheck(self, lines, start_errors, end_errors): for extension in ["c", "cc", "cpp", "cxx", "c++", "cu"]: @@ -1290,6 +1294,26 @@ def testIncludeWhatYouUse(self): printf("hello world");""", "", ) # Avoid false positives w/ c-style include + # C files should be told to include the C header (e.g. ) rather + # than its C++ counterpart (e.g. ). See #399. + self.TestIncludeWhatYouUse( + 'printf("hello world");', + "Add #include for printf [build/include_what_you_use] [4]", + filename="foo.c", + ) + # C++ files are unaffected. + self.TestIncludeWhatYouUse( + 'printf("hello world");', + "Add #include for printf [build/include_what_you_use] [4]", + filename="foo.cpp", + ) + # The "C header already included" behaviour is preserved for C files. + self.TestIncludeWhatYouUse( + """#include + printf("hello world");""", + "", + filename="foo.c", + ) self.TestIncludeWhatYouUse( "void a(const string &foobar);", "Add #include for string [build/include_what_you_use] [4]", From 7914485ddcce8227ef2e001423069ae2512857f6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 12:51:57 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- cpplint.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cpplint.py b/cpplint.py index 2fc5ccd..ba44320 100755 --- a/cpplint.py +++ b/cpplint.py @@ -7158,7 +7158,9 @@ def FilesBelongToSameModule(filename_cc, filename_h): return files_belong_to_same_module, common_path -def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, io=codecs, is_c_file=False): +def CheckForIncludeWhatYouUse( + filename, clean_lines, include_state, error, io=codecs, is_c_file=False +): """Reports for missing stl includes. This function will output warnings to make sure you are including the headers @@ -7625,9 +7627,7 @@ def ProcessFileData(filename, file_extension, lines, error, extra_check_function ) is_c_file = _IsCFile(filename, clean_lines.raw_lines) - CheckForIncludeWhatYouUse( - filename, clean_lines, include_state, error, is_c_file=is_c_file - ) + CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, is_c_file=is_c_file) # Check that the .cc file has included its header if it exists. if _IsSourceExtension(file_extension):