Skip to content

Commit

Permalink
[clang] Turn -Wenum-constexpr-conversion into a hard error (llvm#102364)
Browse files Browse the repository at this point in the history
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 llvm#59036
  • Loading branch information
carlosgalvezp authored and bwendling committed Aug 15, 2024
1 parent e439fbf commit 74ceb4d
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 31 deletions.
21 changes: 21 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,22 @@ C++ Specific Potentially Breaking Changes
few users and can be written as ``__is_same(__remove_cv(T), decltype(nullptr))``,
which GCC supports as well.

- 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
---------------------------

Expand Down Expand Up @@ -143,6 +159,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
--------------------------

Expand Down
5 changes: 2 additions & 3 deletions clang/include/clang/Basic/DiagnosticASTKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -394,10 +394,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
Expand Down
6 changes: 2 additions & 4 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14508,14 +14508,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;
}
Expand Down
4 changes: 2 additions & 2 deletions clang/lib/AST/Interp/Interp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
27 changes: 18 additions & 9 deletions clang/test/AST/Interp/cxx11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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@-1 {{integer value 8 is outside the valid range of values [-8, 7] for the enumeration type 'E1'}}
// both-error@-1 {{constexpr variable 'x2' must be initialized by a constant expression}}
// both-note@-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@-1 {{integer value -8 is outside the valid range of values [0, 7] for the enumeration type 'E2'}}
// both-error@-1 {{constexpr variable 'x3' must be initialized by a constant expression}}
// both-note@-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@-1 {{integer value 8 is outside the valid range of values [0, 7] for the enumeration type 'E2'}}
// both-error@-1 {{constexpr variable 'x5' must be initialized by a constant expression}}
// both-note@-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@-1 {{integer value 2048 is outside the valid range of values [-2048, 2047] for the enumeration type 'E3'}}
// both-error@-1 {{constexpr variable 'x10' must be initialized by a constant expression}}
// both-note@-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@-1 {{integer value 2 is outside the valid range of values [0, 1] for the enumeration type 'E4'}}
// both-error@-1 {{constexpr variable 'x13' must be initialized by a constant expression}}
// both-note@-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@-1 {{integer value 2 is outside the valid range of values [0, 1] for the enumeration type 'EEmpty'}}
// both-error@-1 {{constexpr variable 'x16' must be initialized by a constant expression}}
// both-note@-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@-1 {{integer value 2147483648 is outside the valid range of values [-2147483648, 2147483647] for the enumeration type 'EMaxInt'}}
// both-error@-1 {{constexpr variable 'x20' must be initialized by a constant expression}}
// both-note@-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@-1 {{constexpr variable 'max' must be initialized by a constant expression}}
// both-note@-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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ enum SystemEnum
void testValueInRangeOfEnumerationValuesInSystemHeader()
{
constexpr SystemEnum x1 = static_cast<SystemEnum>(123);
// expected-error@-1 {{integer value 123 is outside the valid range of values [0, 1] for the enumeration type 'SystemEnum'}}
// expected-error@-1 {{constexpr variable 'x1' must be initialized by a constant expression}}
// expected-note@-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
}
Expand Down
33 changes: 22 additions & 11 deletions clang/test/SemaCXX/constant-expression-cxx11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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@-1 {{integer value 8 is outside the valid range of values [-8, 7] for the enumeration type 'E1'}}
// expected-error@-1 {{constexpr variable 'x2' must be initialized by a constant expression}}
// expected-note@-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@-1 {{integer value -8 is outside the valid range of values [0, 7] for the enumeration type 'E2'}}
// expected-error@-1 {{constexpr variable 'x3' must be initialized by a constant expression}}
// expected-note@-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@-1 {{integer value 8 is outside the valid range of values [0, 7] for the enumeration type 'E2'}}
// expected-error@-1 {{constexpr variable 'x5' must be initialized by a constant expression}}
// expected-note@-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@-1 {{integer value 2048 is outside the valid range of values [-2048, 2047] for the enumeration type 'E3'}}
// expected-error@-1 {{constexpr variable 'x10' must be initialized by a constant expression}}
// expected-note@-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@-1 {{integer value 2 is outside the valid range of values [0, 1] for the enumeration type 'E4'}}
// expected-error@-1 {{constexpr variable 'x13' must be initialized by a constant expression}}
// expected-note@-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@-1 {{integer value 2 is outside the valid range of values [0, 1] for the enumeration type 'EEmpty'}}
// expected-error@-1 {{constexpr variable 'x16' must be initialized by a constant expression}}
// expected-note@-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@-1 {{integer value 2147483648 is outside the valid range of values [-2147483648, 2147483647] for the enumeration type 'EMaxInt'}}
// expected-error@-1 {{constexpr variable 'x20' must be initialized by a constant expression}}
// expected-note@-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@-1 {{integer value 123 is outside the valid range of values [0, 1] for the enumeration type 'SystemEnum'}}
// expected-error@-1 {{constexpr variable 'system_enum' must be initialized by a constant expression}}
// expected-note@-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@-1 {{constexpr variable 'max' must be initialized by a constant expression}}
// cxx11-note@-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 {
Expand All @@ -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@-1 {{integer value -1 is outside the valid range of values [0, 7] for the enumeration type 'E2'}}
// expected-error@-1 {{constexpr variable 'GlobalInitCE' must be initialized by a constant expression}}
// expected-note@-2 {{integer value -1 is outside the valid range of values [0, 7] for the enumeration type 'E2'}}
3 changes: 2 additions & 1 deletion clang/test/SemaCXX/cxx2a-consteval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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@-1 {{integer value -1 is outside the valid range of values [0, 1] for the enumeration type 'E'}}
// expected-note@-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@-1 {{call to consteval function 'GH50055::testDefaultArgForParam' is not a constant expression}}
}
}

Expand Down

0 comments on commit 74ceb4d

Please sign in to comment.