[clang-tools-extra] [clang-tidy] readability-enum-initial-value: add AllowConsecutiveInitialValuesExceptLast (PR #228399)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Oct 2 04:28:51 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-tidy
Author: garima-gupta511
<details>
<summary>Changes</summary>
[clang-tidy] readability-enum-initial-value: add AllowConsecutiveInitialValuesExceptLast
Add an opt-in option to allow enums where all enumerators except the last have explicit consecutive integer-literal initializers and the last enumerator is implicitly initialized.
For example:
enum E {
A = 10,
B = 11,
C = 12,
Size,
};
This pattern is commonly used when the final enumerator represents a count or sentinel value.
The new option is disabled by default so that the existing diagnostic behavior is unchanged unless the option is explicitly enabled.
---
Full diff: https://github.com/llvm/llvm-project/pull/228399.diff
6 Files Affected:
- (modified) clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.cpp (+45-7)
- (modified) clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.h (+1)
- (modified) clang-tools-extra/docs/ReleaseNotes.md (+8)
- (modified) clang-tools-extra/docs/clang-tidy/checks/readability/enum-initial-value.md (+17)
- (added) clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value-allow-consecutive.cpp (+73)
- (modified) clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value.cpp (+10)
``````````diff
diff --git a/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.cpp b/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.cpp
index 620c5bece4ebad..e42429e818de84 100644
--- a/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.cpp
+++ b/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.cpp
@@ -15,6 +15,7 @@
#include "clang/Basic/SourceLocation.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/SmallVector.h"
using namespace clang::ast_matchers;
@@ -74,6 +75,34 @@ static bool isInitializedByLiteral(const EnumConstantDecl *Enumerator) {
return Init->isIntegerConstantExpr(Enumerator->getASTContext());
}
+/// Returns true if all but the last enumerator are explicitly initialized
+/// with consecutive (potentially negated) integer literal values and the
+/// last enumerator's value is left implicit, e.g.
+/// enum E { A = 10, B = 11, C = 12, Size };
+static bool hasConsecutiveInitialValuesExceptLast(const EnumDecl &Node) {
+ const llvm::SmallVector<const EnumConstantDecl *> Enumerators(
+ Node.enumerator_begin(), Node.enumerator_end());
+ if (Enumerators.size() < 2)
+ return false;
+
+ const EnumConstantDecl *const LastEnumerator = Enumerators.back();
+ if (LastEnumerator->getInitExpr() != nullptr)
+ return false;
+
+ const EnumConstantDecl *const FirstEnumerator = Enumerators.front();
+ if (!isInitializedByLiteral(FirstEnumerator))
+ return false;
+
+ llvm::APSInt PrevValue = FirstEnumerator->getInitVal();
+ for (size_t I = 1, E = Enumerators.size() - 1; I < E; ++I) {
+ const EnumConstantDecl *const Enumerator = Enumerators[I];
+ if (!isInitializedByLiteral(Enumerator) ||
+ Enumerator->getInitVal() != ++PrevValue)
+ return false;
+ }
+ return LastEnumerator->getInitVal() == ++PrevValue;
+}
+
static void cleanInitialValue(const DiagnosticBuilder &Diag,
const EnumConstantDecl *ECD,
const SourceManager &SM,
@@ -101,10 +130,13 @@ AST_MATCHER(EnumDecl, isMacro) {
return Loc.isMacroID();
}
-AST_MATCHER_P(EnumDecl, hasConsistentInitialValues, bool, AllowSelfRefs) {
+AST_MATCHER_P2(EnumDecl, hasConsistentInitialValues, bool, AllowSelfRefs, bool,
+ AllowConsecutiveExceptLast) {
return isNoneEnumeratorsInitialized(Node, AllowSelfRefs) ||
isOnlyFirstEnumeratorInitialized(Node, AllowSelfRefs) ||
- areAllEnumeratorsInitialized(Node);
+ areAllEnumeratorsInitialized(Node) ||
+ (AllowConsecutiveExceptLast &&
+ hasConsecutiveInitialValuesExceptLast(Node));
}
AST_MATCHER_P(EnumDecl, hasZeroInitialValueForFirstEnumerator, bool,
@@ -164,7 +196,9 @@ EnumInitialValueCheck::EnumInitialValueCheck(StringRef Name,
AllowExplicitSequentialInitialValues(
Options.get("AllowExplicitSequentialInitialValues", true)),
AllowReferencedInitialValues(
- Options.get("AllowReferencedInitialValues", false)) {}
+ Options.get("AllowReferencedInitialValues", false)),
+ AllowConsecutiveInitialValuesExceptLast(
+ Options.get("AllowConsecutiveInitialValuesExceptLast", false)) {}
void EnumInitialValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "AllowExplicitZeroFirstInitialValue",
@@ -173,14 +207,18 @@ void EnumInitialValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
AllowExplicitSequentialInitialValues);
Options.store(Opts, "AllowReferencedInitialValues",
AllowReferencedInitialValues);
+ Options.store(Opts, "AllowConsecutiveInitialValuesExceptLast",
+ AllowConsecutiveInitialValuesExceptLast);
}
void EnumInitialValueCheck::registerMatchers(MatchFinder *Finder) {
const bool AllowSelfRefs = AllowReferencedInitialValues;
- Finder->addMatcher(enumDecl(isDefinition(), unless(isMacro()),
- unless(hasConsistentInitialValues(AllowSelfRefs)))
- .bind("inconsistent"),
- this);
+ Finder->addMatcher(
+ enumDecl(isDefinition(), unless(isMacro()),
+ unless(hasConsistentInitialValues(
+ AllowSelfRefs, AllowConsecutiveInitialValuesExceptLast)))
+ .bind("inconsistent"),
+ this);
if (!AllowExplicitZeroFirstInitialValue)
Finder->addMatcher(
enumDecl(isDefinition(),
diff --git a/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.h b/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.h
index 155818869d28b3..65f3406db0fee4 100644
--- a/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.h
+++ b/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.h
@@ -32,6 +32,7 @@ class EnumInitialValueCheck : public ClangTidyCheck {
const bool AllowExplicitZeroFirstInitialValue;
const bool AllowExplicitSequentialInitialValues;
const bool AllowReferencedInitialValues;
+ const bool AllowConsecutiveInitialValuesExceptLast;
};
} // namespace clang::tidy::readability
diff --git a/clang-tools-extra/docs/ReleaseNotes.md b/clang-tools-extra/docs/ReleaseNotes.md
index c2ee1b06c43e06..704f17b4ad5e20 100644
--- a/clang-tools-extra/docs/ReleaseNotes.md
+++ b/clang-tools-extra/docs/ReleaseNotes.md
@@ -272,6 +272,14 @@ infrastructure are described first, followed by tool-specific sections.
`INT09-C-EX1` exception, allowing enumerators initialized by referencing
another enumerator in the same enum (e.g., `last = first`).
+- Improved {doc}`readability-enum-initial-value
+ <clang-tidy/checks/readability/enum-initial-value>` check by adding
+ the {option}`AllowConsecutiveInitialValuesExceptLast` option, allowing
+ enums where all but the last enumerator are explicitly initialized with
+ consecutive integer literal values and the last enumerator's value is
+ left implicit (e.g., a trailing count/sentinel enumerator). This option
+ defaults to `false`, so the check's default behavior is unchanged.
+
- Improved {doc}`readability-function-cognitive-complexity
<clang-tidy/checks/readability/function-cognitive-complexity>` check by fixing
a crash when checking a function declared with the `alias` attribute.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/enum-initial-value.md b/clang-tools-extra/docs/clang-tidy/checks/readability/enum-initial-value.md
index aec3af8c11eec2..de1db9a8991e0f 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/readability/enum-initial-value.md
+++ b/clang-tools-extra/docs/clang-tidy/checks/readability/enum-initial-value.md
@@ -106,3 +106,20 @@ enum H {
};
```
````
+
+````{option} AllowConsecutiveInitialValuesExceptLast
+When `true`, enums where all but the last enumerator are explicitly
+initialized with consecutive integer literal values, and the last
+enumerator is left uninitialized, are allowed. This is a common pattern
+for enumerations that use the last enumerator as a count/sentinel value.
+Default is `false`.
+
+```c++
+enum I {
+ i0 = 0,
+ i1 = 1,
+ i2 = 2,
+ i_size, // Allowed if AllowConsecutiveInitialValuesExceptLast is true.
+};
+```
+````
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value-allow-consecutive.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value-allow-consecutive.cpp
new file mode 100644
index 00000000000000..d739ab30fed54b
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/enum-initial-value-allow-consecutive.cpp
@@ -0,0 +1,73 @@
+// RUN: %check_clang_tidy %s readability-enum-initial-value %t -- \
+// RUN: -config='{CheckOptions: { \
+// RUN: readability-enum-initial-value.AllowConsecutiveInitialValuesExceptLast: true, \
+// RUN: }}'
+
+// OK: consecutive except last, starting at zero.
+enum class EConsecutive {
+ EConsecutive_a = 0,
+ EConsecutive_b = 1,
+ EConsecutive_c = 2,
+ EConsecutive_size,
+};
+
+// OK: consecutive except last, with a non-zero starting offset.
+enum class EConsecutiveOffset {
+ EConsecutiveOffset_a = 10,
+ EConsecutiveOffset_b = 11,
+ EConsecutiveOffset_c = 12,
+ EConsecutiveOffset_size,
+};
+
+// OK: consecutive except last, with negative values.
+enum class EConsecutiveNegative {
+ EConsecutiveNegative_a = -2,
+ EConsecutiveNegative_b = -1,
+ EConsecutiveNegative_c = 0,
+ EConsecutiveNegative_size,
+};
+
+// OK: minimal case, only two enumerators.
+enum class EConsecutiveMinimal {
+ EConsecutiveMinimal_a = 5,
+ EConsecutiveMinimal_size,
+};
+
+// Error: not consecutive (gap between b and c).
+enum class EConsecutiveBreak {
+ // CHECK-MESSAGES: :[[@LINE-1]]:1: warning: initial values in enum 'EConsecutiveBreak' are not consistent
+ EConsecutiveBreak_a = 0,
+ EConsecutiveBreak_b = 1,
+ EConsecutiveBreak_c = 3,
+ EConsecutiveBreak_size,
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: note: uninitialized enumerator 'EConsecutiveBreak_size' defined here
+ // CHECK-FIXES: EConsecutiveBreak_size = 4,
+};
+
+// Error: one of the "except last" enumerators is left uninitialized, so the
+// remaining explicit values are not consecutive with each other.
+enum class EConsecutiveGap {
+ // CHECK-MESSAGES: :[[@LINE-1]]:1: warning: initial values in enum 'EConsecutiveGap' are not consistent
+ EConsecutiveGap_a = 0,
+ EConsecutiveGap_b,
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: note: uninitialized enumerator 'EConsecutiveGap_b' defined here
+ // CHECK-FIXES: EConsecutiveGap_b = 1,
+ EConsecutiveGap_c = 2,
+ EConsecutiveGap_size,
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: note: uninitialized enumerator 'EConsecutiveGap_size' defined here
+ // CHECK-FIXES: EConsecutiveGap_size = 3,
+};
+
+// Error: the last enumerator is also explicitly initialized (the
+// "consecutive except last" pattern requires the last enumerator to be
+// implicit), and not every other enumerator is explicit either, so this
+// matches none of the accepted styles.
+enum class EConsecutiveLastMismatch {
+ // CHECK-MESSAGES: :[[@LINE-1]]:1: warning: initial values in enum 'EConsecutiveLastMismatch' are not consistent
+ EConsecutiveLastMismatch_a = 0,
+ EConsecutiveLastMismatch_b = 1,
+ EConsecutiveLastMismatch_c,
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: note: uninitialized enumerator 'EConsecutiveLastMismatch_c' defined here
+ // CHECK-FIXES: EConsecutiveLastMismatch_c = 2,
+ EConsecutiveLastMismatch_last = 10,
+};
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 abbd33a1dd001c..0f4eac71a7705a 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
@@ -37,3 +37,13 @@ enum class ERef {
// CHECK-FIXES: ERef_b = 1,
ERef_last = ERef_b,
};
+
+enum class EConsecutiveExceptLast {
+ // CHECK-MESSAGES: :[[@LINE-1]]:1: warning: initial values in enum 'EConsecutiveExceptLast' are not consistent
+ EConsecutiveExceptLast_a = 10,
+ EConsecutiveExceptLast_b = 11,
+ EConsecutiveExceptLast_c = 12,
+ EConsecutiveExceptLast_size,
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: note: uninitialized enumerator 'EConsecutiveExceptLast_size' defined here
+ // CHECK-FIXES: EConsecutiveExceptLast_size = 13,
+};
``````````
</details>
https://github.com/llvm/llvm-project/pull/228399
More information about the cfe-commits
mailing list