[clang] [Clang] Fix conversion from floats to bool-backed enums per CWG1094 (PR #211172)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Jul 21 22:16:17 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Joyee Cheung (joyeecheung)
<details>
<summary>Changes</summary>
After CWG 1094, `[expr.static.cast]p8` has been updated to include:
> A value of floating-point type can also be explicitly converted to an
> enumeration type. The resulting value is the same as converting the
> original value to the underlying type of the enumeration
> ([conv.fpint]), and subsequently to the enumeration type.
`[conv.fpint]p1` states:
> If the destination type is bool, see [conv.bool].
`[conv.bool]` states:
> A zero value, null pointer value, or null member pointer value is
> converted to false; any other value is converted to true.
Previously `TryStaticCast` always used integral conversion for floating-point operands, even if the destination enum type is backed by a bool, so e.g. 0.5 would be incorrectly truncated and then converted to the enum value corresponding to false.
This patch fixes it by using `CK_FloatingToBoolean` to handle bool-backed enums and updates the Bytecode compiler accordingly. Also update the spec reference as the relevant paragraph has been moved to p8.
---
Discovered this bug in Clang when I was [fixing `[expr.static.cast]/8` handling in GCC](https://gcc.gnu.org/cgit/gcc/commit/?id=ca3810f001338bb4d15da43160e017d453f234e8), which handled floating points correctly but not integers, and Clang was the other way around.
I wrote the patch myself by following https://reviews.llvm.org/D85612, then paired with 5 AI models for preliminary reviews. 4/5 of them pointed out that the patch should support `-fexperimental-new-constant-interpreter`, which I then implemented and verified manually.
---
Full diff: https://github.com/llvm/llvm-project/pull/211172.diff
6 Files Affected:
- (modified) clang/docs/ReleaseNotes.md (+4)
- (modified) clang/lib/AST/ByteCode/Compiler.cpp (+1-1)
- (modified) clang/lib/Sema/SemaCast.cpp (+17-7)
- (modified) clang/test/CXX/drs/cwg10xx.cpp (+39)
- (modified) clang/test/CodeGen/enum-bool.cpp (+31)
- (modified) clang/www/cxx_dr_status.html (+1-1)
``````````diff
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 80e770a40838a..ea19d46bc6b3a 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -90,6 +90,10 @@ features cannot lower the translation-unit ABI level;
#### Resolutions to C++ Defect Reports
+- Clang now converts floating-point values to boolean first when converting
+ them to an enumeration type with a fixed `bool` underlying type. This
+ resolves [CWG1094](https://wg21.link/cwg1094).
+
### C Language Changes
#### C2y Feature Support
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 35667a9132680..21df08b950624 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -638,7 +638,7 @@ bool Compiler<Emitter>::VisitCastExpr(const CastExpr *E) {
if (E->getType()->isVectorType())
return this->emitVectorConversion(E->getSubExpr(), E);
if (!SubExpr->getType()->isRealFloatingType() ||
- !E->getType()->isBooleanType())
+ !E->getType()->hasBooleanRepresentation())
return false;
if (const auto *FL = dyn_cast<FloatingLiteral>(SubExpr))
return this->emitConstBool(FL->getValue().isNonZero(), E);
diff --git a/clang/lib/Sema/SemaCast.cpp b/clang/lib/Sema/SemaCast.cpp
index 133837623a7a6..8da73df832d77 100644
--- a/clang/lib/Sema/SemaCast.cpp
+++ b/clang/lib/Sema/SemaCast.cpp
@@ -1489,16 +1489,26 @@ static TryCastResult TryStaticCast(Sema &Self, ExprResult &SrcExpr,
SrcExpr = ExprError();
return TC_Failed;
}
+ // [expr.static.cast]p8
+ // If the enumeration type has a fixed underlying type, the value is
+ // first converted to that type by integral promotion ([conv.prom]) or
+ // integral conversion ([conv.integral]), if necessary, and then to the
+ // enumeration type.
+ const auto *ED = DestType->castAsEnumDecl();
+ bool DestIsFixedBoolean =
+ ED->isFixed() && ED->getIntegerType()->isBooleanType();
if (SrcType->isIntegralOrEnumerationType()) {
- // [expr.static.cast]p10 If the enumeration type has a fixed underlying
- // type, the value is first converted to that type by integral conversion
- const auto *ED = DestType->castAsEnumDecl();
- Kind = ED->isFixed() && ED->getIntegerType()->isBooleanType()
- ? CK_IntegralToBoolean
- : CK_IntegralCast;
+ Kind = DestIsFixedBoolean ? CK_IntegralToBoolean : CK_IntegralCast;
return TC_Success;
} else if (SrcType->isRealFloatingType()) {
- Kind = CK_FloatingToIntegral;
+ // [expr.static.cast]p8
+ // A value of floating-point type can also be explicitly converted
+ // to ... the underlying type of the enumeration ([conv.fpint]), and
+ // subsequently to the enumeration type.
+ //
+ // [conv.fpint] forwards to [conv.bool] for conversion to bool, so
+ // handle that case with CK_FloatingToBoolean.
+ Kind = DestIsFixedBoolean ? CK_FloatingToBoolean : CK_FloatingToIntegral;
return TC_Success;
}
}
diff --git a/clang/test/CXX/drs/cwg10xx.cpp b/clang/test/CXX/drs/cwg10xx.cpp
index 1f190fb5310be..bf9ba9057af9c 100644
--- a/clang/test/CXX/drs/cwg10xx.cpp
+++ b/clang/test/CXX/drs/cwg10xx.cpp
@@ -6,6 +6,14 @@
// RUN: %clang_cc1 -std=c++23 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14
// RUN: %clang_cc1 -std=c++2c %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14
+// RUN: %clang_cc1 -std=c++98 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,cxx98
+// RUN: %clang_cc1 -std=c++11 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11
+// RUN: %clang_cc1 -std=c++14 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14
+// RUN: %clang_cc1 -std=c++17 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14
+// RUN: %clang_cc1 -std=c++20 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14
+// RUN: %clang_cc1 -std=c++23 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14
+// RUN: %clang_cc1 -std=c++2c %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14
+
namespace std {
__extension__ typedef __SIZE_TYPE__ size_t;
@@ -111,3 +119,34 @@ namespace cwg1070 { // cwg1070: 3.5
C c = {};
#endif
} // namespace cwg1070
+
+#if __cplusplus >= 201103L
+namespace cwg1094 { // cwg1094: 24
+enum class E : bool { Zero, One };
+constexpr E from_double(double d) { return static_cast<E>(d); }
+
+static_assert(static_cast<E>(0.0) == E::Zero, "");
+static_assert(static_cast<E>(-0.0) == E::Zero, "");
+static_assert(static_cast<E>(0.5) == E::One, "");
+static_assert(static_cast<E>(-0.5) == E::One, "");
+static_assert(static_cast<E>(2.5) == E::One, "");
+static_assert(static_cast<E>(__builtin_nan("")) == E::One, "");
+
+static_assert(from_double(0.0) == E::Zero, "");
+static_assert(from_double(-0.0) == E::Zero, "");
+static_assert(from_double(0.5) == E::One, "");
+static_assert(from_double(-0.5) == E::One, "");
+static_assert(from_double(2.5) == E::One, "");
+static_assert(from_double(__builtin_nan("")) == E::One, "");
+
+enum class G : unsigned char { Zero, One, Two };
+static_assert(static_cast<G>(0.0) == G::Zero, "");
+static_assert(static_cast<G>(-0.0) == G::Zero, "");
+static_assert(static_cast<G>(0.5) == G::Zero, "");
+static_assert(static_cast<G>(-0.5) == G::Zero, "");
+static_assert(static_cast<G>(2.5) == G::Two, "");
+static_assert(static_cast<G>(__builtin_nan("")) == G::Zero, "");
+// since-cxx11-error at -1 {{static assertion expression is not an integral constant expression}}
+// since-cxx11-note at -2 {{value NaN is outside the range of representable values of type 'G'}}
+} // namespace cwg1094
+#endif
diff --git a/clang/test/CodeGen/enum-bool.cpp b/clang/test/CodeGen/enum-bool.cpp
index 4bf3b91361d28..8336374a1d14a 100644
--- a/clang/test/CodeGen/enum-bool.cpp
+++ b/clang/test/CodeGen/enum-bool.cpp
@@ -22,6 +22,21 @@ E b(int x) { return (E)x; }
// CHECK-LABEL: define{{.*}} zeroext i1 @_ZN6dr23381B1bEi
// CHECK: ret i1 %tobool
+E c(float x) { return static_cast<E>(x); }
+// CHECK-LABEL: define{{.*}} zeroext i1 @_ZN6dr23381B1cEf
+// CHECK: ret i1 %tobool
+
+E d(float x) { return (E)x; }
+// CHECK-LABEL: define{{.*}} zeroext i1 @_ZN6dr23381B1dEf
+// CHECK: ret i1 %tobool
+
+E e(double x) { return static_cast<E>(x); }
+// CHECK-LABEL: define{{.*}} zeroext i1 @_ZN6dr23381B1eEd
+// CHECK: ret i1 %tobool
+
+E f(double x) { return (E)x; }
+// CHECK-LABEL: define{{.*}} zeroext i1 @_ZN6dr23381B1fEd
+// CHECK: ret i1 %tobool
} // namespace B
namespace C {
enum class E { Zero, One };
@@ -45,5 +60,21 @@ E b(int x) { return (E)x; }
// CHECK-LABEL: define{{.*}} zeroext i1 @_ZN6dr23381D1bEi
// CHECK: ret i1 %tobool
+E c(float x) { return static_cast<E>(x); }
+// CHECK-LABEL: define{{.*}} zeroext i1 @_ZN6dr23381D1cEf
+// CHECK: ret i1 %tobool
+
+E d(float x) { return (E)x; }
+// CHECK-LABEL: define{{.*}} zeroext i1 @_ZN6dr23381D1dEf
+// CHECK: ret i1 %tobool
+
+E e(double x) { return static_cast<E>(x); }
+// CHECK-LABEL: define{{.*}} zeroext i1 @_ZN6dr23381D1eEd
+// CHECK: ret i1 %tobool
+
+E f(double x) { return (E)x; }
+// CHECK-LABEL: define{{.*}} zeroext i1 @_ZN6dr23381D1fEd
+// CHECK: ret i1 %tobool
+
} // namespace D
} // namespace dr2338
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index af91ac559d274..67445eea9a752 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -7435,7 +7435,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
<td>[<a href="https://wg21.link/expr.static.cast">expr.static.cast</a>]</td>
<td>C++11</td>
<td>Converting floating-point values to scoped enumeration types</td>
- <td class="unknown" align="center">Unknown</td>
+ <td class="unreleased" align="center">Clang 24</td>
</tr>
<tr id="1095">
<td><a href="https://cplusplus.github.io/CWG/issues/1095.html">1095</a></td>
``````````
</details>
https://github.com/llvm/llvm-project/pull/211172
More information about the cfe-commits
mailing list