Skip to content
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,108 @@
//===--- 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"

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 llvm::StringLiteral (&Replacements)[2]) {
// 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,40 @@
//===--- 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 {

/// Shortens `#if` preprocessor conditions:
///
/// #if defined(MEOW) -> #ifdef MEOW
/// #if !defined(MEOW) -> #ifndef MEOW
///
/// And, since C23 and C++23, shortens `#elif` conditions too:
///
/// #elif defined(MEOW) -> #elifdef MEOW
/// #elif !defined(MEOW) -> #elifndef MEOW
///
/// 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 LangOpts.CPlusPlus || LangOpts.C99;
}
};

} // 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,138 @@
// RUN: %check_clang_tidy -std=c++98 -check-suffixes=,CXX %s readability-use-concise-preprocessor-directives %t
// RUN: %check_clang_tidy -std=c++11 -check-suffixes=,CXX %s readability-use-concise-preprocessor-directives %t
// RUN: %check_clang_tidy -std=c++14 -check-suffixes=,CXX %s readability-use-concise-preprocessor-directives %t
// RUN: %check_clang_tidy -std=c++17 -check-suffixes=,CXX %s readability-use-concise-preprocessor-directives %t
// RUN: %check_clang_tidy -std=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 %s readability-use-concise-preprocessor-directives %t -- -- -x c
// RUN: %check_clang_tidy -std=c11 %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

#if FOO
#elif BAR
#endif

#if defined(FOO) && defined(BAR)
#elif defined(FOO) && defined(BAR)
#endif