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 <[email protected]>
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@-1 {{operator| is not available for flag-like enumeration 
type WithoutOps}} \
+// expected-warning@-1 {{operator& is not available for flag-like enumeration 
type WithoutOps}} \
+// expected-warning@-1 {{operator^ is not available for flag-like enumeration 
type WithoutOps}} \
+// expected-warning@-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@-1 {{operator| is ambiguous for flag-like enumeration type 
Foo}} \
+//   expected-note@#candidate1 {{candidate function}} \
+//   expected-note@#candidate2 {{candidate function}} \
+// expected-warning@-1 {{operator& is deleted for flag-like enumeration type 
Foo}} \
+//   expected-note@#deleted1 {{candidate function has been explicitly 
deleted}} \
+// expected-warning@-1 {{operator^ is deleted for flag-like enumeration type 
Foo: reason}} \
+//   expected-note@#deleted2 {{candidate function has been explicitly 
deleted}} \
+// expected-warning@-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 <[email protected]>
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);
     }
 

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to