[clang] [clang] Diagnose invalid/unavailable operator overloads for scoped flag_enums (PR #218290)
Matthias Wippich via cfe-commits
cfe-commits at lists.llvm.org
Mon Aug 24 08:01:24 PDT 2026
https://github.com/Tsche updated https://github.com/llvm/llvm-project/pull/218290
>From 549fb95d33cfd3b85ea8889a165a0129d7a27ede Mon Sep 17 00:00:00 2001
From: Matthias Wippich <mfwippich at gmail.com>
Date: Sun, 23 Aug 2026 23:19:48 +0200
Subject: [PATCH 1/2] [clang] Diagnose invalid/unavailable operator overloads
for scoped flag_enums
---
clang/docs/ReleaseNotes.md | 3 +
.../clang/Basic/DiagnosticSemaKinds.td | 3 +
clang/lib/Sema/Sema.cpp | 74 +++++++++++++++++++
clang/test/Sema/flag-enum.cpp | 62 ++++++++++++++++
4 files changed, 142 insertions(+)
create mode 100644 clang/test/Sema/flag-enum.cpp
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index ca0dbfa2af229..c63c235bd17a2 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -416,6 +416,9 @@ features cannot lower the translation-unit ABI level;
- `-Wc++98-compat` now diagnoses explicit conversion functions in C++20 and
later, matching the behavior in C++11 through C++17. (#GH161689)
+- `-Wflag-enum` now warns if any of the bitwise operators for scoped enumeration types with
+ the `[[clang::flag_enum]]` attribute are not available, ambiguous or deleted.
+
### Improvements to Clang's time-trace
### Improvements to Coverage Mapping
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 3a910c9c3f2b9..1871ca8fa56ec 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -6721,6 +6721,9 @@ def ext_enumerator_increment_too_large : ExtWarn<
def warn_flag_enum_constant_out_of_range : Warning<
"enumeration value %0 is out of range of flags in enumeration type %1">,
InGroup<FlagEnum>;
+def warn_flag_enum_operator : Warning<
+ "%1 is %select{|not available|ambiguous|deleted}2 for flag-like enumeration type %0%select{|: %4}3">,
+ InGroup<FlagEnum>;
def err_vm_decl_in_file_scope : Error<
"variably modified type declaration not allowed at file scope">;
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index f1e328ccba426..b28cfbd5403dc 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -21,6 +21,7 @@
#include "clang/AST/DeclObjC.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
+#include "clang/AST/OperationKinds.h"
#include "clang/AST/PrettyDeclStackTrace.h"
#include "clang/AST/StmtCXX.h"
#include "clang/AST/TypeOrdering.h"
@@ -38,6 +39,7 @@
#include "clang/Sema/Initialization.h"
#include "clang/Sema/MultiplexExternalSemaSource.h"
#include "clang/Sema/ObjCMethodList.h"
+#include "clang/Sema/Overload.h"
#include "clang/Sema/RISCVIntrinsicManager.h"
#include "clang/Sema/Scope.h"
#include "clang/Sema/ScopeInfo.h"
@@ -1200,6 +1202,74 @@ static bool IsRecordFullyDefined(const CXXRecordDecl *RD,
return Complete;
}
+static void DiagnoseInvalidFlagEnumOperators(Sema &S, const EnumDecl *ED) {
+ assert(ED->hasAttr<FlagEnumAttr>() && "not a flag-like enum");
+ if (!ED->isScoped())
+ return;
+
+ QualType T = S.Context.getCanonicalTagType(ED);
+
+ Expr *LHS = new (S.Context) OpaqueValueExpr(SourceLocation(), T, VK_PRValue);
+ Expr *RHS = new (S.Context) OpaqueValueExpr(SourceLocation(), T, VK_PRValue);
+
+ OverloadedOperatorKind OPs[] = {OO_Pipe, OO_Amp, OO_Caret, OO_Tilde};
+ for (const auto OP : OPs) {
+ auto Name = S.Context.DeclarationNames.getCXXOperatorName(OP);
+ LookupResult R(S, Name, SourceLocation(), Sema::LookupOperatorName);
+
+ S.LookupName(R, S.TUScope);
+
+ OverloadCandidateSet CandidateSet{SourceLocation(),
+ OverloadCandidateSet::CSK_Operator};
+
+ SmallVector<Expr *, 2> Args;
+ if (OP == OO_Tilde) {
+ Args = {LHS};
+ S.LookupOverloadedUnaryOp(CandidateSet, OP, R.asUnresolvedSet(), Args);
+ } else {
+ Args = {LHS, RHS};
+ S.LookupOverloadedBinOp(CandidateSet, OP, R.asUnresolvedSet(), Args);
+ }
+
+ OverloadCandidateSet::iterator Best;
+ OverloadingResult Result =
+ CandidateSet.BestViableFunction(S, SourceLocation(), Best);
+
+ switch (Result) {
+ case OR_Success:
+ break;
+ case OR_No_Viable_Function: {
+ S.Diag(ED->getLocation(), diag::warn_flag_enum_operator)
+ << ED->getName() << Name.getAsString() << OR_No_Viable_Function
+ << false << "";
+ auto Cands = CandidateSet.CompleteCandidates(S, OCD_AllCandidates, Args);
+ CandidateSet.NoteCandidates(S, Args, Cands, Name.getAsString());
+ break;
+ }
+ case OR_Ambiguous: {
+ S.Diag(ED->getLocation(), diag::warn_flag_enum_operator)
+ << ED->getName() << Name.getAsString() << OR_Ambiguous << false << "";
+ auto Cands =
+ CandidateSet.CompleteCandidates(S, OCD_AmbiguousCandidates, Args);
+ CandidateSet.NoteCandidates(S, Args, Cands, Name.getAsString());
+ break;
+ }
+ case OR_Deleted: {
+ StringLiteral *Msg = Best->Function->getDeletedMessage();
+
+ CandidateSet.NoteCandidates(
+ PartialDiagnosticAt(ED->getLocation(),
+ S.PDiag(diag::warn_flag_enum_operator)
+ << ED->getName() << Name.getAsString()
+ << OR_Deleted << (Msg != nullptr)
+ << (Msg ? Msg->getString() : "")),
+ S, OCD_AllCandidates, Args, Name.getAsString());
+ break;
+ }
+ }
+ }
+}
+
void Sema::getSortedUnusedLocalTypedefNameCandidates(
SmallVectorImpl<const TypedefNameDecl *> &Sorted) const {
// The candidates are collected while iterating a Scope's SmallPtrSet, so sort
@@ -1712,6 +1782,10 @@ void Sema::ActOnEndOfTranslationUnit() {
}
}
+ for (const auto &[ED, _] : FlagBitsCache)
+ if (!Diags.isIgnored(diag::warn_flag_enum_operator, ED->getLocation()))
+ DiagnoseInvalidFlagEnumOperators(*this, ED);
+
AnalysisWarnings.IssueWarnings(Context.getTranslationUnitDecl());
if (Context.hasAnyFunctionEffects())
diff --git a/clang/test/Sema/flag-enum.cpp b/clang/test/Sema/flag-enum.cpp
new file mode 100644
index 0000000000000..6e95a7c8f6691
--- /dev/null
+++ b/clang/test/Sema/flag-enum.cpp
@@ -0,0 +1,62 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 %s
+
+enum Unscoped { U0 = 1, U1 = 8 };
+enum class Scoped { S0 = 1, S1 = 8 };
+enum [[clang::flag_enum]] UnscopedFlag { D0 = 1, D1 = 8 };
+enum class [[clang::flag_enum]] WithOps { D0 = 1, D1 = 8 };
+enum class [[clang::flag_enum]] WithoutOps { D0 = 1, D1 = 8 };
+// expected-warning at -1 {{operator| is not available for flag-like enumeration type WithoutOps}} \
+// expected-warning at -1 {{operator& is not available for flag-like enumeration type WithoutOps}} \
+// expected-warning at -1 {{operator^ is not available for flag-like enumeration type WithoutOps}} \
+// expected-warning at -1 {{operator~ is not available for flag-like enumeration type WithoutOps}}
+
+WithOps operator|(WithOps L, WithOps R) {
+ return static_cast<WithOps>(static_cast<unsigned>(L) | static_cast<unsigned>(R));
+}
+
+WithOps operator&(WithOps L, WithOps R) {
+ return static_cast<WithOps>(static_cast<unsigned>(L) & static_cast<unsigned>(R));
+}
+
+WithOps operator^(WithOps L, WithOps R) {
+ return static_cast<WithOps>(static_cast<unsigned>(L) ^ static_cast<unsigned>(R));
+}
+
+WithOps operator~(WithOps L) {
+ return static_cast<WithOps>(~static_cast<unsigned>(L));
+}
+
+namespace test {
+enum class [[clang::flag_enum]] Foo { A=1, B=2 };
+// expected-warning at -1 {{operator| is ambiguous for flag-like enumeration type Foo}} \
+// expected-note@#candidate1 {{candidate function}} \
+// expected-note@#candidate2 {{candidate function}} \
+// expected-warning at -1 {{operator& is deleted for flag-like enumeration type Foo}} \
+// expected-note@#deleted1 {{candidate function has been explicitly deleted}} \
+// expected-warning at -1 {{operator^ is deleted for flag-like enumeration type Foo: reason}} \
+// expected-note@#deleted2 {{candidate function has been explicitly deleted}} \
+// expected-warning at -1 {{operator~ is not available for flag-like enumeration type Foo}}
+
+constexpr Foo operator|(Foo lhs, Foo rhs) { // #candidate1
+ return static_cast<Foo>(static_cast<unsigned>(lhs) | static_cast<unsigned>(rhs));
+}
+
+Foo operator&(Foo L, Foo R) = delete; // #deleted1
+Foo operator^(Foo L, Foo R) = delete("reason"); // #deleted2
+}
+
+constexpr test::Foo operator|(test::Foo lhs, test::Foo rhs) { // #candidate2
+ return static_cast<test::Foo>(static_cast<unsigned>(lhs) | static_cast<unsigned>(rhs));
+}
+
+
+template <class T>
+struct Foo {
+ enum class [[clang::flag_enum]] Bar : T { A=1, B=2 }; // #dependent-enum
+};
+
+template struct Foo<int>;
+// expected-warning@#dependent-enum {{operator| is not available for flag-like enumeration type Bar}} \
+// expected-warning@#dependent-enum {{operator& is not available for flag-like enumeration type Bar}} \
+// expected-warning@#dependent-enum {{operator^ is not available for flag-like enumeration type Bar}} \
+// expected-warning@#dependent-enum {{operator~ is not available for flag-like enumeration type Bar}}
>From 1514753a41c4ece387886e2f66afe79e95a9ddab Mon Sep 17 00:00:00 2001
From: Matthias Wippich <mfwippich at gmail.com>
Date: Mon, 24 Aug 2026 17:01:00 +0200
Subject: [PATCH 2/2] move RHS/LHS onto stack
---
clang/lib/Sema/Sema.cpp | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index b28cfbd5403dc..4010813cf7d9d 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1209,8 +1209,8 @@ static void DiagnoseInvalidFlagEnumOperators(Sema &S, const EnumDecl *ED) {
QualType T = S.Context.getCanonicalTagType(ED);
- Expr *LHS = new (S.Context) OpaqueValueExpr(SourceLocation(), T, VK_PRValue);
- Expr *RHS = new (S.Context) OpaqueValueExpr(SourceLocation(), T, VK_PRValue);
+ auto LHS = OpaqueValueExpr(SourceLocation(), T, VK_PRValue);
+ auto RHS = OpaqueValueExpr(SourceLocation(), T, VK_PRValue);
OverloadedOperatorKind OPs[] = {OO_Pipe, OO_Amp, OO_Caret, OO_Tilde};
for (const auto OP : OPs) {
@@ -1224,10 +1224,10 @@ static void DiagnoseInvalidFlagEnumOperators(Sema &S, const EnumDecl *ED) {
SmallVector<Expr *, 2> Args;
if (OP == OO_Tilde) {
- Args = {LHS};
+ Args = {&LHS};
S.LookupOverloadedUnaryOp(CandidateSet, OP, R.asUnresolvedSet(), Args);
} else {
- Args = {LHS, RHS};
+ Args = {&LHS, &RHS};
S.LookupOverloadedBinOp(CandidateSet, OP, R.asUnresolvedSet(), Args);
}
More information about the cfe-commits
mailing list