https://github.com/Tsche created https://github.com/llvm/llvm-project/pull/218290
This patch adds a warning that is emitted if overload resolution for any of the bitwise operators fails for scoped flag-like enumerations. For unscoped enumerations these are provided already, for scoped enumerations you have to do that explicitly. Forgetting to do so is always a bug, since the expected semantics of flag-like enums explicitly assume the bitwise operators are there. The C++ standard calls such types [bitmask types](https://eel.is/c++draft/bitmask.types) and also requires the compound assignment operators - those are not checked by this patch, since they can be composed from the non-compound ones. >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] [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}} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
