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/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@-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/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 <[email protected]> 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@-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}} +// 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)); -} +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@-1 {{operator| is ambiguous for flag-like enumeration type Foo}} \ +// 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-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-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)); -} +// expected-warning@-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 <[email protected]> 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@-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@-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'}} +// 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 <[email protected]> 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@-1 {{'operator|' is not available for flag-like enumeration type 'ClangModuleFlags'}} +// module-warning@-2 {{'operator&' is not available for flag-like enumeration type 'ClangModuleFlags'}} +// module-warning@-3 {{'operator^' is not available for flag-like enumeration type 'ClangModuleFlags'}} +// module-warning@-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@-1 {{'operator|' is not available for flag-like enumeration type 'NamedModuleFlags'}} +// module-warning@-2 {{'operator&' is not available for flag-like enumeration type 'NamedModuleFlags'}} +// module-warning@-3 {{'operator^' is not available for flag-like enumeration type 'NamedModuleFlags'}} +// module-warning@-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@-5 {{'operator&' is not available for flag-like enumeration type 'FlagEnum'}} \ +// expected-warning@-5 {{'operator^' is not available for flag-like enumeration type 'FlagEnum'}} \ +// expected-warning@-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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
