https://github.com/joyeecheung created 
https://github.com/llvm/llvm-project/pull/211172

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.

>From 6b34f89458ca4413b5face04569cbb12ca4eb698 Mon Sep 17 00:00:00 2001
From: Joyee Cheung <[email protected]>
Date: Wed, 22 Jul 2026 01:04:12 +0200
Subject: [PATCH] [Clang] Fix conversion from floats to bool-backed enums per
 CWG1094

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.
---
 clang/docs/ReleaseNotes.md          |  4 +++
 clang/lib/AST/ByteCode/Compiler.cpp |  2 +-
 clang/lib/Sema/SemaCast.cpp         | 24 ++++++++++++------
 clang/test/CXX/drs/cwg10xx.cpp      | 39 +++++++++++++++++++++++++++++
 clang/test/CodeGen/enum-bool.cpp    | 31 +++++++++++++++++++++++
 clang/www/cxx_dr_status.html        |  2 +-
 6 files changed, 93 insertions(+), 9 deletions(-)

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@-1 {{static assertion expression is not an integral 
constant expression}}
+//   since-cxx11-note@-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>

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

Reply via email to