Skip to content

[clang-tidy] Add new check: readability-use-concise-preprocessor-directives #146830

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jul 13, 2025
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/readability/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ add_clang_library(clangTidyReadabilityModule STATIC
UniqueptrDeleteReleaseCheck.cpp
UppercaseLiteralSuffixCheck.cpp
UseAnyOfAllOfCheck.cpp
UseConcisePreprocessorDirectivesCheck.cpp
UseStdMinMaxCheck.cpp

LINK_LIBS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
#include "UniqueptrDeleteReleaseCheck.h"
#include "UppercaseLiteralSuffixCheck.h"
#include "UseAnyOfAllOfCheck.h"
#include "UseConcisePreprocessorDirectivesCheck.h"
#include "UseStdMinMaxCheck.h"

namespace clang::tidy {
Expand Down Expand Up @@ -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");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,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
^^^^^^^^^^^^^^^^^

Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,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>`,

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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