Skip to content

Commit

Permalink
[clang-tidy] add new check readability-enum-initial-value (#86129)
Browse files Browse the repository at this point in the history
Fixes: #85243.
  • Loading branch information
HerrCai0907 committed Apr 1, 2024
1 parent 10a57f3 commit 3365d62
Show file tree
Hide file tree
Showing 9 changed files with 431 additions and 0 deletions.
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 @@ -17,6 +17,7 @@ add_clang_library(clangTidyReadabilityModule
DeleteNullPointerCheck.cpp
DuplicateIncludeCheck.cpp
ElseAfterReturnCheck.cpp
EnumInitialValueCheck.cpp
FunctionCognitiveComplexityCheck.cpp
FunctionSizeCheck.cpp
IdentifierLengthCheck.cpp
Expand Down
200 changes: 200 additions & 0 deletions clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
//===--- EnumInitialValueCheck.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 "EnumInitialValueCheck.h"
#include "../utils/LexerUtils.h"
#include "clang/AST/Decl.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/SourceLocation.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallString.h"

using namespace clang::ast_matchers;

namespace clang::tidy::readability {

static bool isNoneEnumeratorsInitialized(const EnumDecl &Node) {
return llvm::all_of(Node.enumerators(), [](const EnumConstantDecl *ECD) {
return ECD->getInitExpr() == nullptr;
});
}

static bool isOnlyFirstEnumeratorInitialized(const EnumDecl &Node) {
bool IsFirst = true;
for (const EnumConstantDecl *ECD : Node.enumerators()) {
if ((IsFirst && ECD->getInitExpr() == nullptr) ||
(!IsFirst && ECD->getInitExpr() != nullptr))
return false;
IsFirst = false;
}
return !IsFirst;
}

static bool areAllEnumeratorsInitialized(const EnumDecl &Node) {
return llvm::all_of(Node.enumerators(), [](const EnumConstantDecl *ECD) {
return ECD->getInitExpr() != nullptr;
});
}

/// Check if \p Enumerator is initialized with a (potentially negated) \c
/// IntegerLiteral.
static bool isInitializedByLiteral(const EnumConstantDecl *Enumerator) {
const Expr *const Init = Enumerator->getInitExpr();
if (!Init)
return false;
return Init->isIntegerConstantExpr(Enumerator->getASTContext());
}

static void cleanInitialValue(DiagnosticBuilder &Diag,
const EnumConstantDecl *ECD,
const SourceManager &SM,
const LangOptions &LangOpts) {
const SourceRange InitExprRange = ECD->getInitExpr()->getSourceRange();
if (InitExprRange.isInvalid() || InitExprRange.getBegin().isMacroID() ||
InitExprRange.getEnd().isMacroID())
return;
std::optional<Token> EqualToken = utils::lexer::findNextTokenSkippingComments(
ECD->getLocation(), SM, LangOpts);
if (!EqualToken.has_value() ||
EqualToken.value().getKind() != tok::TokenKind::equal)
return;
const SourceLocation EqualLoc{EqualToken->getLocation()};
if (EqualLoc.isInvalid() || EqualLoc.isMacroID())
return;
Diag << FixItHint::CreateRemoval(EqualLoc)
<< FixItHint::CreateRemoval(InitExprRange);
return;
}

namespace {

AST_MATCHER(EnumDecl, isMacro) {
SourceLocation Loc = Node.getBeginLoc();
return Loc.isMacroID();
}

AST_MATCHER(EnumDecl, hasConsistentInitialValues) {
return isNoneEnumeratorsInitialized(Node) ||
isOnlyFirstEnumeratorInitialized(Node) ||
areAllEnumeratorsInitialized(Node);
}

AST_MATCHER(EnumDecl, hasZeroInitialValueForFirstEnumerator) {
const EnumDecl::enumerator_range Enumerators = Node.enumerators();
if (Enumerators.empty())
return false;
const EnumConstantDecl *ECD = *Enumerators.begin();
return isOnlyFirstEnumeratorInitialized(Node) &&
isInitializedByLiteral(ECD) && ECD->getInitVal().isZero();
}

/// Excludes bitfields because enumerators initialized with the result of a
/// bitwise operator on enumeration values or any other expr that is not a
/// potentially negative integer literal.
/// Enumerations where it is not directly clear if they are used with
/// bitmask, evident when enumerators are only initialized with (potentially
/// negative) integer literals, are ignored. This is also the case when all
/// enumerators are powers of two (e.g., 0, 1, 2).
AST_MATCHER(EnumDecl, hasSequentialInitialValues) {
const EnumDecl::enumerator_range Enumerators = Node.enumerators();
if (Enumerators.empty())
return false;
const EnumConstantDecl *const FirstEnumerator = *Node.enumerator_begin();
llvm::APSInt PrevValue = FirstEnumerator->getInitVal();
if (!isInitializedByLiteral(FirstEnumerator))
return false;
bool AllEnumeratorsArePowersOfTwo = true;
for (const EnumConstantDecl *Enumerator : llvm::drop_begin(Enumerators)) {
const llvm::APSInt NewValue = Enumerator->getInitVal();
if (NewValue != ++PrevValue)
return false;
if (!isInitializedByLiteral(Enumerator))
return false;
PrevValue = NewValue;
AllEnumeratorsArePowersOfTwo &= NewValue.isPowerOf2();
}
return !AllEnumeratorsArePowersOfTwo;
}

} // namespace

EnumInitialValueCheck::EnumInitialValueCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
AllowExplicitZeroFirstInitialValue(
Options.get("AllowExplicitZeroFirstInitialValue", true)),
AllowExplicitSequentialInitialValues(
Options.get("AllowExplicitSequentialInitialValues", true)) {}

void EnumInitialValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "AllowExplicitZeroFirstInitialValue",
AllowExplicitZeroFirstInitialValue);
Options.store(Opts, "AllowExplicitSequentialInitialValues",
AllowExplicitSequentialInitialValues);
}

void EnumInitialValueCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(
enumDecl(unless(isMacro()), unless(hasConsistentInitialValues()))
.bind("inconsistent"),
this);
if (!AllowExplicitZeroFirstInitialValue)
Finder->addMatcher(
enumDecl(hasZeroInitialValueForFirstEnumerator()).bind("zero_first"),
this);
if (!AllowExplicitSequentialInitialValues)
Finder->addMatcher(enumDecl(unless(isMacro()), hasSequentialInitialValues())
.bind("sequential"),
this);
}

void EnumInitialValueCheck::check(const MatchFinder::MatchResult &Result) {
if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("inconsistent")) {
DiagnosticBuilder Diag =
diag(Enum->getBeginLoc(),
"inital values in enum %0 are not consistent, consider explicit "
"initialization of all, none or only the first enumerator")
<< Enum;
for (const EnumConstantDecl *ECD : Enum->enumerators())
if (ECD->getInitExpr() == nullptr) {
const SourceLocation EndLoc = Lexer::getLocForEndOfToken(
ECD->getLocation(), 0, *Result.SourceManager, getLangOpts());
if (EndLoc.isMacroID())
continue;
llvm::SmallString<8> Str{" = "};
ECD->getInitVal().toString(Str);
Diag << FixItHint::CreateInsertion(EndLoc, Str);
}
return;
}

if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("zero_first")) {
const EnumConstantDecl *ECD = *Enum->enumerator_begin();
const SourceLocation Loc = ECD->getLocation();
if (Loc.isInvalid() || Loc.isMacroID())
return;
DiagnosticBuilder Diag = diag(Loc, "zero initial value for the first "
"enumerator in %0 can be disregarded")
<< Enum;
cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts());
return;
}
if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("sequential")) {
DiagnosticBuilder Diag =
diag(Enum->getBeginLoc(),
"sequential initial value in %0 can be ignored")
<< Enum;
for (const EnumConstantDecl *ECD : llvm::drop_begin(Enum->enumerators()))
cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts());
return;
}
}

} // namespace clang::tidy::readability
38 changes: 38 additions & 0 deletions clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//===--- EnumInitialValueCheck.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_ENUMINITIALVALUECHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ENUMINITIALVALUECHECK_H

#include "../ClangTidyCheck.h"

namespace clang::tidy::readability {

/// Enforces consistent style for enumerators' initialization, covering three
/// styles: none, first only, or all initialized explicitly.
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/readability/enum-initial-value.html
class EnumInitialValueCheck : public ClangTidyCheck {
public:
EnumInitialValueCheck(StringRef Name, ClangTidyContext *Context);
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
std::optional<TraversalKind> getCheckTraversalKind() const override {
return TK_IgnoreUnlessSpelledInSource;
}

private:
const bool AllowExplicitZeroFirstInitialValue;
const bool AllowExplicitSequentialInitialValues;
};

} // namespace clang::tidy::readability

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ENUMINITIALVALUECHECK_H
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "DeleteNullPointerCheck.h"
#include "DuplicateIncludeCheck.h"
#include "ElseAfterReturnCheck.h"
#include "EnumInitialValueCheck.h"
#include "FunctionCognitiveComplexityCheck.h"
#include "FunctionSizeCheck.h"
#include "IdentifierLengthCheck.h"
Expand Down Expand Up @@ -92,6 +93,8 @@ class ReadabilityModule : public ClangTidyModule {
"readability-duplicate-include");
CheckFactories.registerCheck<ElseAfterReturnCheck>(
"readability-else-after-return");
CheckFactories.registerCheck<EnumInitialValueCheck>(
"readability-enum-initial-value");
CheckFactories.registerCheck<FunctionCognitiveComplexityCheck>(
"readability-function-cognitive-complexity");
CheckFactories.registerCheck<FunctionSizeCheck>(
Expand Down
6 changes: 6 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ New checks
Finds initializer lists for aggregate types that could be
written as designated initializers instead.

- New :doc:`readability-enum-initial-value
<clang-tidy/checks/readability/enum-initial-value>` check.

Enforces consistent style for enumerators' initialization, covering three
styles: none, first only, or all initialized explicitly.

- New :doc:`readability-use-std-min-max
<clang-tidy/checks/readability/use-std-min-max>` check.

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 @@ -352,6 +352,7 @@ Clang-Tidy Checks
:doc:`readability-delete-null-pointer <readability/delete-null-pointer>`, "Yes"
:doc:`readability-duplicate-include <readability/duplicate-include>`, "Yes"
:doc:`readability-else-after-return <readability/else-after-return>`, "Yes"
:doc:`readability-enum-initial-value <readability/enum-initial-value>`, "Yes"
:doc:`readability-function-cognitive-complexity <readability/function-cognitive-complexity>`,
:doc:`readability-function-size <readability/function-size>`,
:doc:`readability-identifier-length <readability/identifier-length>`,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
.. title:: clang-tidy - readability-enum-initial-value

readability-enum-initial-value
==============================

Enforces consistent style for enumerators' initialization, covering three
styles: none, first only, or all initialized explicitly.

When adding new enumerations, inconsistent initial value will cause potential
enumeration value conflicts.

In an enumeration, the following three cases are accepted.
1. none of enumerators are explicit initialized.
2. the first enumerator is explicit initialized.
3. all of enumerators are explicit initialized.

.. code-block:: c++

// valid, none of enumerators are initialized.
enum A {
e0,
e1,
e2,
};

// valid, the first enumerator is initialized.
enum A {
e0 = 0,
e1,
e2,
};

// valid, all of enumerators are initialized.
enum A {
e0 = 0,
e1 = 1,
e2 = 2,
};

// invalid, e1 is not explicit initialized.
enum A {
e0 = 0,
e1,
e2 = 2,
};

Options
-------

.. option:: AllowExplicitZeroFirstInitialValue

If set to `false`, the first enumerator must not be explicitly initialized.
See examples below. Default is `true`.

.. code-block:: c++

enum A {
e0 = 0, // not allowed if AllowExplicitZeroFirstInitialValue is false
e1,
e2,
};


.. option:: AllowExplicitSequentialInitialValues

If set to `false`, sequential initializations are not allowed.
See examples below. Default is `true`.

.. code-block:: c++

enum A {
e0 = 1, // not allowed if AllowExplicitSequentialInitialValues is false
e1 = 2,
e2 = 3,
};

0 comments on commit 3365d62

Please sign in to comment.