[clang-tools-extra] 59b39c0 - [clang-tidy] Add new check: `readability-use-concise-preprocessor-directives` (#146830)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Jul 13 09:23:31 PDT 2025
Author: Victor Chernyakin
Date: 2025-07-13T19:23:27+03:00
New Revision: 59b39c0031eded7c46e554b161382187cb2d0ca5
URL: https://github.com/llvm/llvm-project/commit/59b39c0031eded7c46e554b161382187cb2d0ca5
DIFF: https://github.com/llvm/llvm-project/commit/59b39c0031eded7c46e554b161382187cb2d0ca5.diff
LOG: [clang-tidy] Add new check: `readability-use-concise-preprocessor-directives` (#146830)
Closes #132561.
This is a check that rewrites `#if`s and `#elif`s like so:
```cpp
#if defined(MEOW) // -> #ifdef MEOW
#if !defined(MEOW) // -> #ifndef MEOW
```
And, since C23 and C++23:
```cpp
#elif defined(MEOW) // -> #elifdef MEOW
#elif !defined(MEOW) // -> #elifndef MEOW
```
Added:
clang-tools-extra/clang-tidy/readability/UseConcisePreprocessorDirectivesCheck.cpp
clang-tools-extra/clang-tidy/readability/UseConcisePreprocessorDirectivesCheck.h
clang-tools-extra/docs/clang-tidy/checks/readability/use-concise-preprocessor-directives.rst
clang-tools-extra/test/clang-tidy/checkers/readability/use-concise-preprocessor-directives.cpp
Modified:
clang-tools-extra/clang-tidy/readability/CMakeLists.txt
clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
clang-tools-extra/docs/ReleaseNotes.rst
clang-tools-extra/docs/clang-tidy/checks/list.rst
Removed:
################################################################################
diff --git a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt
index 4be1a8f831339..4b4c49d3b17d1 100644
--- a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt
@@ -58,6 +58,7 @@ add_clang_library(clangTidyReadabilityModule STATIC
UniqueptrDeleteReleaseCheck.cpp
UppercaseLiteralSuffixCheck.cpp
UseAnyOfAllOfCheck.cpp
+ UseConcisePreprocessorDirectivesCheck.cpp
UseStdMinMaxCheck.cpp
LINK_LIBS
diff --git a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
index d59b0312673b9..12f8cdb289dd2 100644
--- a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
@@ -61,6 +61,7 @@
#include "UniqueptrDeleteReleaseCheck.h"
#include "UppercaseLiteralSuffixCheck.h"
#include "UseAnyOfAllOfCheck.h"
+#include "UseConcisePreprocessorDirectivesCheck.h"
#include "UseStdMinMaxCheck.h"
namespace clang::tidy {
@@ -173,6 +174,8 @@ class ReadabilityModule : public ClangTidyModule {
"readability-uppercase-literal-suffix");
CheckFactories.registerCheck<UseAnyOfAllOfCheck>(
"readability-use-anyofallof");
+ CheckFactories.registerCheck<UseConcisePreprocessorDirectivesCheck>(
+ "readability-use-concise-preprocessor-directives");
CheckFactories.registerCheck<UseStdMinMaxCheck>(
"readability-use-std-min-max");
}
diff --git a/clang-tools-extra/clang-tidy/readability/UseConcisePreprocessorDirectivesCheck.cpp b/clang-tools-extra/clang-tidy/readability/UseConcisePreprocessorDirectivesCheck.cpp
new file mode 100644
index 0000000000000..05c0088e6b41b
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/readability/UseConcisePreprocessorDirectivesCheck.cpp
@@ -0,0 +1,110 @@
+//===--- UseConcisePreprocessorDirectivesCheck.cpp - clang-tidy -----------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "UseConcisePreprocessorDirectivesCheck.h"
+#include "clang/Basic/TokenKinds.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Lex/PPCallbacks.h"
+#include "clang/Lex/Preprocessor.h"
+
+#include <array>
+
+namespace clang::tidy::readability {
+
+namespace {
+
+class IfPreprocessorCallbacks final : public PPCallbacks {
+public:
+ IfPreprocessorCallbacks(ClangTidyCheck &Check, const Preprocessor &PP)
+ : Check(Check), PP(PP) {}
+
+ void If(SourceLocation Loc, SourceRange ConditionRange,
+ ConditionValueKind) override {
+ impl(Loc, ConditionRange, {"ifdef", "ifndef"});
+ }
+
+ void Elif(SourceLocation Loc, SourceRange ConditionRange, ConditionValueKind,
+ SourceLocation) override {
+ if (PP.getLangOpts().C23 || PP.getLangOpts().CPlusPlus23)
+ impl(Loc, ConditionRange, {"elifdef", "elifndef"});
+ }
+
+private:
+ void impl(SourceLocation DirectiveLoc, SourceRange ConditionRange,
+ const std::array<llvm::StringLiteral, 2> &Replacements) {
+ // Lexer requires its input range to be null-terminated.
+ SmallString<128> Condition =
+ Lexer::getSourceText(CharSourceRange::getTokenRange(ConditionRange),
+ PP.getSourceManager(), PP.getLangOpts());
+ Condition.push_back('\0');
+ Lexer Lex(DirectiveLoc, PP.getLangOpts(), Condition.data(),
+ Condition.data(), Condition.data() + Condition.size() - 1);
+ Token Tok;
+ bool Inverted = false; // The inverted form of #*def is #*ndef.
+ std::size_t ParensNestingDepth = 0;
+ for (;;) {
+ if (Lex.LexFromRawLexer(Tok))
+ return;
+
+ if (Tok.is(tok::TokenKind::exclaim) ||
+ (PP.getLangOpts().CPlusPlus &&
+ Tok.is(tok::TokenKind::raw_identifier) &&
+ Tok.getRawIdentifier() == "not"))
+ Inverted = !Inverted;
+ else if (Tok.is(tok::TokenKind::l_paren))
+ ++ParensNestingDepth;
+ else
+ break;
+ }
+
+ if (Tok.isNot(tok::TokenKind::raw_identifier) ||
+ Tok.getRawIdentifier() != "defined")
+ return;
+
+ bool NoMoreTokens = Lex.LexFromRawLexer(Tok);
+ if (Tok.is(tok::TokenKind::l_paren)) {
+ if (NoMoreTokens)
+ return;
+ ++ParensNestingDepth;
+ NoMoreTokens = Lex.LexFromRawLexer(Tok);
+ }
+
+ if (Tok.isNot(tok::TokenKind::raw_identifier))
+ return;
+ const StringRef Macro = Tok.getRawIdentifier();
+
+ while (!NoMoreTokens) {
+ NoMoreTokens = Lex.LexFromRawLexer(Tok);
+ if (Tok.isNot(tok::TokenKind::r_paren))
+ return;
+ --ParensNestingDepth;
+ }
+
+ if (ParensNestingDepth != 0)
+ return;
+
+ Check.diag(
+ DirectiveLoc,
+ "preprocessor condition can be written more concisely using '#%0'")
+ << FixItHint::CreateReplacement(DirectiveLoc, Replacements[Inverted])
+ << FixItHint::CreateReplacement(ConditionRange, Macro)
+ << Replacements[Inverted];
+ }
+
+ ClangTidyCheck &Check;
+ const Preprocessor &PP;
+};
+
+} // namespace
+
+void UseConcisePreprocessorDirectivesCheck::registerPPCallbacks(
+ const SourceManager &, Preprocessor *PP, Preprocessor *) {
+ PP->addPPCallbacks(std::make_unique<IfPreprocessorCallbacks>(*this, *PP));
+}
+
+} // namespace clang::tidy::readability
diff --git a/clang-tools-extra/clang-tidy/readability/UseConcisePreprocessorDirectivesCheck.h b/clang-tools-extra/clang-tidy/readability/UseConcisePreprocessorDirectivesCheck.h
new file mode 100644
index 0000000000000..e65b16876a89a
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/readability/UseConcisePreprocessorDirectivesCheck.h
@@ -0,0 +1,34 @@
+//===--- UseConcisePreprocessorDirectivesCheck.h - clang-tidy ---*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USECONCISEPREPROCESSORDIRECTIVESCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USECONCISEPREPROCESSORDIRECTIVESCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::readability {
+
+/// Finds uses of ``#if`` that can be simplified to ``#ifdef`` or ``#ifndef``
+/// and, since C23 and C++23, uses of ``#elif`` that can be simplified to
+/// ``#elifdef`` or ``#elifndef``.
+///
+/// User-facing documentation:
+/// https://clang.llvm.org/extra/clang-tidy/checks/readability/use-concise-preprocessor-directives.html
+class UseConcisePreprocessorDirectivesCheck : public ClangTidyCheck {
+public:
+ using ClangTidyCheck::ClangTidyCheck;
+ void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
+ Preprocessor *ModuleExpanderPP) override;
+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+ return true;
+ }
+};
+
+} // namespace clang::tidy::readability
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USECONCISEPREPROCESSORDIRECTIVESCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index d76cd3a7c8728..ee7329fa8610b 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -166,6 +166,13 @@ New checks
Finds potentially erroneous calls to ``reset`` method on smart pointers when
the pointee type also has a ``reset`` method.
+- New :doc:`readability-use-concise-preprocessor-directives
+ <clang-tidy/checks/readability/use-concise-preprocessor-directives>` check.
+
+ Finds uses of ``#if`` that can be simplified to ``#ifdef`` or ``#ifndef`` and,
+ since C23 and C++23, uses of ``#elif`` that can be simplified to ``#elifdef``
+ or ``#elifndef``.
+
New check aliases
^^^^^^^^^^^^^^^^^
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 9b62c87664693..0cffbd323caa2 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -411,6 +411,7 @@ Clang-Tidy Checks
:doc:`readability-uniqueptr-delete-release <readability/uniqueptr-delete-release>`, "Yes"
:doc:`readability-uppercase-literal-suffix <readability/uppercase-literal-suffix>`, "Yes"
:doc:`readability-use-anyofallof <readability/use-anyofallof>`,
+ :doc:`readability-use-concise-preprocessor-directives <readability/use-concise-preprocessor-directives>`, "Yes"
:doc:`readability-use-std-min-max <readability/use-std-min-max>`, "Yes"
:doc:`zircon-temporary-objects <zircon/temporary-objects>`,
diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/use-concise-preprocessor-directives.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/use-concise-preprocessor-directives.rst
new file mode 100644
index 0000000000000..30ec7e6b89936
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/readability/use-concise-preprocessor-directives.rst
@@ -0,0 +1,30 @@
+.. title:: clang-tidy - readability-use-concise-preprocessor-directives
+
+readability-use-concise-preprocessor-directives
+===============================================
+
+Finds uses of ``#if`` that can be simplified to ``#ifdef`` or ``#ifndef`` and,
+since C23 and C++23, uses of ``#elif`` that can be simplified to ``#elifdef``
+or ``#elifndef``:
+
+.. code-block:: c++
+
+ #if defined(MEOW)
+ #if !defined(MEOW)
+
+ // becomes
+
+ #ifdef MEOW
+ #ifndef MEOW
+
+Since C23 and C++23:
+
+.. code-block:: c++
+
+ #elif defined(MEOW)
+ #elif !defined(MEOW)
+
+ // becomes
+
+ #elifdef MEOW
+ #elifndef MEOW
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/use-concise-preprocessor-directives.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/use-concise-preprocessor-directives.cpp
new file mode 100644
index 0000000000000..53e079bcca40f
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/use-concise-preprocessor-directives.cpp
@@ -0,0 +1,144 @@
+// RUN: %check_clang_tidy -std=c++98,c++11,c++14,c++17,c++20 -check-suffixes=,CXX %s readability-use-concise-preprocessor-directives %t
+// RUN: %check_clang_tidy -std=c++23-or-later -check-suffixes=,23,CXX,CXX23 %s readability-use-concise-preprocessor-directives %t
+
+// RUN: %check_clang_tidy -std=c99,c11,c17 %s readability-use-concise-preprocessor-directives %t -- -- -x c
+// RUN: %check_clang_tidy -std=c23-or-later -check-suffixes=,23 %s readability-use-concise-preprocessor-directives %t -- -- -x c
+
+// CHECK-MESSAGES: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#ifdef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES: #ifdef FOO
+#if defined(FOO)
+// CHECK-MESSAGES-23: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#elifdef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES-23: #elifdef BAR
+#elif defined(BAR)
+#endif
+
+// CHECK-MESSAGES: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#ifdef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES: #ifdef FOO
+#if defined FOO
+// CHECK-MESSAGES-23: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#elifdef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES-23: #elifdef BAR
+#elif defined BAR
+#endif
+
+// CHECK-MESSAGES: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#ifdef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES: #ifdef FOO
+#if (defined(FOO))
+// CHECK-MESSAGES-23: :[[@LINE+2]]:4: warning: preprocessor condition can be written more concisely using '#elifdef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES-23: # elifdef BAR
+# elif (defined(BAR))
+#endif
+
+// CHECK-MESSAGES: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#ifdef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES: #ifdef FOO
+#if (defined FOO)
+// CHECK-MESSAGES-23: :[[@LINE+2]]:4: warning: preprocessor condition can be written more concisely using '#elifdef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES-23: # elifdef BAR
+# elif (defined BAR)
+#endif
+
+// CHECK-MESSAGES: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#ifndef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES: #ifndef FOO
+#if !defined(FOO)
+// CHECK-MESSAGES-23: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#elifndef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES-23: #elifndef BAR
+#elif !defined(BAR)
+#endif
+
+#ifdef __cplusplus
+// CHECK-MESSAGES-CXX: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#ifndef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES-CXX: #ifndef FOO
+#if not defined(FOO)
+// CHECK-MESSAGES-CXX23: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#elifndef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES-CXX23: #elifndef BAR
+#elif not defined(BAR)
+#endif
+#endif // __cplusplus
+
+// CHECK-MESSAGES: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#ifndef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES: #ifndef FOO
+#if !defined FOO
+// CHECK-MESSAGES-23: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#elifndef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES-23: #elifndef BAR
+#elif !defined BAR
+#endif
+
+#ifdef __cplusplus
+// CHECK-MESSAGES-CXX: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#ifndef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES-CXX: #ifndef FOO
+#if not defined FOO
+// CHECK-MESSAGES-CXX23: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#elifndef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES-CXX23: #elifndef BAR
+#elif not defined BAR
+#endif
+#endif // __cplusplus
+
+// CHECK-MESSAGES: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#ifndef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES: #ifndef FOO
+#if (!defined(FOO))
+// CHECK-MESSAGES-23: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#elifndef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES-23: #elifndef BAR
+#elif (!defined(BAR))
+#endif
+
+// CHECK-MESSAGES: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#ifndef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES: #ifndef FOO
+#if (!defined FOO)
+// CHECK-MESSAGES-23: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#elifndef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES-23: #elifndef BAR
+#elif (!defined BAR)
+#endif
+
+// CHECK-MESSAGES: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#ifndef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES: #ifndef FOO
+#if !(defined(FOO))
+// CHECK-MESSAGES-23: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#elifndef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES-23: #elifndef BAR
+#elif !(defined(BAR))
+#endif
+
+// CHECK-MESSAGES: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#ifndef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES: #ifndef FOO
+#if !(defined FOO)
+// CHECK-MESSAGES-23: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#elifndef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES-23: #elifndef BAR
+#elif !(defined BAR)
+#endif
+
+// These cases with many parentheses and negations are unrealistic, but
+// handling them doesn't really add any complexity to the implementation.
+// Test them for good measure.
+
+// CHECK-MESSAGES: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#ifndef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES: #ifndef FOO
+#if !((!!(defined(FOO))))
+// CHECK-MESSAGES-23: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#elifdef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES-23: #elifdef BAR
+#elif ((!(!(defined(BAR)))))
+#endif
+
+// CHECK-MESSAGES: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#ifndef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES: #ifndef FOO
+#if !((!!(defined FOO)))
+// CHECK-MESSAGES-23: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#elifdef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES-23: #elifdef BAR
+#elif ((!(!(defined BAR))))
+#endif
+
+// CHECK-MESSAGES: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#ifndef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES: #ifndef FOO
+#if !( (!! ( defined FOO )) )
+// CHECK-MESSAGES-23: :[[@LINE+2]]:2: warning: preprocessor condition can be written more concisely using '#elifdef' [readability-use-concise-preprocessor-directives]
+// CHECK-FIXES-23: #elifdef BAR
+#elif ( ( !(!( defined BAR) ) ))
+#endif
+
+#if FOO
+#elif BAR
+#endif
+
+#if defined(FOO) && defined(BAR)
+#elif defined(FOO) && defined(BAR)
+#endif
+
+#if defined FOO && BAR
+#endif
More information about the cfe-commits
mailing list