Skip to content

Commit 3365d62

Browse files
authored
[clang-tidy] add new check readability-enum-initial-value (#86129)
Fixes: #85243.
1 parent 10a57f3 commit 3365d62

File tree

9 files changed

+431
-0
lines changed

9 files changed

+431
-0
lines changed

clang-tools-extra/clang-tidy/readability/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ add_clang_library(clangTidyReadabilityModule
1717
DeleteNullPointerCheck.cpp
1818
DuplicateIncludeCheck.cpp
1919
ElseAfterReturnCheck.cpp
20+
EnumInitialValueCheck.cpp
2021
FunctionCognitiveComplexityCheck.cpp
2122
FunctionSizeCheck.cpp
2223
IdentifierLengthCheck.cpp
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
//===--- EnumInitialValueCheck.cpp - clang-tidy ---------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "EnumInitialValueCheck.h"
10+
#include "../utils/LexerUtils.h"
11+
#include "clang/AST/Decl.h"
12+
#include "clang/ASTMatchers/ASTMatchFinder.h"
13+
#include "clang/ASTMatchers/ASTMatchers.h"
14+
#include "clang/Basic/Diagnostic.h"
15+
#include "clang/Basic/SourceLocation.h"
16+
#include "llvm/ADT/STLExtras.h"
17+
#include "llvm/ADT/SmallString.h"
18+
19+
using namespace clang::ast_matchers;
20+
21+
namespace clang::tidy::readability {
22+
23+
static bool isNoneEnumeratorsInitialized(const EnumDecl &Node) {
24+
return llvm::all_of(Node.enumerators(), [](const EnumConstantDecl *ECD) {
25+
return ECD->getInitExpr() == nullptr;
26+
});
27+
}
28+
29+
static bool isOnlyFirstEnumeratorInitialized(const EnumDecl &Node) {
30+
bool IsFirst = true;
31+
for (const EnumConstantDecl *ECD : Node.enumerators()) {
32+
if ((IsFirst && ECD->getInitExpr() == nullptr) ||
33+
(!IsFirst && ECD->getInitExpr() != nullptr))
34+
return false;
35+
IsFirst = false;
36+
}
37+
return !IsFirst;
38+
}
39+
40+
static bool areAllEnumeratorsInitialized(const EnumDecl &Node) {
41+
return llvm::all_of(Node.enumerators(), [](const EnumConstantDecl *ECD) {
42+
return ECD->getInitExpr() != nullptr;
43+
});
44+
}
45+
46+
/// Check if \p Enumerator is initialized with a (potentially negated) \c
47+
/// IntegerLiteral.
48+
static bool isInitializedByLiteral(const EnumConstantDecl *Enumerator) {
49+
const Expr *const Init = Enumerator->getInitExpr();
50+
if (!Init)
51+
return false;
52+
return Init->isIntegerConstantExpr(Enumerator->getASTContext());
53+
}
54+
55+
static void cleanInitialValue(DiagnosticBuilder &Diag,
56+
const EnumConstantDecl *ECD,
57+
const SourceManager &SM,
58+
const LangOptions &LangOpts) {
59+
const SourceRange InitExprRange = ECD->getInitExpr()->getSourceRange();
60+
if (InitExprRange.isInvalid() || InitExprRange.getBegin().isMacroID() ||
61+
InitExprRange.getEnd().isMacroID())
62+
return;
63+
std::optional<Token> EqualToken = utils::lexer::findNextTokenSkippingComments(
64+
ECD->getLocation(), SM, LangOpts);
65+
if (!EqualToken.has_value() ||
66+
EqualToken.value().getKind() != tok::TokenKind::equal)
67+
return;
68+
const SourceLocation EqualLoc{EqualToken->getLocation()};
69+
if (EqualLoc.isInvalid() || EqualLoc.isMacroID())
70+
return;
71+
Diag << FixItHint::CreateRemoval(EqualLoc)
72+
<< FixItHint::CreateRemoval(InitExprRange);
73+
return;
74+
}
75+
76+
namespace {
77+
78+
AST_MATCHER(EnumDecl, isMacro) {
79+
SourceLocation Loc = Node.getBeginLoc();
80+
return Loc.isMacroID();
81+
}
82+
83+
AST_MATCHER(EnumDecl, hasConsistentInitialValues) {
84+
return isNoneEnumeratorsInitialized(Node) ||
85+
isOnlyFirstEnumeratorInitialized(Node) ||
86+
areAllEnumeratorsInitialized(Node);
87+
}
88+
89+
AST_MATCHER(EnumDecl, hasZeroInitialValueForFirstEnumerator) {
90+
const EnumDecl::enumerator_range Enumerators = Node.enumerators();
91+
if (Enumerators.empty())
92+
return false;
93+
const EnumConstantDecl *ECD = *Enumerators.begin();
94+
return isOnlyFirstEnumeratorInitialized(Node) &&
95+
isInitializedByLiteral(ECD) && ECD->getInitVal().isZero();
96+
}
97+
98+
/// Excludes bitfields because enumerators initialized with the result of a
99+
/// bitwise operator on enumeration values or any other expr that is not a
100+
/// potentially negative integer literal.
101+
/// Enumerations where it is not directly clear if they are used with
102+
/// bitmask, evident when enumerators are only initialized with (potentially
103+
/// negative) integer literals, are ignored. This is also the case when all
104+
/// enumerators are powers of two (e.g., 0, 1, 2).
105+
AST_MATCHER(EnumDecl, hasSequentialInitialValues) {
106+
const EnumDecl::enumerator_range Enumerators = Node.enumerators();
107+
if (Enumerators.empty())
108+
return false;
109+
const EnumConstantDecl *const FirstEnumerator = *Node.enumerator_begin();
110+
llvm::APSInt PrevValue = FirstEnumerator->getInitVal();
111+
if (!isInitializedByLiteral(FirstEnumerator))
112+
return false;
113+
bool AllEnumeratorsArePowersOfTwo = true;
114+
for (const EnumConstantDecl *Enumerator : llvm::drop_begin(Enumerators)) {
115+
const llvm::APSInt NewValue = Enumerator->getInitVal();
116+
if (NewValue != ++PrevValue)
117+
return false;
118+
if (!isInitializedByLiteral(Enumerator))
119+
return false;
120+
PrevValue = NewValue;
121+
AllEnumeratorsArePowersOfTwo &= NewValue.isPowerOf2();
122+
}
123+
return !AllEnumeratorsArePowersOfTwo;
124+
}
125+
126+
} // namespace
127+
128+
EnumInitialValueCheck::EnumInitialValueCheck(StringRef Name,
129+
ClangTidyContext *Context)
130+
: ClangTidyCheck(Name, Context),
131+
AllowExplicitZeroFirstInitialValue(
132+
Options.get("AllowExplicitZeroFirstInitialValue", true)),
133+
AllowExplicitSequentialInitialValues(
134+
Options.get("AllowExplicitSequentialInitialValues", true)) {}
135+
136+
void EnumInitialValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
137+
Options.store(Opts, "AllowExplicitZeroFirstInitialValue",
138+
AllowExplicitZeroFirstInitialValue);
139+
Options.store(Opts, "AllowExplicitSequentialInitialValues",
140+
AllowExplicitSequentialInitialValues);
141+
}
142+
143+
void EnumInitialValueCheck::registerMatchers(MatchFinder *Finder) {
144+
Finder->addMatcher(
145+
enumDecl(unless(isMacro()), unless(hasConsistentInitialValues()))
146+
.bind("inconsistent"),
147+
this);
148+
if (!AllowExplicitZeroFirstInitialValue)
149+
Finder->addMatcher(
150+
enumDecl(hasZeroInitialValueForFirstEnumerator()).bind("zero_first"),
151+
this);
152+
if (!AllowExplicitSequentialInitialValues)
153+
Finder->addMatcher(enumDecl(unless(isMacro()), hasSequentialInitialValues())
154+
.bind("sequential"),
155+
this);
156+
}
157+
158+
void EnumInitialValueCheck::check(const MatchFinder::MatchResult &Result) {
159+
if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("inconsistent")) {
160+
DiagnosticBuilder Diag =
161+
diag(Enum->getBeginLoc(),
162+
"inital values in enum %0 are not consistent, consider explicit "
163+
"initialization of all, none or only the first enumerator")
164+
<< Enum;
165+
for (const EnumConstantDecl *ECD : Enum->enumerators())
166+
if (ECD->getInitExpr() == nullptr) {
167+
const SourceLocation EndLoc = Lexer::getLocForEndOfToken(
168+
ECD->getLocation(), 0, *Result.SourceManager, getLangOpts());
169+
if (EndLoc.isMacroID())
170+
continue;
171+
llvm::SmallString<8> Str{" = "};
172+
ECD->getInitVal().toString(Str);
173+
Diag << FixItHint::CreateInsertion(EndLoc, Str);
174+
}
175+
return;
176+
}
177+
178+
if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("zero_first")) {
179+
const EnumConstantDecl *ECD = *Enum->enumerator_begin();
180+
const SourceLocation Loc = ECD->getLocation();
181+
if (Loc.isInvalid() || Loc.isMacroID())
182+
return;
183+
DiagnosticBuilder Diag = diag(Loc, "zero initial value for the first "
184+
"enumerator in %0 can be disregarded")
185+
<< Enum;
186+
cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts());
187+
return;
188+
}
189+
if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("sequential")) {
190+
DiagnosticBuilder Diag =
191+
diag(Enum->getBeginLoc(),
192+
"sequential initial value in %0 can be ignored")
193+
<< Enum;
194+
for (const EnumConstantDecl *ECD : llvm::drop_begin(Enum->enumerators()))
195+
cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts());
196+
return;
197+
}
198+
}
199+
200+
} // namespace clang::tidy::readability
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//===--- EnumInitialValueCheck.h - clang-tidy -------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ENUMINITIALVALUECHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ENUMINITIALVALUECHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
14+
namespace clang::tidy::readability {
15+
16+
/// Enforces consistent style for enumerators' initialization, covering three
17+
/// styles: none, first only, or all initialized explicitly.
18+
///
19+
/// For the user-facing documentation see:
20+
/// http://clang.llvm.org/extra/clang-tidy/checks/readability/enum-initial-value.html
21+
class EnumInitialValueCheck : public ClangTidyCheck {
22+
public:
23+
EnumInitialValueCheck(StringRef Name, ClangTidyContext *Context);
24+
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
25+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
26+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
27+
std::optional<TraversalKind> getCheckTraversalKind() const override {
28+
return TK_IgnoreUnlessSpelledInSource;
29+
}
30+
31+
private:
32+
const bool AllowExplicitZeroFirstInitialValue;
33+
const bool AllowExplicitSequentialInitialValues;
34+
};
35+
36+
} // namespace clang::tidy::readability
37+
38+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ENUMINITIALVALUECHECK_H

clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "DeleteNullPointerCheck.h"
2323
#include "DuplicateIncludeCheck.h"
2424
#include "ElseAfterReturnCheck.h"
25+
#include "EnumInitialValueCheck.h"
2526
#include "FunctionCognitiveComplexityCheck.h"
2627
#include "FunctionSizeCheck.h"
2728
#include "IdentifierLengthCheck.h"
@@ -92,6 +93,8 @@ class ReadabilityModule : public ClangTidyModule {
9293
"readability-duplicate-include");
9394
CheckFactories.registerCheck<ElseAfterReturnCheck>(
9495
"readability-else-after-return");
96+
CheckFactories.registerCheck<EnumInitialValueCheck>(
97+
"readability-enum-initial-value");
9598
CheckFactories.registerCheck<FunctionCognitiveComplexityCheck>(
9699
"readability-function-cognitive-complexity");
97100
CheckFactories.registerCheck<FunctionSizeCheck>(

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,12 @@ New checks
123123
Finds initializer lists for aggregate types that could be
124124
written as designated initializers instead.
125125

126+
- New :doc:`readability-enum-initial-value
127+
<clang-tidy/checks/readability/enum-initial-value>` check.
128+
129+
Enforces consistent style for enumerators' initialization, covering three
130+
styles: none, first only, or all initialized explicitly.
131+
126132
- New :doc:`readability-use-std-min-max
127133
<clang-tidy/checks/readability/use-std-min-max>` check.
128134

clang-tools-extra/docs/clang-tidy/checks/list.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ Clang-Tidy Checks
352352
:doc:`readability-delete-null-pointer <readability/delete-null-pointer>`, "Yes"
353353
:doc:`readability-duplicate-include <readability/duplicate-include>`, "Yes"
354354
:doc:`readability-else-after-return <readability/else-after-return>`, "Yes"
355+
:doc:`readability-enum-initial-value <readability/enum-initial-value>`, "Yes"
355356
:doc:`readability-function-cognitive-complexity <readability/function-cognitive-complexity>`,
356357
:doc:`readability-function-size <readability/function-size>`,
357358
:doc:`readability-identifier-length <readability/identifier-length>`,
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
.. title:: clang-tidy - readability-enum-initial-value
2+
3+
readability-enum-initial-value
4+
==============================
5+
6+
Enforces consistent style for enumerators' initialization, covering three
7+
styles: none, first only, or all initialized explicitly.
8+
9+
When adding new enumerations, inconsistent initial value will cause potential
10+
enumeration value conflicts.
11+
12+
In an enumeration, the following three cases are accepted.
13+
1. none of enumerators are explicit initialized.
14+
2. the first enumerator is explicit initialized.
15+
3. all of enumerators are explicit initialized.
16+
17+
.. code-block:: c++
18+
19+
// valid, none of enumerators are initialized.
20+
enum A {
21+
e0,
22+
e1,
23+
e2,
24+
};
25+
26+
// valid, the first enumerator is initialized.
27+
enum A {
28+
e0 = 0,
29+
e1,
30+
e2,
31+
};
32+
33+
// valid, all of enumerators are initialized.
34+
enum A {
35+
e0 = 0,
36+
e1 = 1,
37+
e2 = 2,
38+
};
39+
40+
// invalid, e1 is not explicit initialized.
41+
enum A {
42+
e0 = 0,
43+
e1,
44+
e2 = 2,
45+
};
46+
47+
Options
48+
-------
49+
50+
.. option:: AllowExplicitZeroFirstInitialValue
51+
52+
If set to `false`, the first enumerator must not be explicitly initialized.
53+
See examples below. Default is `true`.
54+
55+
.. code-block:: c++
56+
57+
enum A {
58+
e0 = 0, // not allowed if AllowExplicitZeroFirstInitialValue is false
59+
e1,
60+
e2,
61+
};
62+
63+
64+
.. option:: AllowExplicitSequentialInitialValues
65+
66+
If set to `false`, sequential initializations are not allowed.
67+
See examples below. Default is `true`.
68+
69+
.. code-block:: c++
70+
71+
enum A {
72+
e0 = 1, // not allowed if AllowExplicitSequentialInitialValues is false
73+
e1 = 2,
74+
e2 = 3,
75+
};

0 commit comments

Comments
 (0)