[clang] [clang] Diagnose invalid/unavailable operator overloads for scoped flag_enums (PR #218290)
Matthias Wippich via cfe-commits
cfe-commits at lists.llvm.org
Sun Oct 4 17:31:48 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/5] [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/5] 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);
}
>From a04ff8ab2464b65aeb2191deed228fef6f4e5309 Mon Sep 17 00:00:00 2001
From: Matthias Wippich <mfwippich at gmail.com>
Date: Thu, 27 Aug 2026 23:41:40 +0200
Subject: [PATCH 3/5] remove definitions in test, clean up diagnostic arguments
---
clang/lib/Sema/Sema.cpp | 10 +++----
clang/test/Sema/flag-enum.cpp | 53 ++++++++++++-----------------------
2 files changed, 22 insertions(+), 41 deletions(-)
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 4010813cf7d9d..1b366cf09da6a 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1240,15 +1240,14 @@ static void DiagnoseInvalidFlagEnumOperators(Sema &S, const EnumDecl *ED) {
break;
case OR_No_Viable_Function: {
S.Diag(ED->getLocation(), diag::warn_flag_enum_operator)
- << ED->getName() << Name.getAsString() << OR_No_Viable_Function
- << false << "";
+ << ED << Name << 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 << "";
+ << ED << Name << OR_Ambiguous << false << "";
auto Cands =
CandidateSet.CompleteCandidates(S, OCD_AmbiguousCandidates, Args);
CandidateSet.NoteCandidates(S, Args, Cands, Name.getAsString());
@@ -1256,12 +1255,11 @@ static void DiagnoseInvalidFlagEnumOperators(Sema &S, const EnumDecl *ED) {
}
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)
+ << ED << Name << OR_Deleted
+ << (Msg != nullptr)
<< (Msg ? Msg->getString() : "")),
S, OCD_AllCandidates, Args, Name.getAsString());
break;
diff --git a/clang/test/Sema/flag-enum.cpp b/clang/test/Sema/flag-enum.cpp
index 6e95a7c8f6691..1f1da08d172fa 100644
--- a/clang/test/Sema/flag-enum.cpp
+++ b/clang/test/Sema/flag-enum.cpp
@@ -5,50 +5,33 @@ 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}}
+// 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));
-}
+WithOps operator|(WithOps L, WithOps R);
+WithOps operator&(WithOps L, WithOps R);
+WithOps operator^(WithOps L, WithOps R);
+WithOps operator~(WithOps 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-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-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-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));
-}
+// expected-warning at -1 {{'operator~' is not available for flag-like enumeration type 'Foo'}}
+Foo operator|(Foo lhs, Foo rhs); // #candidate1
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));
-}
-
+constexpr test::Foo operator|(test::Foo lhs, test::Foo rhs); // #candidate2
template <class T>
struct Foo {
@@ -56,7 +39,7 @@ struct Foo {
};
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}}
+// 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 7a90cf3853680aa1831f78c014b80bd6bbe2c7c8 Mon Sep 17 00:00:00 2001
From: Matthias Wippich <mfwippich at gmail.com>
Date: Thu, 10 Sep 2026 05:22:37 +0200
Subject: [PATCH 4/5] improve tests
---
clang/test/Sema/flag-enum.cpp | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/clang/test/Sema/flag-enum.cpp b/clang/test/Sema/flag-enum.cpp
index 1f1da08d172fa..c962c3ab93963 100644
--- a/clang/test/Sema/flag-enum.cpp
+++ b/clang/test/Sema/flag-enum.cpp
@@ -11,9 +11,9 @@ enum class [[clang::flag_enum]] WithoutOps { D0 = 1, D1 = 8 };
// expected-warning at -1 {{'operator~' is not available for flag-like enumeration type 'WithoutOps'}}
WithOps operator|(WithOps L, WithOps R);
-WithOps operator&(WithOps L, WithOps R);
-WithOps operator^(WithOps L, WithOps R);
-WithOps operator~(WithOps L);
+WithOps operator&(WithOps const& L, WithOps const& R);
+WithOps operator^(WithOps L, WithOps&& R);
+WithOps operator~(WithOps const&& L);
namespace test {
enum class [[clang::flag_enum]] Foo { A=1, B=2 };
@@ -25,10 +25,12 @@ enum class [[clang::flag_enum]] Foo { A=1, B=2 };
// 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'}}
+// expected-note@#nonviable {{candidate function not viable: expects an lvalue for 1st argument}}
Foo operator|(Foo lhs, Foo rhs); // #candidate1
Foo operator&(Foo L, Foo R) = delete; // #deleted1
Foo operator^(Foo L, Foo R) = delete("reason"); // #deleted2
+Foo operator~(Foo& L); // #nonviable
}
constexpr test::Foo operator|(test::Foo lhs, test::Foo rhs); // #candidate2
>From 7c259f5f6f622ac4b7441f0712a43e8ec74a34e9 Mon Sep 17 00:00:00 2001
From: Matthias Wippich <mfwippich at gmail.com>
Date: Mon, 5 Oct 2026 02:26:31 +0200
Subject: [PATCH 5/5] restore eager cache population, add pch/modules tests
---
clang/lib/Sema/Sema.cpp | 13 +++++++---
clang/lib/Sema/SemaDecl.cpp | 22 +++++++++++-----
clang/test/Modules/flag-enum.cppm | 42 +++++++++++++++++++++++++++++++
clang/test/PCH/flag-enum.c | 31 -----------------------
clang/test/PCH/flag-enum.cpp | 35 ++++++++++++++++++++++++++
5 files changed, 102 insertions(+), 41 deletions(-)
create mode 100644 clang/test/Modules/flag-enum.cppm
delete mode 100644 clang/test/PCH/flag-enum.c
create mode 100644 clang/test/PCH/flag-enum.cpp
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 2b143024a1ff5..dffe41a6b8e27 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1424,6 +1424,15 @@ void Sema::ActOnEndOfTranslationUnit() {
}),
UnusedFileScopedDecls.end());
+ for (const auto &[ED, _] : FlagBitsCache) {
+ const EnumDecl *Definition = ED->getDefinition();
+ if (!Definition || Definition->isFromASTFile())
+ continue;
+ if (!Diags.isIgnored(diag::warn_flag_enum_operator,
+ Definition->getLocation()))
+ DiagnoseInvalidFlagEnumOperators(*this, Definition);
+ }
+
if (TUKind == TU_Prefix) {
// Translation unit prefixes don't need any of the checking below.
if (!PP.isIncrementalProcessingEnabled())
@@ -1790,10 +1799,6 @@ 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/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 88f25903d97c3..45f02633d7018 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -21117,12 +21117,9 @@ static void CheckForDuplicateEnumValues(Sema &S, ArrayRef<Decl *> Elements,
}
}
-bool Sema::IsValueInFlagEnum(const EnumDecl *ED, const llvm::APInt &Val,
- bool AllowMask) const {
- assert(ED->isClosedFlag() && "looking for value in non-flag or open enum");
- assert(ED->isCompleteDefinition() && "expected enum definition");
-
- auto R = FlagBitsCache.try_emplace(ED);
+static llvm::APInt CacheFlagEnum(const Sema &S, const EnumDecl *ED) {
+ assert(ED->hasAttr<FlagEnumAttr>() && "not a flag-like enum");
+ auto R = S.FlagBitsCache.try_emplace(ED);
llvm::APInt &FlagBits = R.first->second;
if (R.second) {
@@ -21133,6 +21130,15 @@ bool Sema::IsValueInFlagEnum(const EnumDecl *ED, const llvm::APInt &Val,
FlagBits = FlagBits.zext(EVal.getBitWidth()) | EVal;
}
}
+ return FlagBits;
+}
+
+bool Sema::IsValueInFlagEnum(const EnumDecl *ED, const llvm::APInt &Val,
+ bool AllowMask) const {
+ assert(ED->isClosedFlag() && "looking for value in non-flag or open enum");
+ assert(ED->isCompleteDefinition() && "expected enum definition");
+
+ llvm::APInt FlagBits = CacheFlagEnum(*this, ED);
// A value is in a flag enum if either its bits are a subset of the enum's
// flag bits (the first condition) or we are allowing masks and the same is
@@ -21343,6 +21349,10 @@ void Sema::ActOnEnumBody(SourceLocation EnumLoc, SourceRange BraceRange,
CheckForDuplicateEnumValues(*this, Elements, Enum, EnumType);
CheckForComparisonInEnumInitializer(*this, Enum);
+ if (Enum->hasAttr<FlagEnumAttr>()) {
+ (void)CacheFlagEnum(*this, Enum);
+ }
+
if (Enum->isClosedFlag()) {
for (Decl *D : Elements) {
EnumConstantDecl *ECD = cast_or_null<EnumConstantDecl>(D);
diff --git a/clang/test/Modules/flag-enum.cppm b/clang/test/Modules/flag-enum.cppm
new file mode 100644
index 0000000000000..fb488fb7eb1e3
--- /dev/null
+++ b/clang/test/Modules/flag-enum.cppm
@@ -0,0 +1,42 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: %clang_cc1 -std=c++11 -x c++ -fmodules -emit-module \
+// RUN: -fmodule-name=ClangModule %t/module.modulemap -o %t/ClangModule.pcm \
+// RUN: -verify=module
+// RUN: %clang_cc1 -std=c++11 -fmodules -fno-implicit-modules \
+// RUN: -fmodule-map-file=%t/module.modulemap \
+// RUN: -fmodule-file=ClangModule=%t/ClangModule.pcm -fsyntax-only \
+// RUN: -verify=use %t/use-clang-module.cpp
+// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/NamedModule.cppm \
+// RUN: -o %t/NamedModule.pcm -verify=module
+// RUN: %clang_cc1 -std=c++20 -fmodule-file=NamedModule=%t/NamedModule.pcm \
+// RUN: -fsyntax-only -verify=use %t/use-named-module.cpp
+
+//--- module.modulemap
+module ClangModule {
+ header "clang-module.h"
+}
+
+//--- clang-module.h
+enum class [[clang::flag_enum]] ClangModuleFlags { A = 1, B = 2 };
+// module-warning at -1 {{'operator|' is not available for flag-like enumeration type 'ClangModuleFlags'}}
+// module-warning at -2 {{'operator&' is not available for flag-like enumeration type 'ClangModuleFlags'}}
+// module-warning at -3 {{'operator^' is not available for flag-like enumeration type 'ClangModuleFlags'}}
+// module-warning at -4 {{'operator~' is not available for flag-like enumeration type 'ClangModuleFlags'}}
+
+//--- use-clang-module.cpp
+// use-no-diagnostics
+#include "clang-module.h"
+
+//--- NamedModule.cppm
+export module NamedModule;
+export enum class [[clang::flag_enum]] NamedModuleFlags { A = 1, B = 2 };
+// module-warning at -1 {{'operator|' is not available for flag-like enumeration type 'NamedModuleFlags'}}
+// module-warning at -2 {{'operator&' is not available for flag-like enumeration type 'NamedModuleFlags'}}
+// module-warning at -3 {{'operator^' is not available for flag-like enumeration type 'NamedModuleFlags'}}
+// module-warning at -4 {{'operator~' is not available for flag-like enumeration type 'NamedModuleFlags'}}
+
+//--- use-named-module.cpp
+// use-no-diagnostics
+import NamedModule;
+NamedModuleFlags flags;
diff --git a/clang/test/PCH/flag-enum.c b/clang/test/PCH/flag-enum.c
deleted file mode 100644
index 2d9bada686184..0000000000000
--- a/clang/test/PCH/flag-enum.c
+++ /dev/null
@@ -1,31 +0,0 @@
-// Check that an enum with the flag_enum attribute that is deserialized from a
-// PCH does not crash, and that its flag bits are still computed correctly.
-
-// RUN: %clang_cc1 -emit-pch -o %t %s
-// RUN: %clang_cc1 -include-pch %t -fsyntax-only -verify %s
-
-#ifndef HEADER
-#define HEADER
-
-enum __attribute__((flag_enum)) FlagEnum {
- A = 0x1,
- B = 0x2,
- C = 0x4,
-};
-
-#else
-
-int f(enum FlagEnum e) {
- switch (e) {
- case A:
- return 1;
- case B | C: // no-warning
- return 2;
- case 0x8: // expected-warning {{case value not in enumerated type 'enum FlagEnum'}}
- return 3;
- default:
- return 0;
- }
-}
-
-#endif
diff --git a/clang/test/PCH/flag-enum.cpp b/clang/test/PCH/flag-enum.cpp
new file mode 100644
index 0000000000000..82a7be53243d7
--- /dev/null
+++ b/clang/test/PCH/flag-enum.cpp
@@ -0,0 +1,35 @@
+// RUN: %clang_cc1 -std=c++11 -emit-pch -o %t -verify %s
+// RUN: %clang_cc1 -std=c++11 -include-pch %t -fsyntax-only -verify %s
+
+#ifndef HEADER
+#define HEADER
+
+enum class [[clang::flag_enum]] FlagEnum {
+ A = 0x1,
+ B = 0x2,
+ C = 0x4
+};
+// expected-warning at -5 {{'operator&' is not available for flag-like enumeration type 'FlagEnum'}} \
+// expected-warning at -5 {{'operator^' is not available for flag-like enumeration type 'FlagEnum'}} \
+// expected-warning at -5 {{'operator~' is not available for flag-like enumeration type 'FlagEnum'}}
+
+constexpr FlagEnum operator|(FlagEnum lhs, FlagEnum rhs) {
+ return static_cast<FlagEnum>(static_cast<int>(lhs) | static_cast<int>(rhs));
+}
+
+#else
+
+int f(FlagEnum flags) {
+ switch (flags) {
+ case FlagEnum::A:
+ return 1;
+ case FlagEnum::B | FlagEnum::C: // no-warning
+ return 2;
+ case FlagEnum(8): // expected-warning {{case value not in enumerated type 'FlagEnum'}}
+ return 3;
+ default:
+ return 0;
+ }
+}
+
+#endif
More information about the cfe-commits
mailing list