[gcc r17-2358] c++: Fix conversion to enum with fixed bool underlying type [PR96496]

Jason Merrill via Gcc-cvs <[email protected]>
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
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" }
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.