[clang] [clang] Turn -Wenum-constexpr-conversion into a hard error (PR #102364)

Carlos Galvez via cfe-commits cfe-commits at lists.llvm.org
Thu Aug 8 11:04:05 PDT 2024


https://github.com/carlosgalvezp updated https://github.com/llvm/llvm-project/pull/102364

>From 88f1a873d6c6ef06ad9a1847d098d65845cf1469 Mon Sep 17 00:00:00 2001
From: Carlos Galvez <carlosgalvezp at gmail.com>
Date: Tue, 6 Aug 2024 22:50:10 +0200
Subject: [PATCH 1/2] [clang] Turn -Wenum-constexpr-conversion into a hard
 error

The warning has been active for a few releases now, first only in
user code, later in system headers, and finally as an error by
default.

Therefore, we believe it is now time to transition into a hard error,
as required by the C++ Standard. The main affected C++ projects
have by now fixed the error, or there's a pending patch for review
that does it.

Fixes #59036
---
 clang/docs/ReleaseNotes.rst                   |  3 ++
 .../include/clang/Basic/DiagnosticASTKinds.td |  5 ++-
 clang/lib/AST/ExprConstant.cpp                |  6 ++--
 clang/lib/AST/Interp/Interp.cpp               |  4 +--
 clang/test/AST/Interp/cxx11.cpp               | 27 ++++++++++-----
 .../enum-constexpr-conversion-system-header.h |  3 +-
 .../SemaCXX/constant-expression-cxx11.cpp     | 33 ++++++++++++-------
 clang/test/SemaCXX/cxx2a-consteval.cpp        |  3 +-
 8 files changed, 53 insertions(+), 31 deletions(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index d322da81723a5f..18e20d04298ae2 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -49,6 +49,9 @@ C++ Specific Potentially Breaking Changes
   few users and can be written as ``__is_same(__remove_cv(T), decltype(nullptr))``,
   which GCC supports as well.
 
+- The warning `-Wenum-constexpr-conversion` has been upgraded into a hard
+  compiler error that cannot be suppressed, as required by the C++ Standard.
+
 ABI Changes in This Version
 ---------------------------
 
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index a024f9b2a9f8c0..af53cf651e51ab 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -410,10 +410,9 @@ def warn_integer_constant_overflow : Warning<
 def warn_fixedpoint_constant_overflow : Warning<
   "overflow in expression; result is %0 with type %1">,
   InGroup<DiagGroup<"fixed-point-overflow">>;
-def warn_constexpr_unscoped_enum_out_of_range : Warning<
+def note_constexpr_unscoped_enum_out_of_range : Note<
   "integer value %0 is outside the valid range of values [%1, %2] for the "
-  "enumeration type %3">, DefaultError, ShowInSystemHeader, ShowInSystemMacro,
-  InGroup<DiagGroup<"enum-constexpr-conversion">>;
+  "enumeration type %3">;
 
 // This is a temporary diagnostic, and shall be removed once our
 // implementation is complete, and like the preceding constexpr notes belongs
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index d4b9095c7509b3..f2b8161f4a9014 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -14406,14 +14406,12 @@ bool IntExprEvaluator::VisitCastExpr(const CastExpr *E) {
         if (ED->getNumNegativeBits() && ConstexprVar &&
             (Max.slt(Result.getInt().getSExtValue()) ||
              Min.sgt(Result.getInt().getSExtValue())))
-          Info.Ctx.getDiagnostics().Report(
-              E->getExprLoc(), diag::warn_constexpr_unscoped_enum_out_of_range)
+          Info.CCEDiag(E, diag::note_constexpr_unscoped_enum_out_of_range)
               << llvm::toString(Result.getInt(), 10) << Min.getSExtValue()
               << Max.getSExtValue() << ED;
         else if (!ED->getNumNegativeBits() && ConstexprVar &&
                  Max.ult(Result.getInt().getZExtValue()))
-          Info.Ctx.getDiagnostics().Report(
-              E->getExprLoc(), diag::warn_constexpr_unscoped_enum_out_of_range)
+          Info.CCEDiag(E, diag::note_constexpr_unscoped_enum_out_of_range)
               << llvm::toString(Result.getInt(), 10) << Min.getZExtValue()
               << Max.getZExtValue() << ED;
       }
diff --git a/clang/lib/AST/Interp/Interp.cpp b/clang/lib/AST/Interp/Interp.cpp
index 0f72b860ddad77..f2440f698b22b2 100644
--- a/clang/lib/AST/Interp/Interp.cpp
+++ b/clang/lib/AST/Interp/Interp.cpp
@@ -924,12 +924,12 @@ void diagnoseEnumValue(InterpState &S, CodePtr OpPC, const EnumDecl *ED,
   if (ED->getNumNegativeBits() &&
       (Max.slt(Value.getSExtValue()) || Min.sgt(Value.getSExtValue()))) {
     const SourceLocation &Loc = S.Current->getLocation(OpPC);
-    S.report(Loc, diag::warn_constexpr_unscoped_enum_out_of_range)
+    S.CCEDiag(Loc, diag::note_constexpr_unscoped_enum_out_of_range)
         << llvm::toString(Value, 10) << Min.getSExtValue() << Max.getSExtValue()
         << ED;
   } else if (!ED->getNumNegativeBits() && Max.ult(Value.getZExtValue())) {
     const SourceLocation &Loc = S.Current->getLocation(OpPC);
-    S.report(Loc, diag::warn_constexpr_unscoped_enum_out_of_range)
+    S.CCEDiag(Loc, diag::note_constexpr_unscoped_enum_out_of_range)
         << llvm::toString(Value, 10) << Min.getZExtValue() << Max.getZExtValue()
         << ED;
   }
diff --git a/clang/test/AST/Interp/cxx11.cpp b/clang/test/AST/Interp/cxx11.cpp
index cf2dfba079ef7e..481e3da9289efa 100644
--- a/clang/test/AST/Interp/cxx11.cpp
+++ b/clang/test/AST/Interp/cxx11.cpp
@@ -93,49 +93,58 @@ E2 testDefaultArgForParam(E2 e2Param = (E2)-1) { // ok, not a constant expressio
 void testValueInRangeOfEnumerationValues() {
   constexpr E1 x1 = static_cast<E1>(-8);
   constexpr E1 x2 = static_cast<E1>(8);
-  // both-error at -1 {{integer value 8 is outside the valid range of values [-8, 7] for the enumeration type 'E1'}}
+  // both-error at -1 {{constexpr variable 'x2' must be initialized by a constant expression}}
+  // both-note at -2 {{integer value 8 is outside the valid range of values [-8, 7] for the enumeration type 'E1'}}
   E1 x2b = static_cast<E1>(8); // ok, not a constant expression context
 
   constexpr E2 x3 = static_cast<E2>(-8);
-  // both-error at -1 {{integer value -8 is outside the valid range of values [0, 7] for the enumeration type 'E2'}}
+  // both-error at -1 {{constexpr variable 'x3' must be initialized by a constant expression}}
+  // both-note at -2 {{integer value -8 is outside the valid range of values [0, 7] for the enumeration type 'E2'}}
   constexpr E2 x4 = static_cast<E2>(0);
   constexpr E2 x5 = static_cast<E2>(8);
-  // both-error at -1 {{integer value 8 is outside the valid range of values [0, 7] for the enumeration type 'E2'}}
+  // both-error at -1 {{constexpr variable 'x5' must be initialized by a constant expression}}
+  // both-note at -2 {{integer value 8 is outside the valid range of values [0, 7] for the enumeration type 'E2'}}
 
   constexpr E3 x6 = static_cast<E3>(-2048);
   constexpr E3 x7 = static_cast<E3>(-8);
   constexpr E3 x8 = static_cast<E3>(0);
   constexpr E3 x9 = static_cast<E3>(8);
   constexpr E3 x10 = static_cast<E3>(2048);
-  // both-error at -1 {{integer value 2048 is outside the valid range of values [-2048, 2047] for the enumeration type 'E3'}}
+  // both-error at -1 {{constexpr variable 'x10' must be initialized by a constant expression}}
+  // both-note at -2 {{integer value 2048 is outside the valid range of values [-2048, 2047] for the enumeration type 'E3'}}
 
   constexpr E4 x11 = static_cast<E4>(0);
   constexpr E4 x12 = static_cast<E4>(1);
   constexpr E4 x13 = static_cast<E4>(2);
-  // both-error at -1 {{integer value 2 is outside the valid range of values [0, 1] for the enumeration type 'E4'}}
+  // both-error at -1 {{constexpr variable 'x13' must be initialized by a constant expression}}
+  // both-note at -2 {{integer value 2 is outside the valid range of values [0, 1] for the enumeration type 'E4'}}
 
   constexpr EEmpty x14 = static_cast<EEmpty>(0);
   constexpr EEmpty x15 = static_cast<EEmpty>(1);
   constexpr EEmpty x16 = static_cast<EEmpty>(2);
-  // both-error at -1 {{integer value 2 is outside the valid range of values [0, 1] for the enumeration type 'EEmpty'}}
+  // both-error at -1 {{constexpr variable 'x16' must be initialized by a constant expression}}
+  // both-note at -2 {{integer value 2 is outside the valid range of values [0, 1] for the enumeration type 'EEmpty'}}
 
   constexpr EFixed x17 = static_cast<EFixed>(100);
   constexpr EScoped x18 = static_cast<EScoped>(100);
 
   constexpr EMaxInt x19 = static_cast<EMaxInt>(__INT_MAX__-1);
   constexpr EMaxInt x20 = static_cast<EMaxInt>((long)__INT_MAX__+1);
-  // both-error at -1 {{integer value 2147483648 is outside the valid range of values [-2147483648, 2147483647] for the enumeration type 'EMaxInt'}}
+  // both-error at -1 {{constexpr variable 'x20' must be initialized by a constant expression}}
+  // both-note at -2 {{integer value 2147483648 is outside the valid range of values [-2147483648, 2147483647] for the enumeration type 'EMaxInt'}}
 
   const NumberType neg_one = (NumberType) ((NumberType) 0 - (NumberType) 1); // ok, not a constant expression context
 }
 
 template<class T, unsigned size> struct Bitfield {
-  static constexpr T max = static_cast<T>((1 << size) - 1); // #enum
+  static constexpr T max = static_cast<T>((1 << size) - 1);
+  // both-error at -1 {{constexpr variable 'max' must be initialized by a constant expression}}
+  // both-note at -2 {{integer value 15 is outside the valid range of values [0, 7] for the enumeration type 'E2'}}
 };
 
 void testValueInRangeOfEnumerationValuesViaTemplate() {
   Bitfield<E2, 3> good;
-  Bitfield<E2, 4> bad; // both-error@#enum {{integer value 15 is outside the valid range of values [0, 7] for the enumeration type 'E2'}}
+  Bitfield<E2, 4> bad; // both-note {{in instantiation}}
 }
 
 enum SortOrder {
diff --git a/clang/test/SemaCXX/Inputs/enum-constexpr-conversion-system-header.h b/clang/test/SemaCXX/Inputs/enum-constexpr-conversion-system-header.h
index 0850f3405eed3a..7d35d5b71d1dfe 100644
--- a/clang/test/SemaCXX/Inputs/enum-constexpr-conversion-system-header.h
+++ b/clang/test/SemaCXX/Inputs/enum-constexpr-conversion-system-header.h
@@ -10,7 +10,8 @@ enum SystemEnum
 void testValueInRangeOfEnumerationValuesInSystemHeader()
 {
     constexpr SystemEnum x1 = static_cast<SystemEnum>(123);
-    // expected-error at -1 {{integer value 123 is outside the valid range of values [0, 1] for the enumeration type 'SystemEnum'}}
+    // expected-error at -1 {{constexpr variable 'x1' must be initialized by a constant expression}}
+    // expected-note at -2 {{integer value 123 is outside the valid range of values [0, 1] for the enumeration type 'SystemEnum'}}
 
     const SystemEnum x2 = static_cast<SystemEnum>(123);  // ok, not a constant expression context
 }
diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp
index 6df8a4740d6ccc..d888887bd8c6f3 100644
--- a/clang/test/SemaCXX/constant-expression-cxx11.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp
@@ -2460,52 +2460,62 @@ E2 testDefaultArgForParam(E2 e2Param = (E2)-1) { // ok, not a constant expressio
 void testValueInRangeOfEnumerationValues() {
   constexpr E1 x1 = static_cast<E1>(-8);
   constexpr E1 x2 = static_cast<E1>(8);
-  // expected-error at -1 {{integer value 8 is outside the valid range of values [-8, 7] for the enumeration type 'E1'}}
+  // expected-error at -1 {{constexpr variable 'x2' must be initialized by a constant expression}}
+  // expected-note at -2 {{integer value 8 is outside the valid range of values [-8, 7] for the enumeration type 'E1'}}
   E1 x2b = static_cast<E1>(8); // ok, not a constant expression context
 
   constexpr E2 x3 = static_cast<E2>(-8);
-  // expected-error at -1 {{integer value -8 is outside the valid range of values [0, 7] for the enumeration type 'E2'}}
+  // expected-error at -1 {{constexpr variable 'x3' must be initialized by a constant expression}}
+  // expected-note at -2 {{integer value -8 is outside the valid range of values [0, 7] for the enumeration type 'E2'}}
   constexpr E2 x4 = static_cast<E2>(0);
   constexpr E2 x5 = static_cast<E2>(8);
-  // expected-error at -1 {{integer value 8 is outside the valid range of values [0, 7] for the enumeration type 'E2'}}
+  // expected-error at -1 {{constexpr variable 'x5' must be initialized by a constant expression}}
+  // expected-note at -2 {{integer value 8 is outside the valid range of values [0, 7] for the enumeration type 'E2'}}
 
   constexpr E3 x6 = static_cast<E3>(-2048);
   constexpr E3 x7 = static_cast<E3>(-8);
   constexpr E3 x8 = static_cast<E3>(0);
   constexpr E3 x9 = static_cast<E3>(8);
   constexpr E3 x10 = static_cast<E3>(2048);
-  // expected-error at -1 {{integer value 2048 is outside the valid range of values [-2048, 2047] for the enumeration type 'E3'}}
+  // expected-error at -1 {{constexpr variable 'x10' must be initialized by a constant expression}}
+  // expected-note at -2 {{integer value 2048 is outside the valid range of values [-2048, 2047] for the enumeration type 'E3'}}
 
   constexpr E4 x11 = static_cast<E4>(0);
   constexpr E4 x12 = static_cast<E4>(1);
   constexpr E4 x13 = static_cast<E4>(2);
-  // expected-error at -1 {{integer value 2 is outside the valid range of values [0, 1] for the enumeration type 'E4'}}
+  // expected-error at -1 {{constexpr variable 'x13' must be initialized by a constant expression}}
+  // expected-note at -2 {{integer value 2 is outside the valid range of values [0, 1] for the enumeration type 'E4'}}
 
   constexpr EEmpty x14 = static_cast<EEmpty>(0);
   constexpr EEmpty x15 = static_cast<EEmpty>(1);
   constexpr EEmpty x16 = static_cast<EEmpty>(2);
-  // expected-error at -1 {{integer value 2 is outside the valid range of values [0, 1] for the enumeration type 'EEmpty'}}
+  // expected-error at -1 {{constexpr variable 'x16' must be initialized by a constant expression}}
+  // expected-note at -2 {{integer value 2 is outside the valid range of values [0, 1] for the enumeration type 'EEmpty'}}
 
   constexpr EFixed x17 = static_cast<EFixed>(100);
   constexpr EScoped x18 = static_cast<EScoped>(100);
 
   constexpr EMaxInt x19 = static_cast<EMaxInt>(__INT_MAX__-1);
   constexpr EMaxInt x20 = static_cast<EMaxInt>((long)__INT_MAX__+1);
-  // expected-error at -1 {{integer value 2147483648 is outside the valid range of values [-2147483648, 2147483647] for the enumeration type 'EMaxInt'}}
+  // expected-error at -1 {{constexpr variable 'x20' must be initialized by a constant expression}}
+  // expected-note at -2 {{integer value 2147483648 is outside the valid range of values [-2147483648, 2147483647] for the enumeration type 'EMaxInt'}}
 
   const NumberType neg_one = (NumberType) ((NumberType) 0 - (NumberType) 1); // ok, not a constant expression context
 
   CONSTEXPR_CAST_TO_SYSTEM_ENUM_OUTSIDE_OF_RANGE;
-  // expected-error at -1 {{integer value 123 is outside the valid range of values [0, 1] for the enumeration type 'SystemEnum'}}
+  // expected-error at -1 {{constexpr variable 'system_enum' must be initialized by a constant expression}}
+  // expected-note at -2 {{integer value 123 is outside the valid range of values [0, 1] for the enumeration type 'SystemEnum'}}
 }
 
 template<class T, unsigned size> struct Bitfield {
-  static constexpr T max = static_cast<T>((1 << size) - 1); // #enum
+  static constexpr T max = static_cast<T>((1 << size) - 1);
+  // cxx11-error at -1 {{constexpr variable 'max' must be initialized by a constant expression}}
+  // cxx11-note at -2 {{integer value 15 is outside the valid range of values [0, 7] for the enumeration type 'E2'}}
 };
 
 void testValueInRangeOfEnumerationValuesViaTemplate() {
   Bitfield<E2, 3> good;
-  Bitfield<E2, 4> bad; // cxx11-error@#enum {{integer value 15 is outside the valid range of values [0, 7] for the enumeration type 'E2'}}
+  Bitfield<E2, 4> bad; // cxx11-note {{in instantiation}}
 }
 
 enum SortOrder {
@@ -2526,4 +2536,5 @@ void A::f(SortOrder order) {
 GH50055::E2 GlobalInitNotCE1 = (GH50055::E2)-1; // ok, not a constant expression context
 GH50055::E2 GlobalInitNotCE2 = GH50055::testDefaultArgForParam(); // ok, not a constant expression context
 constexpr GH50055::E2 GlobalInitCE = (GH50055::E2)-1;
-// expected-error at -1 {{integer value -1 is outside the valid range of values [0, 7] for the enumeration type 'E2'}}
+// expected-error at -1 {{constexpr variable 'GlobalInitCE' must be initialized by a constant expression}}
+// expected-note at -2 {{integer value -1 is outside the valid range of values [0, 7] for the enumeration type 'E2'}}
diff --git a/clang/test/SemaCXX/cxx2a-consteval.cpp b/clang/test/SemaCXX/cxx2a-consteval.cpp
index ba80e57f814244..6b0609a26c5882 100644
--- a/clang/test/SemaCXX/cxx2a-consteval.cpp
+++ b/clang/test/SemaCXX/cxx2a-consteval.cpp
@@ -920,12 +920,13 @@ consteval int aConstevalFunction() { // expected-error {{consteval function neve
 namespace GH50055 {
 enum E {e1=0, e2=1};
 consteval int testDefaultArgForParam(E eParam = (E)-1) {
-// expected-error at -1 {{integer value -1 is outside the valid range of values [0, 1] for the enumeration type 'E'}}
+// expected-note at -1 {{integer value -1 is outside the valid range of values [0, 1] for the enumeration type 'E'}}
   return (int)eParam;
 }
 
 int test() {
   return testDefaultArgForParam() + testDefaultArgForParam((E)1);
+  // expected-error at -1 {{call to consteval function 'GH50055::testDefaultArgForParam' is not a constant expression}}
 }
 }
 

>From d854f012dc955158949d39c15e8ea44c825549ed Mon Sep 17 00:00:00 2001
From: Carlos Galvez <carlosgalvezp at gmail.com>
Date: Thu, 8 Aug 2024 20:03:53 +0200
Subject: [PATCH 2/2] Improve release notes

---
 clang/docs/ReleaseNotes.rst | 22 ++++++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 18e20d04298ae2..32f159f2b0f35b 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -49,8 +49,21 @@ C++ Specific Potentially Breaking Changes
   few users and can be written as ``__is_same(__remove_cv(T), decltype(nullptr))``,
   which GCC supports as well.
 
-- The warning `-Wenum-constexpr-conversion` has been upgraded into a hard
-  compiler error that cannot be suppressed, as required by the C++ Standard.
+- Clang will now correctly diagnose as ill-formed a constant expression where an
+  enum without a fixed underlying type is set to a value outside the range of
+  the enumeration's values.
+
+  .. code-block:: c++
+
+    enum E { Zero, One, Two, Three, Four };
+    constexpr E Val1 = (E)3;  // Ok
+    constexpr E Val2 = (E)7;  // Ok
+    constexpr E Val3 = (E)8;  // Now ill-formed, out of the range [0, 7]
+    constexpr E Val4 = (E)-1; // Now ill-formed, out of the range [0, 7]
+
+  Since Clang 16, it has been possible to suppress the diagnostic via
+  `-Wno-enum-constexpr-conversion`, to allow for a transition period for users.
+  Now, in Clang 20, **it is no longer possible to suppress the diagnostic**.
 
 ABI Changes in This Version
 ---------------------------
@@ -122,6 +135,11 @@ Modified Compiler Flags
 Removed Compiler Flags
 -------------------------
 
+- The compiler flag `-Wenum-constexpr-conversion` (and the `Wno-`, `Wno-error-`
+  derivatives) is now removed, since it's no longer possible to suppress the
+  diagnostic (see above). Users can expect an `unknown warning` diagnostic if
+  it's still in use.
+
 Attribute Changes in Clang
 --------------------------
 



More information about the cfe-commits mailing list