llvmorg-github-actions[bot] wrote:

<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Matthias Wippich (Tsche)

<details>
<summary>Changes</summary>

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.

---
Full diff: https://github.com/llvm/llvm-project/pull/218290.diff


4 Files Affected:

- (modified) clang/docs/ReleaseNotes.md (+3) 
- (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+3) 
- (modified) clang/lib/Sema/Sema.cpp (+74) 
- (added) clang/test/Sema/flag-enum.cpp (+62) 


``````````diff
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}}

``````````

</details>


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

Reply via email to