[clang-tools-extra] [clang-tidy] Add AllowExplicitReferencedInitialValues to `readability-enum-initial-value` (PR #189459)
Björn Svensson via cfe-commits
cfe-commits at lists.llvm.org
Fri Jun 26 04:14:17 PDT 2026
https://github.com/bjosv updated https://github.com/llvm/llvm-project/pull/189459
>From dc1369f688bf63d9ed5492014b7372449262db48 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Svensson?= <bjorn.a.svensson at est.tech>
Date: Mon, 30 Mar 2026 08:19:18 +0000
Subject: [PATCH 1/4] Add AllowExplicitReferencedInitialValues to
readability-enum-initial-value
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Add a new option that allows enumerators initialized by referencing
another enumerator in the same enum (e.g., last = first). This
implements the INT09-C-EX1 exception from the CERT C Coding Standard.
The three AST matchers are converted from AST_MATCHER to AST_MATCHER_P
to accept a bool AllowRefs parameter. The existing helper functions
(isNoneEnumeratorsInitialized, isOnlyFirstEnumeratorInitialized,
areAllEnumeratorsInitialized) are extended to skip self-referencing
enumerators when AllowRefs is set.
Signed-off-by: Björn Svensson <bjorn.a.svensson at est.tech>
---
.../readability/EnumInitialValueCheck.cpp | 73 ++++++++++++++-----
.../readability/EnumInitialValueCheck.h | 5 +-
clang-tools-extra/docs/ReleaseNotes.rst | 3 +
.../checks/readability/enum-initial-value.rst | 17 +++++
.../enum-initial-value-allow-refs-combined.c | 34 +++++++++
.../enum-initial-value-allow-refs.c | 50 +++++++++++++
.../enum-initial-value-allow-refs.cpp | 35 +++++++++
.../checkers/readability/enum-initial-value.c | 10 +++
.../readability/enum-initial-value.cpp | 9 +++
9 files changed, 215 insertions(+), 21 deletions(-)
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value-allow-refs-combined.c
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value-allow-refs.c
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value-allow-refs.cpp
diff --git a/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.cpp b/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.cpp
index 4612d94f3663b..1a6bf28dbaf35 100644
--- a/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.cpp
+++ b/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.cpp
@@ -20,15 +20,39 @@ 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;
+/// Check if \p ECD is initialized by referencing another enumerator in the
+/// same enum (e.g., `last = first`).
+static bool isSelfReference(const EnumConstantDecl *ECD) {
+ const Expr *Init = ECD->getInitExpr();
+ if (!Init)
+ return false;
+ const auto *CE = dyn_cast<ConstantExpr>(Init);
+ if (!CE)
+ return false;
+ const auto *DRE = dyn_cast<DeclRefExpr>(CE->getSubExpr());
+ if (!DRE)
+ return false;
+ const auto *RefECD = dyn_cast<EnumConstantDecl>(DRE->getDecl());
+ return RefECD && RefECD->getDeclContext() == ECD->getDeclContext();
+}
+
+static bool isAllowedReference(const EnumConstantDecl *ECD, bool AllowRefs) {
+ return AllowRefs && isSelfReference(ECD);
+}
+
+static bool isNoneEnumeratorsInitialized(const EnumDecl &Node, bool AllowRefs) {
+ return llvm::all_of(Node.enumerators(), [AllowRefs](
+ const EnumConstantDecl *ECD) {
+ return isAllowedReference(ECD, AllowRefs) || ECD->getInitExpr() == nullptr;
});
}
-static bool isOnlyFirstEnumeratorInitialized(const EnumDecl &Node) {
+static bool isOnlyFirstEnumeratorInitialized(const EnumDecl &Node,
+ bool AllowRefs) {
bool IsFirst = true;
for (const EnumConstantDecl *ECD : Node.enumerators()) {
+ if (isAllowedReference(ECD, AllowRefs))
+ continue;
if ((IsFirst && ECD->getInitExpr() == nullptr) ||
(!IsFirst && ECD->getInitExpr() != nullptr))
return false;
@@ -37,9 +61,10 @@ static bool isOnlyFirstEnumeratorInitialized(const EnumDecl &Node) {
return !IsFirst;
}
-static bool areAllEnumeratorsInitialized(const EnumDecl &Node) {
- return llvm::all_of(Node.enumerators(), [](const EnumConstantDecl *ECD) {
- return ECD->getInitExpr() != nullptr;
+static bool areAllEnumeratorsInitialized(const EnumDecl &Node, bool AllowRefs) {
+ return llvm::all_of(Node.enumerators(), [AllowRefs](
+ const EnumConstantDecl *ECD) {
+ return isAllowedReference(ECD, AllowRefs) || ECD->getInitExpr() != nullptr;
});
}
@@ -79,18 +104,19 @@ AST_MATCHER(EnumDecl, isMacro) {
return Loc.isMacroID();
}
-AST_MATCHER(EnumDecl, hasConsistentInitialValues) {
- return isNoneEnumeratorsInitialized(Node) ||
- isOnlyFirstEnumeratorInitialized(Node) ||
- areAllEnumeratorsInitialized(Node);
+AST_MATCHER_P(EnumDecl, hasConsistentInitialValues, bool, AllowRefs) {
+ return isNoneEnumeratorsInitialized(Node, AllowRefs) ||
+ isOnlyFirstEnumeratorInitialized(Node, AllowRefs) ||
+ areAllEnumeratorsInitialized(Node, AllowRefs);
}
-AST_MATCHER(EnumDecl, hasZeroInitialValueForFirstEnumerator) {
+AST_MATCHER_P(EnumDecl, hasZeroInitialValueForFirstEnumerator, bool,
+ AllowRefs) {
const EnumDecl::enumerator_range Enumerators = Node.enumerators();
if (Enumerators.empty())
return false;
const EnumConstantDecl *ECD = *Enumerators.begin();
- return isOnlyFirstEnumeratorInitialized(Node) &&
+ return isOnlyFirstEnumeratorInitialized(Node, AllowRefs) &&
isInitializedByLiteral(ECD) && ECD->getInitVal().isZero();
}
@@ -101,7 +127,7 @@ AST_MATCHER(EnumDecl, hasZeroInitialValueForFirstEnumerator) {
/// 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) {
+AST_MATCHER_P(EnumDecl, hasSequentialInitialValues, bool, AllowRefs) {
const EnumDecl::enumerator_range Enumerators = Node.enumerators();
if (Enumerators.empty())
return false;
@@ -111,6 +137,8 @@ AST_MATCHER(EnumDecl, hasSequentialInitialValues) {
return false;
bool AllEnumeratorsArePowersOfTwo = true;
for (const EnumConstantDecl *Enumerator : llvm::drop_begin(Enumerators)) {
+ if (isAllowedReference(Enumerator, AllowRefs))
+ continue;
const llvm::APSInt NewValue = Enumerator->getInitVal();
if (NewValue != ++PrevValue)
return false;
@@ -137,28 +165,34 @@ EnumInitialValueCheck::EnumInitialValueCheck(StringRef Name,
AllowExplicitZeroFirstInitialValue(
Options.get("AllowExplicitZeroFirstInitialValue", true)),
AllowExplicitSequentialInitialValues(
- Options.get("AllowExplicitSequentialInitialValues", true)) {}
+ Options.get("AllowExplicitSequentialInitialValues", true)),
+ AllowExplicitReferencedInitialValues(
+ Options.get("AllowExplicitReferencedInitialValues", false)) {}
void EnumInitialValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "AllowExplicitZeroFirstInitialValue",
AllowExplicitZeroFirstInitialValue);
Options.store(Opts, "AllowExplicitSequentialInitialValues",
AllowExplicitSequentialInitialValues);
+ Options.store(Opts, "AllowExplicitReferencedInitialValues",
+ AllowExplicitReferencedInitialValues);
}
void EnumInitialValueCheck::registerMatchers(MatchFinder *Finder) {
+ const bool AllowRefs = AllowExplicitReferencedInitialValues;
Finder->addMatcher(enumDecl(isDefinition(), unless(isMacro()),
- unless(hasConsistentInitialValues()))
+ unless(hasConsistentInitialValues(AllowRefs)))
.bind("inconsistent"),
this);
if (!AllowExplicitZeroFirstInitialValue)
Finder->addMatcher(
- enumDecl(isDefinition(), hasZeroInitialValueForFirstEnumerator())
+ enumDecl(isDefinition(),
+ hasZeroInitialValueForFirstEnumerator(AllowRefs))
.bind("zero_first"),
this);
if (!AllowExplicitSequentialInitialValues)
Finder->addMatcher(enumDecl(isDefinition(), unless(isMacro()),
- hasSequentialInitialValues())
+ hasSequentialInitialValues(AllowRefs))
.bind("sequential"),
this);
}
@@ -215,7 +249,8 @@ void EnumInitialValueCheck::check(const MatchFinder::MatchResult &Result) {
"sequential initial value in '%0' can be ignored")
<< getName(Enum);
for (const EnumConstantDecl *ECD : llvm::drop_begin(Enum->enumerators()))
- cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts());
+ if (!isAllowedReference(ECD, AllowExplicitReferencedInitialValues))
+ cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts());
return;
}
}
diff --git a/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.h b/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.h
index d03e26b4468f4..e2b9e6d8703f9 100644
--- a/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.h
+++ b/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.h
@@ -13,8 +13,8 @@
namespace clang::tidy::readability {
-/// Enforces consistent style for enumerators' initialization, covering three
-/// styles: none, first only, or all initialized explicitly.
+/// Enforces consistent style for enumerators' initialization, covering four
+/// styles: none, first only, all initialized explicitly, or allow referenced.
///
/// For the user-facing documentation see:
/// https://clang.llvm.org/extra/clang-tidy/checks/readability/enum-initial-value.html
@@ -31,6 +31,7 @@ class EnumInitialValueCheck : public ClangTidyCheck {
private:
const bool AllowExplicitZeroFirstInitialValue;
const bool AllowExplicitSequentialInitialValues;
+ const bool AllowExplicitReferencedInitialValues;
};
} // namespace clang::tidy::readability
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index b1fd95db9e395..0d17b0ee3dde9 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -421,6 +421,9 @@ Changes in existing checks
<clang-tidy/checks/readability/enum-initial-value>` check: the warning message
now uses separate note diagnostics for each uninitialized enumerator, making
it easier to see which specific enumerators need explicit initialization.
+ Added :option:`AllowExplicitReferencedInitialValues` option to support the
+ `INT09-C-EX1` exception, allowing enumerators initialized by referencing
+ another enumerator in the same enum (e.g., ``last = first``).
- Improved :doc:`readability-implicit-bool-conversion
<clang-tidy/checks/readability/implicit-bool-conversion>` check:
diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/enum-initial-value.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/enum-initial-value.rst
index f59c433c51d0b..94b78af70a5e1 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/readability/enum-initial-value.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/readability/enum-initial-value.rst
@@ -92,3 +92,20 @@ Options
g0 = 1, // Not allowed if AllowExplicitSequentialInitialValues is false.
g1 = 2,
g2 = 3,
+ };
+
+.. option:: AllowExplicitReferencedInitialValues
+
+ If set to `true`, enumerators initialized by referencing another enumerator
+ in the same enum are allowed, and the remaining enumerators are checked for
+ consistency. This implements the ``INT09-C-EX1`` exception from the CERT C
+ Coding Standard.
+ Default is `false`.
+
+ .. code-block:: c++
+
+ enum H {
+ h0,
+ h1,
+ h2 = h1, // Allowed if AllowExplicitReferencedInitialValues is true.
+ };
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value-allow-refs-combined.c b/clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value-allow-refs-combined.c
new file mode 100644
index 0000000000000..715229a9b78f9
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value-allow-refs-combined.c
@@ -0,0 +1,34 @@
+// RUN: %check_clang_tidy %s readability-enum-initial-value %t -- \
+// RUN: -config='{CheckOptions: { \
+// RUN: readability-enum-initial-value.AllowExplicitZeroFirstInitialValue: false, \
+// RUN: readability-enum-initial-value.AllowExplicitSequentialInitialValues: false, \
+// RUN: readability-enum-initial-value.AllowExplicitReferencedInitialValues: true, \
+// RUN: }}'
+
+// Error: zero-first + self-ref, should still warn about the zero.
+enum EZeroRef {
+ EZeroRef_a = 0,
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: zero initial value for the first enumerator in 'EZeroRef' can be disregarded
+ // CHECK-FIXES: EZeroRef_a ,
+ EZeroRef_b,
+ EZeroRef_last = EZeroRef_b,
+};
+
+// Error: sequential + self-ref, should still warn but not suggest
+// removing the self-ref.
+enum ESeqRef {
+ // CHECK-MESSAGES: :[[@LINE-1]]:1: warning: sequential initial value in 'ESeqRef' can be ignored
+ ESeqRef_a = 1,
+ ESeqRef_b = 2,
+ // CHECK-FIXES: ESeqRef_b ,
+ ESeqRef_c = 3,
+ // CHECK-FIXES: ESeqRef_c ,
+ ESeqRef_alias = ESeqRef_a,
+};
+
+// OK: none + self-ref, no warnings.
+enum ENoneRef {
+ ENoneRef_a,
+ ENoneRef_b,
+ ENoneRef_last = ENoneRef_b,
+};
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value-allow-refs.c b/clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value-allow-refs.c
new file mode 100644
index 0000000000000..96017524b6059
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value-allow-refs.c
@@ -0,0 +1,50 @@
+// RUN: %check_clang_tidy %s readability-enum-initial-value %t -- \
+// RUN: -config='{CheckOptions: { \
+// RUN: readability-enum-initial-value.AllowExplicitReferencedInitialValues: true, \
+// RUN: }}'
+
+// OK: none + self-ref.
+enum ERef1 {
+ ERef1_a,
+ ERef1_b,
+ ERef1_last = ERef1_b,
+};
+
+// OK: first-only + self-ref.
+enum ERef2 {
+ ERef2_a = 0,
+ ERef2_b,
+ ERef2_last = ERef2_b,
+};
+
+// OK: all + self-ref.
+enum ERef3 {
+ ERef3_a = 0,
+ ERef3_b = 1,
+ ERef3_last = ERef3_b,
+};
+
+// Error: cross-enum reference is not a self-ref.
+enum ERefOther {
+ ERefOther_a,
+ ERefOther_b,
+};
+enum ERefCross {
+ // CHECK-MESSAGES: :[[@LINE-1]]:1: warning: initial values in enum 'ERefCross' are not consistent
+ ERefCross_a,
+ // CHECK-FIXES: ERefCross_a = 0,
+ ERefCross_b,
+ // CHECK-FIXES: ERefCross_b = 1,
+ ERefCross_last = ERefOther_b,
+};
+
+// Error: inconsistent even after ignoring self-refs.
+enum ERefBad {
+ // CHECK-MESSAGES: :[[@LINE-1]]:1: warning: initial values in enum 'ERefBad' are not consistent
+ ERefBad_a = 1,
+ ERefBad_b,
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: note: uninitialized enumerator 'ERefBad_b' defined here
+ // CHECK-FIXES: ERefBad_b = 2,
+ ERefBad_c = 5,
+ ERefBad_alias = ERefBad_a,
+};
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value-allow-refs.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value-allow-refs.cpp
new file mode 100644
index 0000000000000..c0b48b4a78331
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value-allow-refs.cpp
@@ -0,0 +1,35 @@
+// RUN: %check_clang_tidy %s readability-enum-initial-value %t -- \
+// RUN: -config='{CheckOptions: { \
+// RUN: readability-enum-initial-value.AllowExplicitReferencedInitialValues: true, \
+// RUN: }}'
+
+// OK: none + self-ref.
+enum class ERef {
+ ERef_a,
+ ERef_b,
+ ERef_last = ERef_b,
+};
+
+// OK: first-only + self-ref.
+enum class ERefFirst {
+ ERefFirst_a = 1,
+ ERefFirst_b,
+ ERefFirst_alias = ERefFirst_a,
+};
+
+// OK: all + self-ref.
+enum class ERefAll {
+ ERefAll_a = 0,
+ ERefAll_b = 1,
+ ERefAll_last = ERefAll_b,
+};
+
+// Error: literal duplicate (not a reference).
+enum class ERefErr {
+ // CHECK-MESSAGES: :[[@LINE-1]]:1: warning: initial values in enum 'ERefErr' are not consistent
+ ERefErr_a,
+ // CHECK-FIXES: ERefErr_a = 0,
+ ERefErr_b,
+ // CHECK-FIXES: ERefErr_b = 1,
+ ERef_last = 1,
+};
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value.c b/clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value.c
index fcf5b20ea4ee2..6242d4c26fe26 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value.c
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value.c
@@ -34,6 +34,16 @@ enum EAll {
EAll_c = 4,
};
+enum ERef {
+ // CHECK-MESSAGES: :[[@LINE-1]]:1: warning: initial values in enum 'ERef' are not consistent
+ // CHECK-MESSAGES-ENABLE: :[[@LINE-2]]:1: warning: initial values in enum 'ERef' are not consistent
+ ERef_a,
+ // CHECK-FIXES: ERef_a = 0,
+ ERef_b,
+ // CHECK-FIXES: ERef_b = 1,
+ ERef_last = ERef_b,
+};
+
#define ENUMERATOR_1 EMacro1_b
enum EMacro1 {
// CHECK-MESSAGES: :[[@LINE-1]]:1: warning: initial values in enum 'EMacro1' are not consistent, consider explicit initialization of all, none or only the first enumerator
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value.cpp
index 7a97534fd8e2b..3c9b24e54338e 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value.cpp
@@ -26,3 +26,12 @@ enum class EAll {
EAll_b = 2,
EAll_c = 3,
};
+
+enum class ERef {
+ // CHECK-MESSAGES: :[[@LINE-1]]:1: warning: initial values in enum 'ERef' are not consistent
+ ERef_a,
+ // CHECK-FIXES: ERef_a = 0,
+ ERef_b,
+ // CHECK-FIXES: ERef_b = 1,
+ ERef_last = ERef_b,
+};
>From 0efaf2d751b51f48fce0cf4e1a27034ce3f058c8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Svensson?= <bjorn.a.svensson at est.tech>
Date: Mon, 30 Mar 2026 23:04:15 +0200
Subject: [PATCH 2/4] fixup: use single backtick in docs and list changes in
ReleaseNotes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Björn Svensson <bjorn.a.svensson at est.tech>
---
clang-tools-extra/docs/ReleaseNotes.rst | 15 +++++++++------
.../checks/readability/enum-initial-value.rst | 2 +-
2 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 0d17b0ee3dde9..c317aa26b9f6b 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -418,12 +418,15 @@ Changes in existing checks
``[[noreturn]]`` functions.
- Improved :doc:`readability-enum-initial-value
- <clang-tidy/checks/readability/enum-initial-value>` check: the warning message
- now uses separate note diagnostics for each uninitialized enumerator, making
- it easier to see which specific enumerators need explicit initialization.
- Added :option:`AllowExplicitReferencedInitialValues` option to support the
- `INT09-C-EX1` exception, allowing enumerators initialized by referencing
- another enumerator in the same enum (e.g., ``last = first``).
+ <clang-tidy/checks/readability/enum-initial-value>` check:
+
+ - The warning message now uses separate note diagnostics for each
+ uninitialized enumerator, making it easier to see which specific
+ enumerators need explicit initialization.
+
+ - Added :option:`AllowExplicitReferencedInitialValues` option to support
+ the `INT09-C-EX1` exception, allowing enumerators initialized by
+ referencing another enumerator in the same enum (e.g., ``last = first``).
- Improved :doc:`readability-implicit-bool-conversion
<clang-tidy/checks/readability/implicit-bool-conversion>` check:
diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/enum-initial-value.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/enum-initial-value.rst
index 94b78af70a5e1..4d48201e0415d 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/readability/enum-initial-value.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/readability/enum-initial-value.rst
@@ -98,7 +98,7 @@ Options
If set to `true`, enumerators initialized by referencing another enumerator
in the same enum are allowed, and the remaining enumerators are checked for
- consistency. This implements the ``INT09-C-EX1`` exception from the CERT C
+ consistency. This implements the `INT09-C-EX1` exception from the CERT C
Coding Standard.
Default is `false`.
>From 980a1bd2dd27487f95bba76ee2d14f427e26c4a5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Svensson?= <bjorn.a.svensson at est.tech>
Date: Tue, 31 Mar 2026 09:48:35 +0200
Subject: [PATCH 3/4] fixup: update ReleaseNotes by removing tag and use
80-char fill
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Björn Svensson <bjorn.a.svensson at est.tech>
---
clang-tools-extra/docs/ReleaseNotes.rst | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index c317aa26b9f6b..670d5d288944c 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -421,12 +421,12 @@ Changes in existing checks
<clang-tidy/checks/readability/enum-initial-value>` check:
- The warning message now uses separate note diagnostics for each
- uninitialized enumerator, making it easier to see which specific
- enumerators need explicit initialization.
+ uninitialized enumerator, making it easier to see which specific enumerators
+ need explicit initialization.
- - Added :option:`AllowExplicitReferencedInitialValues` option to support
- the `INT09-C-EX1` exception, allowing enumerators initialized by
- referencing another enumerator in the same enum (e.g., ``last = first``).
+ - Added `AllowExplicitReferencedInitialValues` option to support the
+ `INT09-C-EX1` exception, allowing enumerators initialized by referencing
+ another enumerator in the same enum (e.g., ``last = first``).
- Improved :doc:`readability-implicit-bool-conversion
<clang-tidy/checks/readability/implicit-bool-conversion>` check:
>From c7487ae49d270b2b8808bbc62f83f2b7c42ce2e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Svensson?= <bjorn.a.svensson at est.tech>
Date: Fri, 26 Jun 2026 12:13:17 +0200
Subject: [PATCH 4/4] fixup: use dyn_cast_if_present and improve naming
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Björn Svensson <bjorn.a.svensson at est.tech>
---
.../readability/EnumInitialValueCheck.cpp | 74 +++++++++----------
1 file changed, 37 insertions(+), 37 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.cpp b/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.cpp
index 1a6bf28dbaf35..7745825f88a4a 100644
--- a/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.cpp
+++ b/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.cpp
@@ -23,35 +23,33 @@ namespace clang::tidy::readability {
/// Check if \p ECD is initialized by referencing another enumerator in the
/// same enum (e.g., `last = first`).
static bool isSelfReference(const EnumConstantDecl *ECD) {
- const Expr *Init = ECD->getInitExpr();
- if (!Init)
- return false;
- const auto *CE = dyn_cast<ConstantExpr>(Init);
- if (!CE)
- return false;
- const auto *DRE = dyn_cast<DeclRefExpr>(CE->getSubExpr());
- if (!DRE)
- return false;
- const auto *RefECD = dyn_cast<EnumConstantDecl>(DRE->getDecl());
+ const auto *CE = dyn_cast_if_present<ConstantExpr>(ECD->getInitExpr());
+ const auto *DRE =
+ dyn_cast_if_present<DeclRefExpr>(CE ? CE->getSubExpr() : nullptr);
+ const auto *RefECD =
+ dyn_cast_if_present<EnumConstantDecl>(DRE ? DRE->getDecl() : nullptr);
return RefECD && RefECD->getDeclContext() == ECD->getDeclContext();
}
-static bool isAllowedReference(const EnumConstantDecl *ECD, bool AllowRefs) {
- return AllowRefs && isSelfReference(ECD);
+static bool isAllowedSelfReference(const EnumConstantDecl *ECD,
+ bool AllowSelfRefs) {
+ return AllowSelfRefs && isSelfReference(ECD);
}
-static bool isNoneEnumeratorsInitialized(const EnumDecl &Node, bool AllowRefs) {
- return llvm::all_of(Node.enumerators(), [AllowRefs](
- const EnumConstantDecl *ECD) {
- return isAllowedReference(ECD, AllowRefs) || ECD->getInitExpr() == nullptr;
- });
+static bool isNoneEnumeratorsInitialized(const EnumDecl &Node,
+ bool AllowSelfRefs) {
+ return llvm::all_of(Node.enumerators(),
+ [AllowSelfRefs](const EnumConstantDecl *ECD) {
+ return isAllowedSelfReference(ECD, AllowSelfRefs) ||
+ ECD->getInitExpr() == nullptr;
+ });
}
static bool isOnlyFirstEnumeratorInitialized(const EnumDecl &Node,
- bool AllowRefs) {
+ bool AllowSelfRefs) {
bool IsFirst = true;
for (const EnumConstantDecl *ECD : Node.enumerators()) {
- if (isAllowedReference(ECD, AllowRefs))
+ if (isAllowedSelfReference(ECD, AllowSelfRefs))
continue;
if ((IsFirst && ECD->getInitExpr() == nullptr) ||
(!IsFirst && ECD->getInitExpr() != nullptr))
@@ -61,11 +59,13 @@ static bool isOnlyFirstEnumeratorInitialized(const EnumDecl &Node,
return !IsFirst;
}
-static bool areAllEnumeratorsInitialized(const EnumDecl &Node, bool AllowRefs) {
- return llvm::all_of(Node.enumerators(), [AllowRefs](
- const EnumConstantDecl *ECD) {
- return isAllowedReference(ECD, AllowRefs) || ECD->getInitExpr() != nullptr;
- });
+static bool areAllEnumeratorsInitialized(const EnumDecl &Node,
+ bool AllowSelfRefs) {
+ return llvm::all_of(Node.enumerators(),
+ [AllowSelfRefs](const EnumConstantDecl *ECD) {
+ return isAllowedSelfReference(ECD, AllowSelfRefs) ||
+ ECD->getInitExpr() != nullptr;
+ });
}
/// Check if \p Enumerator is initialized with a (potentially negated) \c
@@ -104,19 +104,19 @@ AST_MATCHER(EnumDecl, isMacro) {
return Loc.isMacroID();
}
-AST_MATCHER_P(EnumDecl, hasConsistentInitialValues, bool, AllowRefs) {
- return isNoneEnumeratorsInitialized(Node, AllowRefs) ||
- isOnlyFirstEnumeratorInitialized(Node, AllowRefs) ||
- areAllEnumeratorsInitialized(Node, AllowRefs);
+AST_MATCHER_P(EnumDecl, hasConsistentInitialValues, bool, AllowSelfRefs) {
+ return isNoneEnumeratorsInitialized(Node, AllowSelfRefs) ||
+ isOnlyFirstEnumeratorInitialized(Node, AllowSelfRefs) ||
+ areAllEnumeratorsInitialized(Node, AllowSelfRefs);
}
AST_MATCHER_P(EnumDecl, hasZeroInitialValueForFirstEnumerator, bool,
- AllowRefs) {
+ AllowSelfRefs) {
const EnumDecl::enumerator_range Enumerators = Node.enumerators();
if (Enumerators.empty())
return false;
const EnumConstantDecl *ECD = *Enumerators.begin();
- return isOnlyFirstEnumeratorInitialized(Node, AllowRefs) &&
+ return isOnlyFirstEnumeratorInitialized(Node, AllowSelfRefs) &&
isInitializedByLiteral(ECD) && ECD->getInitVal().isZero();
}
@@ -127,7 +127,7 @@ AST_MATCHER_P(EnumDecl, hasZeroInitialValueForFirstEnumerator, bool,
/// 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_P(EnumDecl, hasSequentialInitialValues, bool, AllowRefs) {
+AST_MATCHER_P(EnumDecl, hasSequentialInitialValues, bool, AllowSelfRefs) {
const EnumDecl::enumerator_range Enumerators = Node.enumerators();
if (Enumerators.empty())
return false;
@@ -137,7 +137,7 @@ AST_MATCHER_P(EnumDecl, hasSequentialInitialValues, bool, AllowRefs) {
return false;
bool AllEnumeratorsArePowersOfTwo = true;
for (const EnumConstantDecl *Enumerator : llvm::drop_begin(Enumerators)) {
- if (isAllowedReference(Enumerator, AllowRefs))
+ if (isAllowedSelfReference(Enumerator, AllowSelfRefs))
continue;
const llvm::APSInt NewValue = Enumerator->getInitVal();
if (NewValue != ++PrevValue)
@@ -179,20 +179,20 @@ void EnumInitialValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
}
void EnumInitialValueCheck::registerMatchers(MatchFinder *Finder) {
- const bool AllowRefs = AllowExplicitReferencedInitialValues;
+ const bool AllowSelfRefs = AllowExplicitReferencedInitialValues;
Finder->addMatcher(enumDecl(isDefinition(), unless(isMacro()),
- unless(hasConsistentInitialValues(AllowRefs)))
+ unless(hasConsistentInitialValues(AllowSelfRefs)))
.bind("inconsistent"),
this);
if (!AllowExplicitZeroFirstInitialValue)
Finder->addMatcher(
enumDecl(isDefinition(),
- hasZeroInitialValueForFirstEnumerator(AllowRefs))
+ hasZeroInitialValueForFirstEnumerator(AllowSelfRefs))
.bind("zero_first"),
this);
if (!AllowExplicitSequentialInitialValues)
Finder->addMatcher(enumDecl(isDefinition(), unless(isMacro()),
- hasSequentialInitialValues(AllowRefs))
+ hasSequentialInitialValues(AllowSelfRefs))
.bind("sequential"),
this);
}
@@ -249,7 +249,7 @@ void EnumInitialValueCheck::check(const MatchFinder::MatchResult &Result) {
"sequential initial value in '%0' can be ignored")
<< getName(Enum);
for (const EnumConstantDecl *ECD : llvm::drop_begin(Enum->enumerators()))
- if (!isAllowedReference(ECD, AllowExplicitReferencedInitialValues))
+ if (!isAllowedSelfReference(ECD, AllowExplicitReferencedInitialValues))
cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts());
return;
}
More information about the cfe-commits
mailing list