c++: error with bit-fields and scoped enums [PR111895]

Message ID 20231024161843.20031-1-polacek@redhat.com
State Accepted
Headers
Series c++: error with bit-fields and scoped enums [PR111895] |

Checks

Context Check Description
snail/gcc-patch-check success Github commit url

Commit Message

Marek Polacek Oct. 24, 2023, 4:18 p.m. UTC
  Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

-- >8 --
Here we issue a bogus error: invalid operands of types 'unsigned char:2'
and 'int' to binary 'operator!=' when casting a bit-field of scoped enum
type to bool.

In build_static_cast_1, perform_direct_initialization_if_possible returns
NULL_TREE, because the invented declaration T t(e) fails, which is
correct.  So we go down to ocp_convert, which has code to deal with this
case:
          /* We can't implicitly convert a scoped enum to bool, so convert
             to the underlying type first.  */
          if (SCOPED_ENUM_P (intype) && (convtype & CONV_STATIC))
            e = build_nop (ENUM_UNDERLYING_TYPE (intype), e);
but the SCOPED_ENUM_P is false since intype is <unnamed-unsigned:2>.
This could be fixed by using unlowered_expr_type.  But then
c_common_truthvalue_conversion/CASE_CONVERT has a similar problem, and
unlowered_expr_type is a C++-only function.

Rather than adding a dummy unlowered_expr_type to C, I think we should
follow [expr.static.cast]p3: "the lvalue-to-rvalue conversion is applied
to the bit-field and the resulting prvalue is used as the operand of the
static_cast."  There are no prvalue bit-fields, so the l-to-r conversion
will get us an expression whose type is the enum.  (I thought we didn't
need decay_conversion because that does a whole lot more but using it
would make sense to me too.)

	PR c++/111895

gcc/cp/ChangeLog:

	* typeck.cc (build_static_cast_1): Call
	convert_bitfield_to_declared_type.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp0x/scoped_enum12.C: New test.
---
 gcc/cp/typeck.cc                           | 9 +++++++++
 gcc/testsuite/g++.dg/cpp0x/scoped_enum12.C | 8 ++++++++
 2 files changed, 17 insertions(+)
 create mode 100644 gcc/testsuite/g++.dg/cpp0x/scoped_enum12.C


base-commit: 99a6c1065de2db04d0f56f4b2cc89acecf21b72e
  

Comments

Jason Merrill Oct. 24, 2023, 8:46 p.m. UTC | #1
On 10/24/23 12:18, Marek Polacek wrote:
> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> 
> -- >8 --
> Here we issue a bogus error: invalid operands of types 'unsigned char:2'
> and 'int' to binary 'operator!=' when casting a bit-field of scoped enum
> type to bool.
> 
> In build_static_cast_1, perform_direct_initialization_if_possible returns
> NULL_TREE, because the invented declaration T t(e) fails, which is
> correct.  So we go down to ocp_convert, which has code to deal with this
> case:
>            /* We can't implicitly convert a scoped enum to bool, so convert
>               to the underlying type first.  */
>            if (SCOPED_ENUM_P (intype) && (convtype & CONV_STATIC))
>              e = build_nop (ENUM_UNDERLYING_TYPE (intype), e);
> but the SCOPED_ENUM_P is false since intype is <unnamed-unsigned:2>.
> This could be fixed by using unlowered_expr_type.  But then
> c_common_truthvalue_conversion/CASE_CONVERT has a similar problem, and
> unlowered_expr_type is a C++-only function.
> 
> Rather than adding a dummy unlowered_expr_type to C, I think we should
> follow [expr.static.cast]p3: "the lvalue-to-rvalue conversion is applied
> to the bit-field and the resulting prvalue is used as the operand of the
> static_cast."  There are no prvalue bit-fields, so the l-to-r conversion
> will get us an expression whose type is the enum.  (I thought we didn't
> need decay_conversion because that does a whole lot more but using it
> would make sense to me too.)

It's possible that we might want some of that more, particularly 
mark_rvalue_use; decay_conversion seems like the right answer.  OK with 
that change.

rvalue() would also make sense, though that seems to be missing a call 
to unlowered_expr_type at the moment.  In fact, after "otherwise, it's 
the lvalue-to-rvalue conversion" in decay_conv should probably just be a 
call to rvalue, with missing bits added to the latter function.

Jason
  
Marek Polacek Oct. 24, 2023, 8:56 p.m. UTC | #2
On Tue, Oct 24, 2023 at 04:46:02PM -0400, Jason Merrill wrote:
> On 10/24/23 12:18, Marek Polacek wrote:
> > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> > 
> > -- >8 --
> > Here we issue a bogus error: invalid operands of types 'unsigned char:2'
> > and 'int' to binary 'operator!=' when casting a bit-field of scoped enum
> > type to bool.
> > 
> > In build_static_cast_1, perform_direct_initialization_if_possible returns
> > NULL_TREE, because the invented declaration T t(e) fails, which is
> > correct.  So we go down to ocp_convert, which has code to deal with this
> > case:
> >            /* We can't implicitly convert a scoped enum to bool, so convert
> >               to the underlying type first.  */
> >            if (SCOPED_ENUM_P (intype) && (convtype & CONV_STATIC))
> >              e = build_nop (ENUM_UNDERLYING_TYPE (intype), e);
> > but the SCOPED_ENUM_P is false since intype is <unnamed-unsigned:2>.
> > This could be fixed by using unlowered_expr_type.  But then
> > c_common_truthvalue_conversion/CASE_CONVERT has a similar problem, and
> > unlowered_expr_type is a C++-only function.
> > 
> > Rather than adding a dummy unlowered_expr_type to C, I think we should
> > follow [expr.static.cast]p3: "the lvalue-to-rvalue conversion is applied
> > to the bit-field and the resulting prvalue is used as the operand of the
> > static_cast."  There are no prvalue bit-fields, so the l-to-r conversion
> > will get us an expression whose type is the enum.  (I thought we didn't
> > need decay_conversion because that does a whole lot more but using it
> > would make sense to me too.)
> 
> It's possible that we might want some of that more, particularly
> mark_rvalue_use; decay_conversion seems like the right answer.  OK with that
> change.

Makes total sense, thank you.  (I'd tested the version with decay_conversion
and it worked fine.)
 
> rvalue() would also make sense, though that seems to be missing a call to
> unlowered_expr_type at the moment.  In fact, after "otherwise, it's the
> lvalue-to-rvalue conversion" in decay_conv should probably just be a call to
> rvalue, with missing bits added to the latter function.

Sounds good; I hope I'll get to it next week.  I'm not going to make it
part of this patch so that I can backport this one to 13 and leave the
cleanup for trunk only.

Marek
  

Patch

diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index f3dc80c40cf..50427090e5d 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -8405,6 +8405,15 @@  build_static_cast_1 (location_t loc, tree type, tree expr, bool c_cast_p,
 	return expr;
       if (TREE_CODE (expr) == EXCESS_PRECISION_EXPR)
 	expr = TREE_OPERAND (expr, 0);
+      /* [expr.static.cast]: "If the value is not a bit-field, the result
+	 refers to the object or the specified base class subobject thereof;
+	 otherwise, the lvalue-to-rvalue conversion is applied to the
+	 bit-field and the resulting prvalue is used as the operand of the
+	 static_cast."  There are no prvalue bit-fields; the l-to-r conversion
+	 will give us an object of the underlying type of the bit-field.  We
+	 can let convert_bitfield_to_declared_type convert EXPR to the desired
+	 type.  */
+      expr = convert_bitfield_to_declared_type (expr);
       return ocp_convert (type, expr, CONV_C_CAST, LOOKUP_NORMAL, complain);
     }
 
diff --git a/gcc/testsuite/g++.dg/cpp0x/scoped_enum12.C b/gcc/testsuite/g++.dg/cpp0x/scoped_enum12.C
new file mode 100644
index 00000000000..1d10431e6dc
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/scoped_enum12.C
@@ -0,0 +1,8 @@ 
+// PR c++/111895
+// { dg-do compile { target c++11 } }
+
+enum class o_field : unsigned char { no, yes, different_from_s };
+struct fields {
+  o_field o : 2;
+};
+bool func(fields f) { return static_cast<bool>(f.o); }