https://gcc.gnu.org/g:ca3810f001338bb4d15da43160e017d453f234e8

commit r17-2358-gca3810f001338bb4d15da43160e017d453f234e8
Author: Joyee Cheung <[email protected]>
Date:   Mon Jul 13 11:35:47 2026 -0400

    c++: Fix conversion to enum with fixed bool underlying type [PR96496]
    
    For an enumeration with a fixed underlying type, [expr.static.cast]/8
    requires the operand to be converted to the underlying type first, and
    then to the enumeration type.
    
    Previously, ocp_convert would convert the operand directly to the
    enumeration type via convert_to_integer_maybe_fold.  For non-bool
    underlying types this gives the required value, but for an underlying
    type of bool it truncates the operand to the enum's 1-bit precision,
    keeping only the low bit and contradicting [conv.bool]: e.g.
    converting 2 yielded the enum value corresponding to false.
    
    This patch fixes it by following the specification and converting
    operands with a fixed underlying type to that type first, then to the
    enumeration type.  For bool this routes the operand through the
    existing boolean-handling code with its truth value conversion, so it
    now goes through -Wint-in-bool-context diagnostics just like a cast to
    bool.  The conversion now no longer emits -Wconversion if the operand
    is out of range since the value is converted first through well-defined
    conversions, consistent with explicit casts to the underlying type
    itself.  This patch also includes tests for the changed warning
    behavior.
    
            PR c++/96496
    
    gcc/cp/ChangeLog:
    
            * cvt.cc (ocp_convert): For an enumeration type with a fixed
            underlying type, convert the operand to that type first, as
            specified by [expr.static.cast]/8.  Update the comment quoting
            [expr.static.cast]/8.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/cpp0x/enum-bool-conv.C: New test.
            * g++.dg/warn/Wconversion-enum-fixed.C: New test.
            * g++.dg/warn/Wint-in-bool-context-bool-enum.C: New test.
    
    Signed-off-by: Joyee Cheung <[email protected]>

Diff:
---
 gcc/cp/cvt.cc                                      |  26 ++++-
 gcc/testsuite/g++.dg/cpp0x/enum-bool-conv.C        | 109 +++++++++++++++++++++
 gcc/testsuite/g++.dg/warn/Wconversion-enum-fixed.C |  24 +++++
 .../g++.dg/warn/Wint-in-bool-context-bool-enum.C   |  13 +++
 4 files changed, 168 insertions(+), 4 deletions(-)

diff --git a/gcc/cp/cvt.cc b/gcc/cp/cvt.cc
index b18f66e582f9..a378b51a2bed 100644
--- a/gcc/cp/cvt.cc
+++ b/gcc/cp/cvt.cc
@@ -838,10 +838,28 @@ ocp_convert (tree type, tree expr, int convtype, int 
flags,
          /* [expr.static.cast]
 
             8. A value of integral or enumeration type can be explicitly
-            converted to an enumeration type. The value is unchanged if
-            the original value is within the range of the enumeration
-            values. Otherwise, the resulting enumeration value is
-            unspecified.  */
+            converted to a complete enumeration type.  If the enumeration
+            type has a fixed underlying type, the value is first converted
+            to that type by integral promotion or integral conversion, if
+            necessary, and then to the enumeration type.  If the
+            enumeration type does not have a fixed underlying type, the
+            value is unchanged if the original value is within the range
+            of the enumeration values, and otherwise, the behavior is
+            undefined.  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, and subsequently to the
+            enumeration type.  */
+         if ((ENUM_FIXED_UNDERLYING_TYPE_P (type)
+              && INTEGRAL_OR_ENUMERATION_TYPE_P (intype))
+             || SCALAR_FLOAT_TYPE_P (intype))
+           {
+             e = ocp_convert (ENUM_UNDERLYING_TYPE (type), e, convtype,
+                              flags, complain);
+             if (e == error_mark_node)
+               return error_mark_node;
+           }
+
          tree val = fold_for_warn (e);
          if ((complain & tf_warning)
              && TREE_CODE (val) == INTEGER_CST
diff --git a/gcc/testsuite/g++.dg/cpp0x/enum-bool-conv.C 
b/gcc/testsuite/g++.dg/cpp0x/enum-bool-conv.C
new file mode 100644
index 000000000000..92196d0e688b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/enum-bool-conv.C
@@ -0,0 +1,109 @@
+// PR c++/96496
+// { dg-do run { target c++11 } }
+// Conversion to an enum with a fixed underlying type of bool should convert
+// the value to bool first ([expr.static.cast]/8, CWG 2338).
+
+enum Flag : bool { kNo, kYes };
+enum class ScopedFlag : bool { kOff, kOn };
+
+struct S {
+  constexpr operator Flag () const { return kYes; }
+  operator bool() = delete;    // but not using this conversion function
+};
+
+template <typename T> struct A
+{
+  enum E : T { kZero, kOne };
+};
+
+static_assert (static_cast<Flag> (2) == kYes, "");
+static_assert (static_cast<Flag> (-1) == kYes, "");
+static_assert ((Flag) 2 == kYes, "");
+static_assert (static_cast<Flag> (true) == kYes, "");
+static_assert (static_cast<Flag> (false) == kNo, "");
+static_assert (static_cast<Flag> (0) == kNo, "");
+static_assert (static_cast<Flag> (1) == kYes, "");
+static_assert (static_cast<Flag> (0.5) == kYes, "");
+static_assert (static_cast<Flag> (0.0) == kNo, "");
+static_assert (static_cast<ScopedFlag> (6) == ScopedFlag::kOn, "");
+static_assert (static_cast<Flag> (ScopedFlag::kOn) == kYes, "");
+static_assert (static_cast<Flag> (S{}) == kYes, "");
+static_assert (static_cast<A<bool>::E> (6) == A<bool>::kOne, "");
+static_assert (static_cast<A<char>::E> (6) == 6, "");
+
+// Helpers to keep the operands non-constant and exercise the
+// runtime conversion.
+
+Flag
+convert (int x)
+{
+  return static_cast<Flag> (x);
+}
+
+Flag
+convert (double d)
+{
+  return static_cast<Flag> (d);
+}
+
+Flag
+convert_cstyle (int x)
+{
+  return (Flag) x;
+}
+
+Flag
+convert_from_bool (bool b)
+{
+  return static_cast<Flag> (b);
+}
+
+ScopedFlag
+convert_scoped (int x)
+{
+  return static_cast<ScopedFlag> (x);
+}
+
+A<bool>::E
+convert_dep_bool (int x)
+{
+  return static_cast<A<bool>::E> (x);
+}
+
+A<char>::E
+convert_dep_char (int x)
+{
+  return static_cast<A<char>::E> (x);
+}
+
+Flag
+convert_from_scoped (ScopedFlag f)
+{
+  return static_cast<Flag> (f);
+}
+
+Flag
+convert_via_operator (S s)
+{
+  return static_cast<Flag> (s);
+}
+
+int
+main ()
+{
+  if (convert (2) != kYes) __builtin_abort ();
+  if (convert (-1) != kYes) __builtin_abort ();
+  if (convert (0) != kNo) __builtin_abort ();
+  if (convert (1) != kYes) __builtin_abort ();
+  if (convert (0.5) != kYes) __builtin_abort ();
+  if (convert (0.0) != kNo) __builtin_abort ();
+  if (convert_cstyle (2) != kYes) __builtin_abort ();
+  if (convert_cstyle (0) != kNo) __builtin_abort ();
+  if (convert_from_bool (true) != kYes) __builtin_abort ();
+  if (convert_from_bool (false) != kNo) __builtin_abort ();
+  if (convert_scoped (6) != ScopedFlag::kOn) __builtin_abort ();
+  if (convert_from_scoped (ScopedFlag::kOn) != kYes) __builtin_abort ();
+  if (convert_via_operator (S{}) != kYes) __builtin_abort ();
+  if (convert_dep_bool (6) != A<bool>::kOne) __builtin_abort ();
+  if (convert_dep_char (6) != 6) __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/warn/Wconversion-enum-fixed.C 
b/gcc/testsuite/g++.dg/warn/Wconversion-enum-fixed.C
new file mode 100644
index 000000000000..a72e232f16d5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wconversion-enum-fixed.C
@@ -0,0 +1,24 @@
+// PR c++/96496
+// { dg-do compile { target c++11 } }
+// { dg-options "-Wconversion" }
+// Conversion to an enum with a fixed underlying type is well-defined
+// ([expr.static.cast]/8, CWG 2338), so -Wconversion should not warn.
+
+enum Flag : bool { kNo, kYes };
+enum C2 : unsigned char { C0 };
+enum SC : signed char { SC0 };
+enum class SI : short { SI0 };
+
+int f1 () { return (Flag) 6; }          // { dg-bogus "outside the range" }
+int f2 () { return (C2) -1; }           // { dg-bogus "outside the range" }
+int f3 () { return (C2) 257; }          // { dg-bogus "outside the range" }
+int f4 () { return (SC) 128; }          // { dg-bogus "outside the range" }
+int f5 () { return (int) (SI) 65537; }  // { dg-bogus "outside the range" }
+int f6 (double d) { return (Flag) d; }  // { dg-bogus "conversion" }
+int f7 () { return (Flag) 2.5; }        // { dg-bogus "conversion" }
+
+// The conversion gives the same value as converting to the underlying type.
+static_assert (static_cast<C2> (257) == static_cast<unsigned char> (257), "");
+static_assert (static_cast<SC> (128) == static_cast<signed char> (128), "");
+static_assert (static_cast<SI> (65537)
+              == static_cast<SI> (static_cast<short> (65537)), "");
diff --git a/gcc/testsuite/g++.dg/warn/Wint-in-bool-context-bool-enum.C 
b/gcc/testsuite/g++.dg/warn/Wint-in-bool-context-bool-enum.C
new file mode 100644
index 000000000000..c15faf12f607
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wint-in-bool-context-bool-enum.C
@@ -0,0 +1,13 @@
+// PR c++/96496
+// { dg-do compile { target c++11 } }
+// { dg-options "-Wint-in-bool-context" }
+// For static_cast<Flag>(expr), -Wint-in-bool-context applies similarly
+// to static_cast<bool>(expr).
+
+enum Flag : bool { kNo, kYes };
+
+Flag f1 (int x) { return static_cast<Flag> (x << 1); } // { dg-warning "in 
boolean context" }
+Flag f2 (int x, int y) { return static_cast<Flag> (x * y); } // { dg-warning 
"in boolean context" }
+Flag f3 (bool c) { return static_cast<Flag> (c ? 2 : 3); } // { dg-warning "in 
boolean context" }
+Flag f4 (int x) { return static_cast<Flag> (x); } // { dg-bogus "in boolean 
context" }
+Flag f5 (int x) { return static_cast<Flag> (x != 0); } // { dg-bogus "in 
boolean context" }

Reply via email to