diff mbox series

c++: Deprecate arithmetic convs on different enums [PR97573]

Message ID 20201028180153.569279-1-polacek@redhat.com
State New
Headers show
Series c++: Deprecate arithmetic convs on different enums [PR97573] | expand

Commit Message

Marek Polacek Oct. 28, 2020, 6:01 p.m. UTC
I noticed that C++20 P1120R0 deprecated certain arithmetic conversions
as outlined in [depr.arith.conv.enum], but we don't warn about them.  In
particular, "If one operand is of enumeration type and the other operand
is of a different enumeration type or a floating-point type, this
behavior is deprecated."  These will likely become ill-formed in C++23,
so we should warn by default in C++20.  To this effect, this patch adds
two new warnings (like clang++): -Wdeprecated-enum-enum-conversion and
-Wdeprecated-enum-float-conversion.  They are enabled by default in
C++20.  In older dialects, to enable these warnings you can now use
-Wenum-conversion which I made available in C++ too.  Note that unlike
C, in C++ it is not enabled by -Wextra, because that breaks bootstrap.

We already warn about comparisons of two different enumeration types via
-Wenum-compare, the rest is handled in this patch: we're performing the
usual arithmetic conversions in these contexts:
  - an arithmetic operation,
  - a bitwise operation,
  - a comparison,
  - a conditional operator,
  - a compound assign operator.

Using the spaceship operator as enum <=> real_type is ill-formed but we
don't reject it yet.  We should also address [depr.array.comp] too, but
it's not handled in this patch.

Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

gcc/c-family/ChangeLog:

	PR c++/97573
	* c-opts.c (c_common_post_options): In C++20, turn on
	-Wdeprecated-enum-enum-conversion and
	-Wdeprecated-enum-float-conversion.
	* c.opt (Wdeprecated-enum-enum-conversion,
	Wdeprecated-enum-float-conversion): New options.
	(Wenum-conversion): Allow for C++ too.

gcc/cp/ChangeLog:

	PR c++/97573
	* call.c (build_conditional_expr_1): Warn about the deprecated
	enum/real type conversion in C++20.  Also warn about a non-enumerated
	and enumerated type in ?: when -Wenum-conversion is on.
	* typeck.c (do_warn_enum_conversions): New function.
	(cp_build_binary_op): Call it.

gcc/ChangeLog:

	PR c++/97573
	* doc/invoke.texi: Document -Wdeprecated-enum-enum-conversion
	and -Wdeprecated-enum-float-conversion.  -Wenum-conversion is
	no longer C/ObjC only.

gcc/testsuite/ChangeLog:

	PR c++/97573
	* g++.dg/cpp0x/linkage2.C: Add dg-warning.
	* g++.dg/parse/attr3.C: Likewise.
	* g++.dg/cpp2a/enum-conv1.C: New test.
	* g++.dg/cpp2a/enum-conv2.C: New test.
	* g++.dg/cpp2a/enum-conv3.C: New test.
---
 gcc/c-family/c-opts.c                   |  10 ++
 gcc/c-family/c.opt                      |  11 ++-
 gcc/cp/call.c                           |  35 +++++--
 gcc/cp/typeck.c                         | 112 +++++++++++++++++++++-
 gcc/doc/invoke.texi                     |  44 ++++++++-
 gcc/testsuite/g++.dg/cpp0x/linkage2.C   |   2 +-
 gcc/testsuite/g++.dg/cpp2a/enum-conv1.C | 120 ++++++++++++++++++++++++
 gcc/testsuite/g++.dg/cpp2a/enum-conv2.C | 115 +++++++++++++++++++++++
 gcc/testsuite/g++.dg/cpp2a/enum-conv3.C | 115 +++++++++++++++++++++++
 gcc/testsuite/g++.dg/parse/attr3.C      |   2 +-
 10 files changed, 549 insertions(+), 17 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/enum-conv1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/enum-conv2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/enum-conv3.C


base-commit: 75ce04fba49eb30b6a8fe23bc3605cf0ef9a8e28

Comments

Jason Merrill Oct. 28, 2020, 6:43 p.m. UTC | #1
On 10/28/20 2:01 PM, Marek Polacek wrote:
> I noticed that C++20 P1120R0 deprecated certain arithmetic conversions
> as outlined in [depr.arith.conv.enum], but we don't warn about them.  In
> particular, "If one operand is of enumeration type and the other operand
> is of a different enumeration type or a floating-point type, this
> behavior is deprecated."  These will likely become ill-formed in C++23,
> so we should warn by default in C++20.  To this effect, this patch adds
> two new warnings (like clang++): -Wdeprecated-enum-enum-conversion and
> -Wdeprecated-enum-float-conversion.  They are enabled by default in
> C++20.  In older dialects, to enable these warnings you can now use
> -Wenum-conversion which I made available in C++ too.  Note that unlike
> C, in C++ it is not enabled by -Wextra, because that breaks bootstrap.
> 
> We already warn about comparisons of two different enumeration types via
> -Wenum-compare, the rest is handled in this patch: we're performing the
> usual arithmetic conversions in these contexts:
>    - an arithmetic operation,
>    - a bitwise operation,
>    - a comparison,
>    - a conditional operator,
>    - a compound assign operator.
> 
> Using the spaceship operator as enum <=> real_type is ill-formed but we
> don't reject it yet.

Hmm, oops.  Will you fix that as well?  It should be simple to fix in 
the SPACESHIP_EXPR block that starts just at the end of this patch.

> We should also address [depr.array.comp] too, but
> it's not handled in this patch.
> 
> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

OK, thanks.

> gcc/c-family/ChangeLog:
> 
> 	PR c++/97573
> 	* c-opts.c (c_common_post_options): In C++20, turn on
> 	-Wdeprecated-enum-enum-conversion and
> 	-Wdeprecated-enum-float-conversion.
> 	* c.opt (Wdeprecated-enum-enum-conversion,
> 	Wdeprecated-enum-float-conversion): New options.
> 	(Wenum-conversion): Allow for C++ too.
> 
> gcc/cp/ChangeLog:
> 
> 	PR c++/97573
> 	* call.c (build_conditional_expr_1): Warn about the deprecated
> 	enum/real type conversion in C++20.  Also warn about a non-enumerated
> 	and enumerated type in ?: when -Wenum-conversion is on.
> 	* typeck.c (do_warn_enum_conversions): New function.
> 	(cp_build_binary_op): Call it.
> 
> gcc/ChangeLog:
> 
> 	PR c++/97573
> 	* doc/invoke.texi: Document -Wdeprecated-enum-enum-conversion
> 	and -Wdeprecated-enum-float-conversion.  -Wenum-conversion is
> 	no longer C/ObjC only.
> 
> gcc/testsuite/ChangeLog:
> 
> 	PR c++/97573
> 	* g++.dg/cpp0x/linkage2.C: Add dg-warning.
> 	* g++.dg/parse/attr3.C: Likewise.
> 	* g++.dg/cpp2a/enum-conv1.C: New test.
> 	* g++.dg/cpp2a/enum-conv2.C: New test.
> 	* g++.dg/cpp2a/enum-conv3.C: New test.
> ---
>   gcc/c-family/c-opts.c                   |  10 ++
>   gcc/c-family/c.opt                      |  11 ++-
>   gcc/cp/call.c                           |  35 +++++--
>   gcc/cp/typeck.c                         | 112 +++++++++++++++++++++-
>   gcc/doc/invoke.texi                     |  44 ++++++++-
>   gcc/testsuite/g++.dg/cpp0x/linkage2.C   |   2 +-
>   gcc/testsuite/g++.dg/cpp2a/enum-conv1.C | 120 ++++++++++++++++++++++++
>   gcc/testsuite/g++.dg/cpp2a/enum-conv2.C | 115 +++++++++++++++++++++++
>   gcc/testsuite/g++.dg/cpp2a/enum-conv3.C | 115 +++++++++++++++++++++++
>   gcc/testsuite/g++.dg/parse/attr3.C      |   2 +-
>   10 files changed, 549 insertions(+), 17 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/enum-conv1.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/enum-conv2.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/enum-conv3.C
> 
> diff --git a/gcc/c-family/c-opts.c b/gcc/c-family/c-opts.c
> index 38d33849423..120f4489f6c 100644
> --- a/gcc/c-family/c-opts.c
> +++ b/gcc/c-family/c-opts.c
> @@ -925,6 +925,16 @@ c_common_post_options (const char **pfilename)
>     SET_OPTION_IF_UNSET (&global_options, &global_options_set, warn_volatile,
>   		       cxx_dialect >= cxx20 && warn_deprecated);
>   
> +  /* -Wdeprecated-enum-enum-conversion is enabled by default in C++20.  */
> +  SET_OPTION_IF_UNSET (&global_options, &global_options_set,
> +		       warn_deprecated_enum_enum_conv,
> +		       cxx_dialect >= cxx20 && warn_deprecated);
> +
> +  /* -Wdeprecated-enum-float-conversion is enabled by default in C++20.  */
> +  SET_OPTION_IF_UNSET (&global_options, &global_options_set,
> +		       warn_deprecated_enum_float_conv,
> +		       cxx_dialect >= cxx20 && warn_deprecated);
> +
>     /* Declone C++ 'structors if -Os.  */
>     if (flag_declone_ctor_dtor == -1)
>       flag_declone_ctor_dtor = optimize_size;
> diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
> index 1009defbf16..10e53ea67c9 100644
> --- a/gcc/c-family/c.opt
> +++ b/gcc/c-family/c.opt
> @@ -518,6 +518,15 @@ C++ ObjC++ Var(warn_deprecated_copy, 2) Warning
>   Mark implicitly-declared copy operations as deprecated if the class has a
>   user-provided copy operation or destructor.
>   
> +Wdeprecated-enum-enum-conversion
> +C++ ObjC++ Var(warn_deprecated_enum_enum_conv) Warning
> +Warn about deprecated arithmetic conversions on operands of enumeration types.
> +
> +Wdeprecated-enum-float-conversion
> +C++ ObjC++ Var(warn_deprecated_enum_float_conv) Warning
> +Warn about deprecated arithmetic conversions on operands where one is of enumeration
> +type and the other is of a floating-point type.
> +
>   Wdesignated-init
>   C ObjC Var(warn_designated_init) Init(1) Warning
>   Warn about positional initialization of structs requiring designated initializers.
> @@ -559,7 +568,7 @@ C ObjC C++ ObjC++ Var(warn_enum_compare) Init(-1) Warning LangEnabledBy(C ObjC,W
>   Warn about comparison of different enum types.
>   
>   Wenum-conversion
> -C ObjC Var(warn_enum_conversion) Init(0) Warning LangEnabledBy(C ObjC,Wextra)
> +C ObjC C++ ObjC++ Var(warn_enum_conversion) Init(0) Warning LangEnabledBy(C ObjC,Wextra)
>   Warn about implicit conversion of enum types.
>   
>   Werror
> diff --git a/gcc/cp/call.c b/gcc/cp/call.c
> index bd662518958..9861be1f856 100644
> --- a/gcc/cp/call.c
> +++ b/gcc/cp/call.c
> @@ -5643,17 +5643,40 @@ build_conditional_expr_1 (const op_location_t &loc,
>   			"in conditional expression: %qT vs %qT",
>   			arg2_type, arg3_type);
>           }
> -      else if (extra_warnings
> +      else if ((complain & tf_warning)
> +	       && warn_deprecated_enum_float_conv
> +	       && ((TREE_CODE (arg2_type) == ENUMERAL_TYPE
> +		    && TREE_CODE (arg3_type) == REAL_TYPE)
> +		   || (TREE_CODE (arg2_type) == REAL_TYPE
> +		       && TREE_CODE (arg3_type) == ENUMERAL_TYPE)))
> +	{
> +	  if (TREE_CODE (arg2_type) == ENUMERAL_TYPE)
> +	    warning_at (loc, OPT_Wdeprecated_enum_float_conversion,
> +			"conditional expression between enumeration type "
> +			"%qT and floating-point type %qT is deprecated",
> +			arg2_type, arg3_type);
> +	  else
> +	    warning_at (loc, OPT_Wdeprecated_enum_float_conversion,
> +			"conditional expression between floating-point "
> +			"type %qT and enumeration type %qT is deprecated",
> +			arg2_type, arg3_type);
> +	}
> +      else if ((extra_warnings || warn_enum_conversion)
>   	       && ((TREE_CODE (arg2_type) == ENUMERAL_TYPE
>   		    && !same_type_p (arg3_type, type_promotes_to (arg2_type)))
>   		   || (TREE_CODE (arg3_type) == ENUMERAL_TYPE
>   		       && !same_type_p (arg2_type,
>   					type_promotes_to (arg3_type)))))
> -        {
> -          if (complain & tf_warning)
> -	    warning_at (loc, OPT_Wextra, "enumerated and non-enumerated "
> -			"type in conditional expression");
> -        }
> +	{
> +	  if (complain & tf_warning)
> +	    {
> +	      enum opt_code opt = (warn_enum_conversion
> +				   ? OPT_Wenum_conversion
> +				   : OPT_Wextra);
> +	      warning_at (loc, opt, "enumerated and "
> +			  "non-enumerated type in conditional expression");
> +	    }
> +	}
>   
>         arg2 = perform_implicit_conversion (result_type, arg2, complain);
>         arg3 = perform_implicit_conversion (result_type, arg3, complain);
> diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c
> index 48d34f1132a..7305310ecbe 100644
> --- a/gcc/cp/typeck.c
> +++ b/gcc/cp/typeck.c
> @@ -4428,6 +4428,104 @@ warn_for_null_address (location_t location, tree op, tsubst_flags_t complain)
>       }
>   }
>   
> +/* Warn about [expr.arith.conv]/2: If one operand is of enumeration type and
> +   the other operand is of a different enumeration type or a floating-point
> +   type, this behavior is deprecated ([depr.arith.conv.enum]).  CODE is the
> +   code of the binary operation, TYPE0 and TYPE1 are the types of the operands,
> +   and LOC is the location for the whole binary expression.
> +   TODO: Consider combining this with -Wenum-compare in build_new_op_1.  */
> +
> +static void
> +do_warn_enum_conversions (location_t loc, enum tree_code code, tree type0,
> +			  tree type1)
> +{
> +  if (TREE_CODE (type0) == ENUMERAL_TYPE
> +      && TREE_CODE (type1) == ENUMERAL_TYPE
> +      && TYPE_MAIN_VARIANT (type0) != TYPE_MAIN_VARIANT (type1))
> +    {
> +      /* In C++20, -Wdeprecated-enum-enum-conversion is on by default.
> +	 Otherwise, warn if -Wenum-conversion is on.  */
> +      enum opt_code opt;
> +      if (warn_deprecated_enum_enum_conv)
> +	opt = OPT_Wdeprecated_enum_enum_conversion;
> +      else if (warn_enum_conversion)
> +	opt = OPT_Wenum_conversion;
> +      else
> +	return;
> +
> +      switch (code)
> +	{
> +	case GT_EXPR:
> +	case LT_EXPR:
> +	case GE_EXPR:
> +	case LE_EXPR:
> +	case EQ_EXPR:
> +	case NE_EXPR:
> +	  /* Comparisons are handled by -Wenum-compare.  */
> +	  return;
> +	case SPACESHIP_EXPR:
> +	  /* This is invalid, don't warn.  */
> +	  return;
> +	case BIT_AND_EXPR:
> +	case BIT_IOR_EXPR:
> +	case BIT_XOR_EXPR:
> +	  warning_at (loc, opt, "bitwise operation between different "
> +		      "enumeration types %qT and %qT is deprecated",
> +		      type0, type1);
> +	  return;
> +	default:
> +	  warning_at (loc, opt, "arithmetic between different enumeration "
> +		      "types %qT and %qT is deprecated", type0, type1);
> +	  return;
> +	}
> +    }
> +  else if ((TREE_CODE (type0) == ENUMERAL_TYPE
> +	    && TREE_CODE (type1) == REAL_TYPE)
> +	   || (TREE_CODE (type0) == REAL_TYPE
> +	       && TREE_CODE (type1) == ENUMERAL_TYPE))
> +    {
> +      const bool enum_first_p = TREE_CODE (type0) == ENUMERAL_TYPE;
> +      /* In C++20, -Wdeprecated-enum-float-conversion is on by default.
> +	 Otherwise, warn if -Wenum-conversion is on.  */
> +      enum opt_code opt;
> +      if (warn_deprecated_enum_float_conv)
> +	opt = OPT_Wdeprecated_enum_float_conversion;
> +      else if (warn_enum_conversion)
> +	opt = OPT_Wenum_conversion;
> +      else
> +	return;
> +
> +      switch (code)
> +	{
> +	case GT_EXPR:
> +	case LT_EXPR:
> +	case GE_EXPR:
> +	case LE_EXPR:
> +	case EQ_EXPR:
> +	case NE_EXPR:
> +	  if (enum_first_p)
> +	    warning_at (loc, opt, "comparison of enumeration type %qT with "
> +			"floating-point type %qT is deprecated",
> +			type0, type1);
> +	  else
> +	    warning_at (loc, opt, "comparison of floating-point type %qT "
> +			"with enumeration type %qT is deprecated",
> +			type0, type1);
> +	  return;
> +	default:
> +	  if (enum_first_p)
> +	    warning_at (loc, opt, "arithmetic between enumeration type %qT "
> +			"and floating-point type %qT is deprecated",
> +			type0, type1);
> +	  else
> +	    warning_at (loc, opt, "arithmetic between floating-point type %qT "
> +			"and enumeration type %qT is deprecated",
> +			type0, type1);
> +	  return;
> +	}
> +    }
> +}
> +
>   /* Build a binary-operation expression without default conversions.
>      CODE is the kind of expression to build.
>      LOCATION is the location_t of the operator in the source code.
> @@ -5445,11 +5543,15 @@ cp_build_binary_op (const op_location_t &location,
>       {
>         result_type = cp_common_type (type0, type1);
>         if (complain & tf_warning)
> -	do_warn_double_promotion (result_type, type0, type1,
> -				  "implicit conversion from %qH to %qI "
> -				  "to match other operand of binary "
> -				  "expression",
> -				  location);
> +	{
> +	  do_warn_double_promotion (result_type, type0, type1,
> +				    "implicit conversion from %qH to %qI "
> +				    "to match other operand of binary "
> +				    "expression",
> +				    location);
> +	  do_warn_enum_conversions (location, code, TREE_TYPE (orig_op0),
> +				    TREE_TYPE (orig_op1));
> +	}
>       }
>   
>     if (code == SPACESHIP_EXPR)
> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> index f82eeea097a..72ae4a23203 100644
> --- a/gcc/doc/invoke.texi
> +++ b/gcc/doc/invoke.texi
> @@ -239,6 +239,7 @@ in the following sections.
>   -Wno-conversion-null  -Wctad-maybe-unsupported @gol
>   -Wctor-dtor-privacy  -Wno-delete-incomplete @gol
>   -Wdelete-non-virtual-dtor  -Wdeprecated-copy  -Wdeprecated-copy-dtor @gol
> +-Wno-deprecated-enum-enum-conversion -Wno-deprecated-enum-float-conversion @gol
>   -Weffc++  -Wextra-semi  -Wno-inaccessible-base @gol
>   -Wno-inherited-variadic-ctor  -Wno-init-list-lifetime @gol
>   -Wno-invalid-offsetof  -Wno-literal-suffix  -Wmismatched-tags @gol
> @@ -3358,6 +3359,42 @@ warning is enabled by @option{-Wextra}.  With
>   @option{-Wdeprecated-copy-dtor}, also deprecate if the class has a
>   user-provided destructor.
>   
> +@item -Wno-deprecated-enum-enum-conversion @r{(C++ and Objective-C++ only)}
> +@opindex Wdeprecated-enum-enum-conversion
> +@opindex Wno-deprecated-enum-enum-conversion
> +Disable the warning about the case when the usual arithmetic conversions
> +are applied on operands where one is of enumeration type and the other is
> +of a different enumeration type.  This conversion was deprecated in C++20.
> +For example:
> +
> +@smallexample
> +enum E1 @{ e @};
> +enum E2 @{ f @};
> +int k = f - e;
> +@end smallexample
> +
> +@option{-Wdeprecated-enum-enum-conversion} is enabled by default with
> +@option{-std=c++20}.  In pre-C++20 dialects, this warning can be enabled
> +by @option{-Wenum-conversion}.
> +
> +@item -Wno-deprecated-enum-float-conversion @r{(C++ and Objective-C++ only)}
> +@opindex Wdeprecated-enum-float-conversion
> +@opindex Wno-deprecated-enum-float-conversion
> +Disable the warning about the case when the usual arithmetic conversions
> +are applied on operands where one is of enumeration type and the other is
> +of a floating-point type.  This conversion was deprecated in C++20.  For
> +example:
> +
> +@smallexample
> +enum E1 @{ e @};
> +enum E2 @{ f @};
> +bool b = e <= 3.7;
> +@end smallexample
> +
> +@option{-Wdeprecated-enum-float-conversion} is enabled by default with
> +@option{-std=c++20}.  In pre-C++20 dialects, this warning can be enabled
> +by @option{-Wenum-conversion}.
> +
>   @item -Wno-init-list-lifetime @r{(C++ and Objective-C++ only)}
>   @opindex Winit-list-lifetime
>   @opindex Wno-init-list-lifetime
> @@ -5271,7 +5308,6 @@ Options} and @ref{Objective-C and Objective-C++ Dialect Options}.
>   -Wcomment  @gol
>   -Wduplicate-decl-specifier @r{(C and Objective-C only)} @gol
>   -Wenum-compare @r{(in C/ObjC; this is on by default in C++)} @gol
> --Wenum-conversion @r{in C/ObjC;} @gol
>   -Wformat   @gol
>   -Wformat-overflow  @gol
>   -Wformat-truncation  @gol
> @@ -5340,6 +5376,7 @@ name is still supported, but the newer name is more descriptive.)
>   -Wcast-function-type  @gol
>   -Wdeprecated-copy @r{(C++ only)} @gol
>   -Wempty-body  @gol
> +-Wenum-conversion @r{(C only)} @gol
>   -Wignored-qualifiers @gol
>   -Wimplicit-fallthrough=3 @gol
>   -Wmissing-field-initializers  @gol
> @@ -8006,11 +8043,12 @@ In C++ enumerated type mismatches in conditional expressions are also
>   diagnosed and the warning is enabled by default.  In C this warning is
>   enabled by @option{-Wall}.
>   
> -@item -Wenum-conversion @r{(C, Objective-C only)}
> +@item -Wenum-conversion
>   @opindex Wenum-conversion
>   @opindex Wno-enum-conversion
>   Warn when a value of enumerated type is implicitly converted to a
> -different enumerated type.  This warning is enabled by @option{-Wextra}.
> +different enumerated type.  This warning is enabled by @option{-Wextra}
> +in C@.
>   
>   @item -Wjump-misses-init @r{(C, Objective-C only)}
>   @opindex Wjump-misses-init
> diff --git a/gcc/testsuite/g++.dg/cpp0x/linkage2.C b/gcc/testsuite/g++.dg/cpp0x/linkage2.C
> index 52858687ed3..549bd825aab 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/linkage2.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/linkage2.C
> @@ -29,5 +29,5 @@ void f() {
>     ba.g(a);              // OK
>     ba.h(a);              // error, B<T>::h never defined
>     i(ba, a);             // OK
> -  e1+e2+e3;
> +  e1+e2+e3; // { dg-warning "arithmetic between different enumeration types" "" { target c++20 } }
>   }
> diff --git a/gcc/testsuite/g++.dg/cpp2a/enum-conv1.C b/gcc/testsuite/g++.dg/cpp2a/enum-conv1.C
> new file mode 100644
> index 00000000000..d4960f334dd
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/enum-conv1.C
> @@ -0,0 +1,120 @@
> +// PR c++/97573
> +// { dg-do compile }
> +// No special options.  In C++20 (only), we should get the deprecated warnings
> +// by default.  -Wenum-compare is enabled by default so some of them will be
> +// printed even pre-C++20.
> +
> +enum E1 { e } e1;
> +enum E2 { f } e2;
> +__extension__ static enum { } u1;
> +__extension__ static enum { } u2;
> +static double d;
> +
> +void
> +conv ()
> +{
> +  bool b1 = e == e1;
> +  bool b2 = e == f; // { dg-warning "comparison between .enum E1. and .enum E2." }
> +  bool b3 = e == 0.0; // { dg-warning "comparison of enumeration type .E1. with floating-point type .double." "" { target c++20 } }
> +  bool b4 = 0.0 == f; // { dg-warning "comparison of floating-point type .double. with enumeration type .E2." "" { target c++20 } }
> +  int n1 = true ? e : f; // { dg-warning "enumerated mismatch" }
> +  int n2 = true ? e : 0.0; // { dg-warning "conditional expression between" "" { target c++20 } }
> +}
> +
> +int
> +enum_enum (bool b)
> +{
> +  int r = 0;
> +  const E1 e1c = e;
> +
> +  r += e - e;
> +  r += e - e1;
> +  r += e - f; // { dg-warning "arithmetic between different enumeration types .E1. and .E2." "" { target c++20 } }
> +  r += f - e; // { dg-warning "arithmetic between different enumeration types .E2. and .E1." "" { target c++20 } }
> +
> +  r += f + f;
> +  r += f + e; // { dg-warning "arithmetic between different enumeration types .E2. and .E1." "" { target c++20 } }
> +  r += e + f; // { dg-warning "arithmetic between different enumeration types .E1. and .E2." "" { target c++20 } }
> +
> +  r += e1 - e2; // { dg-warning "arithmetic between different enumeration types .E1. and .E2." "" { target c++20 } }
> +  r += e1 - e1c;
> +  r += e1c - e1;
> +
> +  r += e * f; // { dg-warning "arithmetic between different enumeration types .E1. and .E2." "" { target c++20 } }
> +  r += f * e; // { dg-warning "arithmetic between different enumeration types .E2. and .E1." "" { target c++20 } }
> +  r += e * e;
> +
> +  r += e1 < e1c;
> +  r += e < e1;
> +  r += e1 < e2; // { dg-warning "comparison between .enum E1. and .enum E2." }
> +  r += e < f; // { dg-warning "comparison between .enum E1. and .enum E2." }
> +  r += f < e; // { dg-warning "comparison between .enum E2. and .enum E1." }
> +
> +  r += e1 == e1c;
> +  r += e == e1;
> +  r += e == f; // { dg-warning "comparison between .enum E1. and .enum E2." }
> +  r += f == e; // { dg-warning "comparison between .enum E2. and .enum E1." }
> +  r += e1 == e2; // { dg-warning "comparison between .enum E1. and .enum E2." }
> +  r += e2 == e1; // { dg-warning "comparison between .enum E2. and .enum E1." }
> +
> +  r += b ? e1 : e1c;
> +  r += b ? e1 : e;
> +  r += b ? f : e; // { dg-warning "enumerated mismatch in conditional expression: .E2. vs .E1." }
> +  r += b ? e1 : e2; // { dg-warning "enumerated mismatch in conditional expression: .E1. vs .E2." }
> +
> +  r += e | f; // { dg-warning "bitwise operation between different enumeration types .E1. and .E2." "" { target c++20 } }
> +  r += e ^ f; // { dg-warning "bitwise operation between different enumeration types .E1. and .E2." "" { target c++20 } }
> +  r += e & f; // { dg-warning "bitwise operation between different enumeration types .E1. and .E2." "" { target c++20 } }
> +  r += !e;
> +  r += e1 | e;
> +
> +  r += e << f;
> +  r += e >> f;
> +  r += e || f;
> +  r += e && f;
> +  e1 = e1c;
> +
> +  // Anonymous enum.
> +  r += u1 - u1;
> +  r += u1 + u2; // { dg-warning "arithmetic between different enumeration types" "" { target c++20 } }
> +  r += u1 * u2; // { dg-warning "arithmetic between different enumeration types" "" { target c++20 } }
> +  r += u1 == u2; // { dg-warning "comparison between" }
> +  r += u1 & u2; // { dg-warning "bitwise operation between different enumeration types" "" { target c++20 } }
> +
> +  return r;
> +}
> +
> +double
> +enum_float (bool b)
> +{
> +  double r = 0.0;
> +
> +  r += e1 - d; // { dg-warning "arithmetic between enumeration type .E1. and floating-point type .double." "" { target c++20 } }
> +  r += d - e1; // { dg-warning "arithmetic between floating-point type .double. and enumeration type .E1." "" { target c++20 } }
> +  r += e1 + d; // { dg-warning "arithmetic between enumeration type .E1. and floating-point type .double." "" { target c++20 } }
> +  r += d + e1; // { dg-warning "arithmetic between floating-point type .double. and enumeration type .E1." "" { target c++20 } }
> +  r += e1 * d; // { dg-warning "arithmetic between enumeration type .E1. and floating-point type .double." "" { target c++20 } }
> +  r += d * e1; // { dg-warning "arithmetic between floating-point type .double. and enumeration type .E1." "" { target c++20 } }
> +  r += u1 * d; // { dg-warning "arithmetic between enumeration type" "" { target c++20 } }
> +  r += d * u1; // { dg-warning "arithmetic between floating-point type" "" { target c++20 } }
> +
> +  r += e1 < d;  // { dg-warning "comparison of enumeration type .E1. with floating-point type .double." "" { target c++20 } }
> +  r += d < e1;  // { dg-warning "comparison of floating-point type .double. with enumeration type .E1." "" { target c++20 } }
> +  r += d == e1; // { dg-warning "comparison of floating-point type .double. with enumeration type .E1." "" { target c++20 } }
> +  r += e1 == d; // { dg-warning "comparison of enumeration type .E1. with floating-point type .double." "" { target c++20 } }
> +  r += u1 == d; // { dg-warning "comparison of enumeration type" "" { target c++20 } }
> +  r += d == u1; // { dg-warning "comparison of floating-point type" "" { target c++20 } }
> +
> +  r += b ? e1 : d; // { dg-warning "conditional expression between enumeration type .E1. and floating-point type .double." "" { target c++20 } }
> +  r += b ? d : e1; // { dg-warning "conditional expression between floating-point type .double. and enumeration type .E1." "" { target c++20 } }
> +  r += b ? d : u1; // { dg-warning "conditional expression between" "" { target c++20 } }
> +  r += b ? u1 : d; // { dg-warning "conditional expression between" "" { target c++20 } }
> +
> +  // FIXME should be error
> +  // e1 <=> d;
> +
> +  d += e1; // { dg-warning "arithmetic between floating-point type .double. and enumeration type .E1." "" { target c++20 } }
> +  d = e1;
> +
> +  return r;
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp2a/enum-conv2.C b/gcc/testsuite/g++.dg/cpp2a/enum-conv2.C
> new file mode 100644
> index 00000000000..f15827bda14
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/enum-conv2.C
> @@ -0,0 +1,115 @@
> +// PR c++/97573
> +// { dg-do compile { target c++20 } }
> +// { dg-options "-Wno-deprecated -Wno-enum-compare" }
> +
> +enum E1 { e } e1;
> +enum E2 { f } e2;
> +__extension__ static enum { } u1;
> +__extension__ static enum { } u2;
> +static double d;
> +
> +void
> +conv ()
> +{
> +  bool b1 = e == e1;
> +  bool b2 = e == f;
> +  bool b3 = e == 0.0;
> +  bool b4 = 0.0 == f;
> +  int n1 = true ? e : f;
> +  int n2 = true ? e : 0.0;
> +}
> +
> +int
> +enum_enum (bool b)
> +{
> +  int r = 0;
> +  const E1 e1c = e;
> +
> +  r += e - e;
> +  r += e - e1;
> +  r += e - f;
> +  r += f - e;
> +
> +  r += f + f;
> +  r += f + e;
> +  r += e + f;
> +
> +  r += e1 - e2;
> +  r += e1 - e1c;
> +  r += e1c - e1;
> +
> +  r += e * f;
> +  r += f * e;
> +  r += e * e;
> +
> +  r += e1 < e1c;
> +  r += e < e1;
> +  r += e1 < e2;
> +  r += e < f;
> +  r += f < e;
> +
> +  r += e1 == e1c;
> +  r += e == e1;
> +  r += e == f;
> +  r += f == e;
> +  r += e1 == e2;
> +  r += e2 == e1;
> +
> +  r += b ? e1 : e1c;
> +  r += b ? e1 : e;
> +  r += b ? f : e;
> +  r += b ? e1 : e2;
> +
> +  r += e | f;
> +  r += e ^ f;
> +  r += e & f;
> +  r += !e;
> +  r += e1 | e;
> +
> +  r += e << f;
> +  r += e >> f;
> +  r += e || f;
> +  r += e && f;
> +  e1 = e1c;
> +
> +  // Anonymous enum.
> +  r += u1 - u1;
> +  r += u1 + u2;
> +  r += u1 * u2;
> +  r += u1 == u2;
> +  r += u1 & u2;
> +
> +  return r;
> +}
> +
> +double
> +enum_float (bool b)
> +{
> +  double r = 0.0;
> +
> +  r += e1 - d;
> +  r += d - e1;
> +  r += e1 + d;
> +  r += d + e1;
> +  r += e1 * d;
> +  r += d * e1;
> +  r += u1 * d;
> +  r += d * u1;
> +
> +  r += e1 < d;
> +  r += d < e1;
> +  r += d == e1;
> +  r += e1 == d;
> +  r += u1 == d;
> +  r += d == u1;
> +
> +  r += b ? e1 : d;
> +  r += b ? d : e1;
> +  r += b ? d : u1;
> +  r += b ? u1 : d;
> +
> +  d += e1;
> +  d = e1;
> +
> +  return r;
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp2a/enum-conv3.C b/gcc/testsuite/g++.dg/cpp2a/enum-conv3.C
> new file mode 100644
> index 00000000000..67bdf1600d7
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/enum-conv3.C
> @@ -0,0 +1,115 @@
> +// PR c++/97573
> +// { dg-do compile { target { c++17_down } } }
> +// { dg-options "-Wenum-conversion" }
> +
> +enum E1 { e } e1;
> +enum E2 { f } e2;
> +__extension__ static enum { } u1;
> +__extension__ static enum { } u2;
> +static double d;
> +
> +void
> +conv ()
> +{
> +  bool b1 = e == e1;
> +  bool b2 = e == f; // { dg-warning "comparison between .enum E1. and .enum E2." }
> +  bool b3 = e == 0.0; // { dg-warning "comparison of enumeration type .E1. with floating-point type .double." }
> +  bool b4 = 0.0 == f; // { dg-warning "comparison of floating-point type .double. with enumeration type .E2." }
> +  int n1 = true ? e : f; // { dg-warning "enumerated mismatch" }
> +  int n2 = true ? e : 0.0; // { dg-warning "enumerated and non-enumerated type in conditional expression" }
> +}
> +
> +int
> +enum_enum (bool b)
> +{
> +  int r = 0;
> +  const E1 e1c = e;
> +
> +  r += e - e;
> +  r += e - e1;
> +  r += e - f; // { dg-warning "arithmetic between different enumeration types .E1. and .E2." }
> +  r += f - e; // { dg-warning "arithmetic between different enumeration types .E2. and .E1." }
> +
> +  r += f + f;
> +  r += f + e; // { dg-warning "arithmetic between different enumeration types .E2. and .E1." }
> +  r += e + f; // { dg-warning "arithmetic between different enumeration types .E1. and .E2." }
> +
> +  r += e1 - e2; // { dg-warning "arithmetic between different enumeration types .E1. and .E2." }
> +  r += e1 - e1c;
> +  r += e1c - e1;
> +
> +  r += e * f; // { dg-warning "arithmetic between different enumeration types .E1. and .E2." }
> +  r += f * e; // { dg-warning "arithmetic between different enumeration types .E2. and .E1." }
> +  r += e * e;
> +
> +  r += e1 < e1c;
> +  r += e < e1;
> +  r += e1 < e2; // { dg-warning "comparison between .enum E1. and .enum E2." }
> +  r += e < f; // { dg-warning "comparison between .enum E1. and .enum E2." }
> +  r += f < e; // { dg-warning "comparison between .enum E2. and .enum E1." }
> +
> +  r += e1 == e1c;
> +  r += e == e1;
> +  r += e == f; // { dg-warning "comparison between .enum E1. and .enum E2." }
> +  r += f == e; // { dg-warning "comparison between .enum E2. and .enum E1." }
> +  r += e1 == e2; // { dg-warning "comparison between .enum E1. and .enum E2." }
> +  r += e2 == e1; // { dg-warning "comparison between .enum E2. and .enum E1." }
> +
> +  r += b ? e1 : e1c;
> +  r += b ? e1 : e;
> +  r += b ? f : e; // { dg-warning "enumerated mismatch in conditional expression: .E2. vs .E1." }
> +  r += b ? e1 : e2; // { dg-warning "enumerated mismatch in conditional expression: .E1. vs .E2." }
> +
> +  r += e | f; // { dg-warning "bitwise operation between different enumeration types .E1. and .E2." }
> +  r += e ^ f; // { dg-warning "bitwise operation between different enumeration types .E1. and .E2." }
> +  r += e & f; // { dg-warning "bitwise operation between different enumeration types .E1. and .E2." }
> +  r += !e;
> +  r += e1 | e;
> +
> +  r += e << f;
> +  r += e >> f;
> +  r += e || f;
> +  r += e && f;
> +  e1 = e1c;
> +
> +  // Anonymous enum.
> +  r += u1 - u1;
> +  r += u1 + u2; // { dg-warning "arithmetic between different enumeration types" }
> +  r += u1 * u2; // { dg-warning "arithmetic between different enumeration types" }
> +  r += u1 == u2; // { dg-warning "comparison between" }
> +  r += u1 & u2; // { dg-warning "bitwise operation between different enumeration types" }
> +
> +  return r;
> +}
> +
> +double
> +enum_float (bool b)
> +{
> +  double r = 0.0;
> +
> +  r += e1 - d; // { dg-warning "arithmetic between enumeration type .E1. and floating-point type .double." }
> +  r += d - e1; // { dg-warning "arithmetic between floating-point type .double. and enumeration type .E1." }
> +  r += e1 + d; // { dg-warning "arithmetic between enumeration type .E1. and floating-point type .double." }
> +  r += d + e1; // { dg-warning "arithmetic between floating-point type .double. and enumeration type .E1." }
> +  r += e1 * d; // { dg-warning "arithmetic between enumeration type .E1. and floating-point type .double." }
> +  r += d * e1; // { dg-warning "arithmetic between floating-point type .double. and enumeration type .E1." }
> +  r += u1 * d; // { dg-warning "arithmetic between enumeration type" }
> +  r += d * u1; // { dg-warning "arithmetic between floating-point type" }
> +
> +  r += e1 < d;  // { dg-warning "comparison of enumeration type .E1. with floating-point type .double." }
> +  r += d < e1;  // { dg-warning "comparison of floating-point type .double. with enumeration type .E1." }
> +  r += d == e1; // { dg-warning "comparison of floating-point type .double. with enumeration type .E1." }
> +  r += e1 == d; // { dg-warning "comparison of enumeration type .E1. with floating-point type .double." }
> +  r += u1 == d; // { dg-warning "comparison of enumeration type" }
> +  r += d == u1; // { dg-warning "comparison of floating-point type" }
> +
> +  r += b ? e1 : d; // { dg-warning "enumerated and non-enumerated type in conditional expression" }
> +  r += b ? d : e1; // { dg-warning "enumerated and non-enumerated type in conditional expression" }
> +  r += b ? d : u1; // { dg-warning "enumerated and non-enumerated type in conditional expression" }
> +  r += b ? u1 : d; // { dg-warning "enumerated and non-enumerated type in conditional expression" }
> +
> +  d += e1; // { dg-warning "arithmetic between floating-point type .double. and enumeration type .E1." }
> +  d = e1;
> +
> +  return r;
> +}
> diff --git a/gcc/testsuite/g++.dg/parse/attr3.C b/gcc/testsuite/g++.dg/parse/attr3.C
> index 57fa60e130e..de095988015 100644
> --- a/gcc/testsuite/g++.dg/parse/attr3.C
> +++ b/gcc/testsuite/g++.dg/parse/attr3.C
> @@ -10,5 +10,5 @@ int main () {
>       S::F y;	// { dg-warning "'F' is deprecated" }
>       y = S::f;
>   
> -    return x + y;
> +    return x + y; // { dg-warning "arithmetic between different enumeration types" "" { target c++20 } }
>   }
> 
> base-commit: 75ce04fba49eb30b6a8fe23bc3605cf0ef9a8e28
>
Marek Polacek Oct. 29, 2020, 2:46 a.m. UTC | #2
On Wed, Oct 28, 2020 at 02:43:30PM -0400, Jason Merrill wrote:
> On 10/28/20 2:01 PM, Marek Polacek wrote:
> > I noticed that C++20 P1120R0 deprecated certain arithmetic conversions
> > as outlined in [depr.arith.conv.enum], but we don't warn about them.  In
> > particular, "If one operand is of enumeration type and the other operand
> > is of a different enumeration type or a floating-point type, this
> > behavior is deprecated."  These will likely become ill-formed in C++23,
> > so we should warn by default in C++20.  To this effect, this patch adds
> > two new warnings (like clang++): -Wdeprecated-enum-enum-conversion and
> > -Wdeprecated-enum-float-conversion.  They are enabled by default in
> > C++20.  In older dialects, to enable these warnings you can now use
> > -Wenum-conversion which I made available in C++ too.  Note that unlike
> > C, in C++ it is not enabled by -Wextra, because that breaks bootstrap.
> > 
> > We already warn about comparisons of two different enumeration types via
> > -Wenum-compare, the rest is handled in this patch: we're performing the
> > usual arithmetic conversions in these contexts:
> >    - an arithmetic operation,
> >    - a bitwise operation,
> >    - a comparison,
> >    - a conditional operator,
> >    - a compound assign operator.
> > 
> > Using the spaceship operator as enum <=> real_type is ill-formed but we
> > don't reject it yet.
> 
> Hmm, oops.  Will you fix that as well?  It should be simple to fix in the
> SPACESHIP_EXPR block that starts just at the end of this patch.

Sure.

Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

From 8ae2e45f2dd35510aed3be1ab249b8612e33f00d Mon Sep 17 00:00:00 2001
From: Marek Polacek <polacek@redhat.com>
Date: Wed, 28 Oct 2020 19:02:29 -0400
Subject: [PATCH] c++: Reject float <=> enum.

As [depr.arith.conv.enum] says, these are ill-formed.

gcc/cp/ChangeLog:

	* typeck.c (do_warn_enum_conversions): Don't warn for SPACESHIP_EXPR.
	(cp_build_binary_op): Reject float <=> enum or enum <=> float.  Use
	CP_INTEGRAL_TYPE_P instead of INTEGRAL_OR_ENUMERATION_TYPE_P.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp2a/enum-conv1.C: Remove unused code.
	* g++.dg/cpp2a/spaceship-err5.C: New test.
---
 gcc/cp/typeck.c                             | 13 ++++++++++--
 gcc/testsuite/g++.dg/cpp2a/enum-conv1.C     |  3 ---
 gcc/testsuite/g++.dg/cpp2a/spaceship-err5.C | 23 +++++++++++++++++++++
 3 files changed, 34 insertions(+), 5 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/spaceship-err5.C

diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c
index 7305310ecbe..d3b701610cf 100644
--- a/gcc/cp/typeck.c
+++ b/gcc/cp/typeck.c
@@ -4512,6 +4512,9 @@ do_warn_enum_conversions (location_t loc, enum tree_code code, tree type0,
 			"with enumeration type %qT is deprecated",
 			type0, type1);
 	  return;
+	case SPACESHIP_EXPR:
+	  /* This is invalid, don't warn.  */
+	  return;
 	default:
 	  if (enum_first_p)
 	    warning_at (loc, opt, "arithmetic between enumeration type %qT "
@@ -5584,6 +5587,12 @@ cp_build_binary_op (const op_location_t &location,
 	   arithmetic conversions are applied to the operands."  So we don't do
 	   arithmetic conversions if the operands both have enumeral type.  */
 	result_type = NULL_TREE;
+      else if ((orig_code0 == ENUMERAL_TYPE && orig_code1 == REAL_TYPE)
+	       || (orig_code0 == REAL_TYPE && orig_code1 == ENUMERAL_TYPE))
+	/* [depr.arith.conv.enum]: Three-way comparisons between such operands
+	   [where one is of enumeration type and the other is of a different
+	   enumeration type or a floating-point type] are ill-formed.  */
+	result_type = NULL_TREE;
 
       if (result_type)
 	{
@@ -5598,12 +5607,12 @@ cp_build_binary_op (const op_location_t &location,
 	     type to a floating point type, the program is ill-formed.  */
 	  bool ok = true;
 	  if (TREE_CODE (result_type) == REAL_TYPE
-	      && INTEGRAL_OR_ENUMERATION_TYPE_P (TREE_TYPE (orig_op0)))
+	      && CP_INTEGRAL_TYPE_P (orig_type0))
 	    /* OK */;
 	  else if (!check_narrowing (result_type, orig_op0, complain))
 	    ok = false;
 	  if (TREE_CODE (result_type) == REAL_TYPE
-	      && INTEGRAL_OR_ENUMERATION_TYPE_P (TREE_TYPE (orig_op1)))
+	      && CP_INTEGRAL_TYPE_P (orig_type1))
 	    /* OK */;
 	  else if (!check_narrowing (result_type, orig_op1, complain))
 	    ok = false;
diff --git a/gcc/testsuite/g++.dg/cpp2a/enum-conv1.C b/gcc/testsuite/g++.dg/cpp2a/enum-conv1.C
index d4960f334dd..4571b5e8968 100644
--- a/gcc/testsuite/g++.dg/cpp2a/enum-conv1.C
+++ b/gcc/testsuite/g++.dg/cpp2a/enum-conv1.C
@@ -110,9 +110,6 @@ enum_float (bool b)
   r += b ? d : u1; // { dg-warning "conditional expression between" "" { target c++20 } }
   r += b ? u1 : d; // { dg-warning "conditional expression between" "" { target c++20 } }
 
-  // FIXME should be error
-  // e1 <=> d;
-
   d += e1; // { dg-warning "arithmetic between floating-point type .double. and enumeration type .E1." "" { target c++20 } }
   d = e1;
 
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-err5.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-err5.C
new file mode 100644
index 00000000000..3dc2a0f2365
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-err5.C
@@ -0,0 +1,23 @@
+// { dg-do compile { target c++20 } }
+// Test [depr.arith.conv.enum] for <=>.
+
+#include <compare>
+
+enum E1 { e } e1;
+enum E2 { f } e2;
+static double d;
+
+void
+g ()
+{
+  void(e1 <=> e);
+  e1 <=> d; // { dg-error "invalid operands of types .E1. and .double." }
+  d <=> e1; // { dg-error "invalid operands of types .double. and .E1." }
+  e <=> d; // { dg-error "invalid operands of types .E1. and .double." }
+  d <=> e; // { dg-error "invalid operands of types .double. and .E1." }
+
+  e <=> f; // { dg-error "invalid operands of types .E1. and .E2." }
+  f <=> e; // { dg-error "invalid operands of types .E2. and .E1." }
+  e1 <=> e2; // { dg-error "invalid operands of types .E1. and .E2." }
+  e2 <=> e1; // { dg-error "invalid operands of types .E2. and .E1." }
+}

base-commit: 4166ebedf8b8a302b86132fdf846fac204c83368
Jason Merrill Oct. 29, 2020, 3:19 p.m. UTC | #3
On 10/28/20 10:46 PM, Marek Polacek wrote:
> On Wed, Oct 28, 2020 at 02:43:30PM -0400, Jason Merrill wrote:
>> On 10/28/20 2:01 PM, Marek Polacek wrote:
>>> I noticed that C++20 P1120R0 deprecated certain arithmetic conversions
>>> as outlined in [depr.arith.conv.enum], but we don't warn about them.  In
>>> particular, "If one operand is of enumeration type and the other operand
>>> is of a different enumeration type or a floating-point type, this
>>> behavior is deprecated."  These will likely become ill-formed in C++23,
>>> so we should warn by default in C++20.  To this effect, this patch adds
>>> two new warnings (like clang++): -Wdeprecated-enum-enum-conversion and
>>> -Wdeprecated-enum-float-conversion.  They are enabled by default in
>>> C++20.  In older dialects, to enable these warnings you can now use
>>> -Wenum-conversion which I made available in C++ too.  Note that unlike
>>> C, in C++ it is not enabled by -Wextra, because that breaks bootstrap.
>>>
>>> We already warn about comparisons of two different enumeration types via
>>> -Wenum-compare, the rest is handled in this patch: we're performing the
>>> usual arithmetic conversions in these contexts:
>>>     - an arithmetic operation,
>>>     - a bitwise operation,
>>>     - a comparison,
>>>     - a conditional operator,
>>>     - a compound assign operator.
>>>
>>> Using the spaceship operator as enum <=> real_type is ill-formed but we
>>> don't reject it yet.
>>
>> Hmm, oops.  Will you fix that as well?  It should be simple to fix in the
>> SPACESHIP_EXPR block that starts just at the end of this patch.
> 
> Sure.
> 
> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

OK, thanks.

>  From 8ae2e45f2dd35510aed3be1ab249b8612e33f00d Mon Sep 17 00:00:00 2001
> From: Marek Polacek <polacek@redhat.com>
> Date: Wed, 28 Oct 2020 19:02:29 -0400
> Subject: [PATCH] c++: Reject float <=> enum.
> 
> As [depr.arith.conv.enum] says, these are ill-formed.
> 
> gcc/cp/ChangeLog:
> 
> 	* typeck.c (do_warn_enum_conversions): Don't warn for SPACESHIP_EXPR.
> 	(cp_build_binary_op): Reject float <=> enum or enum <=> float.  Use
> 	CP_INTEGRAL_TYPE_P instead of INTEGRAL_OR_ENUMERATION_TYPE_P.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp2a/enum-conv1.C: Remove unused code.
> 	* g++.dg/cpp2a/spaceship-err5.C: New test.
> ---
>   gcc/cp/typeck.c                             | 13 ++++++++++--
>   gcc/testsuite/g++.dg/cpp2a/enum-conv1.C     |  3 ---
>   gcc/testsuite/g++.dg/cpp2a/spaceship-err5.C | 23 +++++++++++++++++++++
>   3 files changed, 34 insertions(+), 5 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/spaceship-err5.C
> 
> diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c
> index 7305310ecbe..d3b701610cf 100644
> --- a/gcc/cp/typeck.c
> +++ b/gcc/cp/typeck.c
> @@ -4512,6 +4512,9 @@ do_warn_enum_conversions (location_t loc, enum tree_code code, tree type0,
>   			"with enumeration type %qT is deprecated",
>   			type0, type1);
>   	  return;
> +	case SPACESHIP_EXPR:
> +	  /* This is invalid, don't warn.  */
> +	  return;
>   	default:
>   	  if (enum_first_p)
>   	    warning_at (loc, opt, "arithmetic between enumeration type %qT "
> @@ -5584,6 +5587,12 @@ cp_build_binary_op (const op_location_t &location,
>   	   arithmetic conversions are applied to the operands."  So we don't do
>   	   arithmetic conversions if the operands both have enumeral type.  */
>   	result_type = NULL_TREE;
> +      else if ((orig_code0 == ENUMERAL_TYPE && orig_code1 == REAL_TYPE)
> +	       || (orig_code0 == REAL_TYPE && orig_code1 == ENUMERAL_TYPE))
> +	/* [depr.arith.conv.enum]: Three-way comparisons between such operands
> +	   [where one is of enumeration type and the other is of a different
> +	   enumeration type or a floating-point type] are ill-formed.  */
> +	result_type = NULL_TREE;
>   
>         if (result_type)
>   	{
> @@ -5598,12 +5607,12 @@ cp_build_binary_op (const op_location_t &location,
>   	     type to a floating point type, the program is ill-formed.  */
>   	  bool ok = true;
>   	  if (TREE_CODE (result_type) == REAL_TYPE
> -	      && INTEGRAL_OR_ENUMERATION_TYPE_P (TREE_TYPE (orig_op0)))
> +	      && CP_INTEGRAL_TYPE_P (orig_type0))
>   	    /* OK */;
>   	  else if (!check_narrowing (result_type, orig_op0, complain))
>   	    ok = false;
>   	  if (TREE_CODE (result_type) == REAL_TYPE
> -	      && INTEGRAL_OR_ENUMERATION_TYPE_P (TREE_TYPE (orig_op1)))
> +	      && CP_INTEGRAL_TYPE_P (orig_type1))
>   	    /* OK */;
>   	  else if (!check_narrowing (result_type, orig_op1, complain))
>   	    ok = false;
> diff --git a/gcc/testsuite/g++.dg/cpp2a/enum-conv1.C b/gcc/testsuite/g++.dg/cpp2a/enum-conv1.C
> index d4960f334dd..4571b5e8968 100644
> --- a/gcc/testsuite/g++.dg/cpp2a/enum-conv1.C
> +++ b/gcc/testsuite/g++.dg/cpp2a/enum-conv1.C
> @@ -110,9 +110,6 @@ enum_float (bool b)
>     r += b ? d : u1; // { dg-warning "conditional expression between" "" { target c++20 } }
>     r += b ? u1 : d; // { dg-warning "conditional expression between" "" { target c++20 } }
>   
> -  // FIXME should be error
> -  // e1 <=> d;
> -
>     d += e1; // { dg-warning "arithmetic between floating-point type .double. and enumeration type .E1." "" { target c++20 } }
>     d = e1;
>   
> diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-err5.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-err5.C
> new file mode 100644
> index 00000000000..3dc2a0f2365
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-err5.C
> @@ -0,0 +1,23 @@
> +// { dg-do compile { target c++20 } }
> +// Test [depr.arith.conv.enum] for <=>.
> +
> +#include <compare>
> +
> +enum E1 { e } e1;
> +enum E2 { f } e2;
> +static double d;
> +
> +void
> +g ()
> +{
> +  void(e1 <=> e);
> +  e1 <=> d; // { dg-error "invalid operands of types .E1. and .double." }
> +  d <=> e1; // { dg-error "invalid operands of types .double. and .E1." }
> +  e <=> d; // { dg-error "invalid operands of types .E1. and .double." }
> +  d <=> e; // { dg-error "invalid operands of types .double. and .E1." }
> +
> +  e <=> f; // { dg-error "invalid operands of types .E1. and .E2." }
> +  f <=> e; // { dg-error "invalid operands of types .E2. and .E1." }
> +  e1 <=> e2; // { dg-error "invalid operands of types .E1. and .E2." }
> +  e2 <=> e1; // { dg-error "invalid operands of types .E2. and .E1." }
> +}
> 
> base-commit: 4166ebedf8b8a302b86132fdf846fac204c83368
>
diff mbox series

Patch

diff --git a/gcc/c-family/c-opts.c b/gcc/c-family/c-opts.c
index 38d33849423..120f4489f6c 100644
--- a/gcc/c-family/c-opts.c
+++ b/gcc/c-family/c-opts.c
@@ -925,6 +925,16 @@  c_common_post_options (const char **pfilename)
   SET_OPTION_IF_UNSET (&global_options, &global_options_set, warn_volatile,
 		       cxx_dialect >= cxx20 && warn_deprecated);
 
+  /* -Wdeprecated-enum-enum-conversion is enabled by default in C++20.  */
+  SET_OPTION_IF_UNSET (&global_options, &global_options_set,
+		       warn_deprecated_enum_enum_conv,
+		       cxx_dialect >= cxx20 && warn_deprecated);
+
+  /* -Wdeprecated-enum-float-conversion is enabled by default in C++20.  */
+  SET_OPTION_IF_UNSET (&global_options, &global_options_set,
+		       warn_deprecated_enum_float_conv,
+		       cxx_dialect >= cxx20 && warn_deprecated);
+
   /* Declone C++ 'structors if -Os.  */
   if (flag_declone_ctor_dtor == -1)
     flag_declone_ctor_dtor = optimize_size;
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 1009defbf16..10e53ea67c9 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -518,6 +518,15 @@  C++ ObjC++ Var(warn_deprecated_copy, 2) Warning
 Mark implicitly-declared copy operations as deprecated if the class has a
 user-provided copy operation or destructor.
 
+Wdeprecated-enum-enum-conversion
+C++ ObjC++ Var(warn_deprecated_enum_enum_conv) Warning
+Warn about deprecated arithmetic conversions on operands of enumeration types.
+
+Wdeprecated-enum-float-conversion
+C++ ObjC++ Var(warn_deprecated_enum_float_conv) Warning
+Warn about deprecated arithmetic conversions on operands where one is of enumeration
+type and the other is of a floating-point type.
+
 Wdesignated-init
 C ObjC Var(warn_designated_init) Init(1) Warning
 Warn about positional initialization of structs requiring designated initializers.
@@ -559,7 +568,7 @@  C ObjC C++ ObjC++ Var(warn_enum_compare) Init(-1) Warning LangEnabledBy(C ObjC,W
 Warn about comparison of different enum types.
 
 Wenum-conversion
-C ObjC Var(warn_enum_conversion) Init(0) Warning LangEnabledBy(C ObjC,Wextra)
+C ObjC C++ ObjC++ Var(warn_enum_conversion) Init(0) Warning LangEnabledBy(C ObjC,Wextra)
 Warn about implicit conversion of enum types.
 
 Werror
diff --git a/gcc/cp/call.c b/gcc/cp/call.c
index bd662518958..9861be1f856 100644
--- a/gcc/cp/call.c
+++ b/gcc/cp/call.c
@@ -5643,17 +5643,40 @@  build_conditional_expr_1 (const op_location_t &loc,
 			"in conditional expression: %qT vs %qT",
 			arg2_type, arg3_type);
         }
-      else if (extra_warnings
+      else if ((complain & tf_warning)
+	       && warn_deprecated_enum_float_conv
+	       && ((TREE_CODE (arg2_type) == ENUMERAL_TYPE
+		    && TREE_CODE (arg3_type) == REAL_TYPE)
+		   || (TREE_CODE (arg2_type) == REAL_TYPE
+		       && TREE_CODE (arg3_type) == ENUMERAL_TYPE)))
+	{
+	  if (TREE_CODE (arg2_type) == ENUMERAL_TYPE)
+	    warning_at (loc, OPT_Wdeprecated_enum_float_conversion,
+			"conditional expression between enumeration type "
+			"%qT and floating-point type %qT is deprecated",
+			arg2_type, arg3_type);
+	  else
+	    warning_at (loc, OPT_Wdeprecated_enum_float_conversion,
+			"conditional expression between floating-point "
+			"type %qT and enumeration type %qT is deprecated",
+			arg2_type, arg3_type);
+	}
+      else if ((extra_warnings || warn_enum_conversion)
 	       && ((TREE_CODE (arg2_type) == ENUMERAL_TYPE
 		    && !same_type_p (arg3_type, type_promotes_to (arg2_type)))
 		   || (TREE_CODE (arg3_type) == ENUMERAL_TYPE
 		       && !same_type_p (arg2_type,
 					type_promotes_to (arg3_type)))))
-        {
-          if (complain & tf_warning)
-	    warning_at (loc, OPT_Wextra, "enumerated and non-enumerated "
-			"type in conditional expression");
-        }
+	{
+	  if (complain & tf_warning)
+	    {
+	      enum opt_code opt = (warn_enum_conversion
+				   ? OPT_Wenum_conversion
+				   : OPT_Wextra);
+	      warning_at (loc, opt, "enumerated and "
+			  "non-enumerated type in conditional expression");
+	    }
+	}
 
       arg2 = perform_implicit_conversion (result_type, arg2, complain);
       arg3 = perform_implicit_conversion (result_type, arg3, complain);
diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c
index 48d34f1132a..7305310ecbe 100644
--- a/gcc/cp/typeck.c
+++ b/gcc/cp/typeck.c
@@ -4428,6 +4428,104 @@  warn_for_null_address (location_t location, tree op, tsubst_flags_t complain)
     }
 }
 
+/* Warn about [expr.arith.conv]/2: If one operand is of enumeration type and
+   the other operand is of a different enumeration type or a floating-point
+   type, this behavior is deprecated ([depr.arith.conv.enum]).  CODE is the
+   code of the binary operation, TYPE0 and TYPE1 are the types of the operands,
+   and LOC is the location for the whole binary expression.
+   TODO: Consider combining this with -Wenum-compare in build_new_op_1.  */
+
+static void
+do_warn_enum_conversions (location_t loc, enum tree_code code, tree type0,
+			  tree type1)
+{
+  if (TREE_CODE (type0) == ENUMERAL_TYPE
+      && TREE_CODE (type1) == ENUMERAL_TYPE
+      && TYPE_MAIN_VARIANT (type0) != TYPE_MAIN_VARIANT (type1))
+    {
+      /* In C++20, -Wdeprecated-enum-enum-conversion is on by default.
+	 Otherwise, warn if -Wenum-conversion is on.  */
+      enum opt_code opt;
+      if (warn_deprecated_enum_enum_conv)
+	opt = OPT_Wdeprecated_enum_enum_conversion;
+      else if (warn_enum_conversion)
+	opt = OPT_Wenum_conversion;
+      else
+	return;
+
+      switch (code)
+	{
+	case GT_EXPR:
+	case LT_EXPR:
+	case GE_EXPR:
+	case LE_EXPR:
+	case EQ_EXPR:
+	case NE_EXPR:
+	  /* Comparisons are handled by -Wenum-compare.  */
+	  return;
+	case SPACESHIP_EXPR:
+	  /* This is invalid, don't warn.  */
+	  return;
+	case BIT_AND_EXPR:
+	case BIT_IOR_EXPR:
+	case BIT_XOR_EXPR:
+	  warning_at (loc, opt, "bitwise operation between different "
+		      "enumeration types %qT and %qT is deprecated",
+		      type0, type1);
+	  return;
+	default:
+	  warning_at (loc, opt, "arithmetic between different enumeration "
+		      "types %qT and %qT is deprecated", type0, type1);
+	  return;
+	}
+    }
+  else if ((TREE_CODE (type0) == ENUMERAL_TYPE
+	    && TREE_CODE (type1) == REAL_TYPE)
+	   || (TREE_CODE (type0) == REAL_TYPE
+	       && TREE_CODE (type1) == ENUMERAL_TYPE))
+    {
+      const bool enum_first_p = TREE_CODE (type0) == ENUMERAL_TYPE;
+      /* In C++20, -Wdeprecated-enum-float-conversion is on by default.
+	 Otherwise, warn if -Wenum-conversion is on.  */
+      enum opt_code opt;
+      if (warn_deprecated_enum_float_conv)
+	opt = OPT_Wdeprecated_enum_float_conversion;
+      else if (warn_enum_conversion)
+	opt = OPT_Wenum_conversion;
+      else
+	return;
+
+      switch (code)
+	{
+	case GT_EXPR:
+	case LT_EXPR:
+	case GE_EXPR:
+	case LE_EXPR:
+	case EQ_EXPR:
+	case NE_EXPR:
+	  if (enum_first_p)
+	    warning_at (loc, opt, "comparison of enumeration type %qT with "
+			"floating-point type %qT is deprecated",
+			type0, type1);
+	  else
+	    warning_at (loc, opt, "comparison of floating-point type %qT "
+			"with enumeration type %qT is deprecated",
+			type0, type1);
+	  return;
+	default:
+	  if (enum_first_p)
+	    warning_at (loc, opt, "arithmetic between enumeration type %qT "
+			"and floating-point type %qT is deprecated",
+			type0, type1);
+	  else
+	    warning_at (loc, opt, "arithmetic between floating-point type %qT "
+			"and enumeration type %qT is deprecated",
+			type0, type1);
+	  return;
+	}
+    }
+}
+
 /* Build a binary-operation expression without default conversions.
    CODE is the kind of expression to build.
    LOCATION is the location_t of the operator in the source code.
@@ -5445,11 +5543,15 @@  cp_build_binary_op (const op_location_t &location,
     {
       result_type = cp_common_type (type0, type1);
       if (complain & tf_warning)
-	do_warn_double_promotion (result_type, type0, type1,
-				  "implicit conversion from %qH to %qI "
-				  "to match other operand of binary "
-				  "expression",
-				  location);
+	{
+	  do_warn_double_promotion (result_type, type0, type1,
+				    "implicit conversion from %qH to %qI "
+				    "to match other operand of binary "
+				    "expression",
+				    location);
+	  do_warn_enum_conversions (location, code, TREE_TYPE (orig_op0),
+				    TREE_TYPE (orig_op1));
+	}
     }
 
   if (code == SPACESHIP_EXPR)
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index f82eeea097a..72ae4a23203 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -239,6 +239,7 @@  in the following sections.
 -Wno-conversion-null  -Wctad-maybe-unsupported @gol
 -Wctor-dtor-privacy  -Wno-delete-incomplete @gol
 -Wdelete-non-virtual-dtor  -Wdeprecated-copy  -Wdeprecated-copy-dtor @gol
+-Wno-deprecated-enum-enum-conversion -Wno-deprecated-enum-float-conversion @gol
 -Weffc++  -Wextra-semi  -Wno-inaccessible-base @gol
 -Wno-inherited-variadic-ctor  -Wno-init-list-lifetime @gol
 -Wno-invalid-offsetof  -Wno-literal-suffix  -Wmismatched-tags @gol
@@ -3358,6 +3359,42 @@  warning is enabled by @option{-Wextra}.  With
 @option{-Wdeprecated-copy-dtor}, also deprecate if the class has a
 user-provided destructor.
 
+@item -Wno-deprecated-enum-enum-conversion @r{(C++ and Objective-C++ only)}
+@opindex Wdeprecated-enum-enum-conversion
+@opindex Wno-deprecated-enum-enum-conversion
+Disable the warning about the case when the usual arithmetic conversions
+are applied on operands where one is of enumeration type and the other is
+of a different enumeration type.  This conversion was deprecated in C++20.
+For example:
+
+@smallexample
+enum E1 @{ e @};
+enum E2 @{ f @};
+int k = f - e;
+@end smallexample
+
+@option{-Wdeprecated-enum-enum-conversion} is enabled by default with
+@option{-std=c++20}.  In pre-C++20 dialects, this warning can be enabled
+by @option{-Wenum-conversion}.
+
+@item -Wno-deprecated-enum-float-conversion @r{(C++ and Objective-C++ only)}
+@opindex Wdeprecated-enum-float-conversion
+@opindex Wno-deprecated-enum-float-conversion
+Disable the warning about the case when the usual arithmetic conversions
+are applied on operands where one is of enumeration type and the other is
+of a floating-point type.  This conversion was deprecated in C++20.  For
+example:
+
+@smallexample
+enum E1 @{ e @};
+enum E2 @{ f @};
+bool b = e <= 3.7;
+@end smallexample
+
+@option{-Wdeprecated-enum-float-conversion} is enabled by default with
+@option{-std=c++20}.  In pre-C++20 dialects, this warning can be enabled
+by @option{-Wenum-conversion}.
+
 @item -Wno-init-list-lifetime @r{(C++ and Objective-C++ only)}
 @opindex Winit-list-lifetime
 @opindex Wno-init-list-lifetime
@@ -5271,7 +5308,6 @@  Options} and @ref{Objective-C and Objective-C++ Dialect Options}.
 -Wcomment  @gol
 -Wduplicate-decl-specifier @r{(C and Objective-C only)} @gol
 -Wenum-compare @r{(in C/ObjC; this is on by default in C++)} @gol
--Wenum-conversion @r{in C/ObjC;} @gol
 -Wformat   @gol
 -Wformat-overflow  @gol
 -Wformat-truncation  @gol
@@ -5340,6 +5376,7 @@  name is still supported, but the newer name is more descriptive.)
 -Wcast-function-type  @gol
 -Wdeprecated-copy @r{(C++ only)} @gol
 -Wempty-body  @gol
+-Wenum-conversion @r{(C only)} @gol
 -Wignored-qualifiers @gol
 -Wimplicit-fallthrough=3 @gol
 -Wmissing-field-initializers  @gol
@@ -8006,11 +8043,12 @@  In C++ enumerated type mismatches in conditional expressions are also
 diagnosed and the warning is enabled by default.  In C this warning is 
 enabled by @option{-Wall}.
 
-@item -Wenum-conversion @r{(C, Objective-C only)}
+@item -Wenum-conversion
 @opindex Wenum-conversion
 @opindex Wno-enum-conversion
 Warn when a value of enumerated type is implicitly converted to a 
-different enumerated type.  This warning is enabled by @option{-Wextra}.
+different enumerated type.  This warning is enabled by @option{-Wextra}
+in C@.
 
 @item -Wjump-misses-init @r{(C, Objective-C only)}
 @opindex Wjump-misses-init
diff --git a/gcc/testsuite/g++.dg/cpp0x/linkage2.C b/gcc/testsuite/g++.dg/cpp0x/linkage2.C
index 52858687ed3..549bd825aab 100644
--- a/gcc/testsuite/g++.dg/cpp0x/linkage2.C
+++ b/gcc/testsuite/g++.dg/cpp0x/linkage2.C
@@ -29,5 +29,5 @@  void f() {
   ba.g(a);              // OK
   ba.h(a);              // error, B<T>::h never defined
   i(ba, a);             // OK
-  e1+e2+e3;
+  e1+e2+e3; // { dg-warning "arithmetic between different enumeration types" "" { target c++20 } }
 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/enum-conv1.C b/gcc/testsuite/g++.dg/cpp2a/enum-conv1.C
new file mode 100644
index 00000000000..d4960f334dd
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/enum-conv1.C
@@ -0,0 +1,120 @@ 
+// PR c++/97573
+// { dg-do compile }
+// No special options.  In C++20 (only), we should get the deprecated warnings
+// by default.  -Wenum-compare is enabled by default so some of them will be
+// printed even pre-C++20.
+
+enum E1 { e } e1;
+enum E2 { f } e2;
+__extension__ static enum { } u1;
+__extension__ static enum { } u2;
+static double d;
+
+void
+conv ()
+{
+  bool b1 = e == e1;
+  bool b2 = e == f; // { dg-warning "comparison between .enum E1. and .enum E2." }
+  bool b3 = e == 0.0; // { dg-warning "comparison of enumeration type .E1. with floating-point type .double." "" { target c++20 } }
+  bool b4 = 0.0 == f; // { dg-warning "comparison of floating-point type .double. with enumeration type .E2." "" { target c++20 } }
+  int n1 = true ? e : f; // { dg-warning "enumerated mismatch" }
+  int n2 = true ? e : 0.0; // { dg-warning "conditional expression between" "" { target c++20 } }
+}
+
+int
+enum_enum (bool b)
+{
+  int r = 0;
+  const E1 e1c = e;
+
+  r += e - e;
+  r += e - e1;
+  r += e - f; // { dg-warning "arithmetic between different enumeration types .E1. and .E2." "" { target c++20 } }
+  r += f - e; // { dg-warning "arithmetic between different enumeration types .E2. and .E1." "" { target c++20 } }
+
+  r += f + f;
+  r += f + e; // { dg-warning "arithmetic between different enumeration types .E2. and .E1." "" { target c++20 } }
+  r += e + f; // { dg-warning "arithmetic between different enumeration types .E1. and .E2." "" { target c++20 } }
+
+  r += e1 - e2; // { dg-warning "arithmetic between different enumeration types .E1. and .E2." "" { target c++20 } }
+  r += e1 - e1c;
+  r += e1c - e1;
+
+  r += e * f; // { dg-warning "arithmetic between different enumeration types .E1. and .E2." "" { target c++20 } }
+  r += f * e; // { dg-warning "arithmetic between different enumeration types .E2. and .E1." "" { target c++20 } }
+  r += e * e;
+
+  r += e1 < e1c;
+  r += e < e1;
+  r += e1 < e2; // { dg-warning "comparison between .enum E1. and .enum E2." }
+  r += e < f; // { dg-warning "comparison between .enum E1. and .enum E2." }
+  r += f < e; // { dg-warning "comparison between .enum E2. and .enum E1." }
+
+  r += e1 == e1c;
+  r += e == e1;
+  r += e == f; // { dg-warning "comparison between .enum E1. and .enum E2." }
+  r += f == e; // { dg-warning "comparison between .enum E2. and .enum E1." }
+  r += e1 == e2; // { dg-warning "comparison between .enum E1. and .enum E2." }
+  r += e2 == e1; // { dg-warning "comparison between .enum E2. and .enum E1." }
+
+  r += b ? e1 : e1c;
+  r += b ? e1 : e;
+  r += b ? f : e; // { dg-warning "enumerated mismatch in conditional expression: .E2. vs .E1." }
+  r += b ? e1 : e2; // { dg-warning "enumerated mismatch in conditional expression: .E1. vs .E2." }
+
+  r += e | f; // { dg-warning "bitwise operation between different enumeration types .E1. and .E2." "" { target c++20 } }
+  r += e ^ f; // { dg-warning "bitwise operation between different enumeration types .E1. and .E2." "" { target c++20 } }
+  r += e & f; // { dg-warning "bitwise operation between different enumeration types .E1. and .E2." "" { target c++20 } }
+  r += !e;
+  r += e1 | e;
+
+  r += e << f;
+  r += e >> f;
+  r += e || f;
+  r += e && f;
+  e1 = e1c;
+
+  // Anonymous enum.
+  r += u1 - u1;
+  r += u1 + u2; // { dg-warning "arithmetic between different enumeration types" "" { target c++20 } }
+  r += u1 * u2; // { dg-warning "arithmetic between different enumeration types" "" { target c++20 } }
+  r += u1 == u2; // { dg-warning "comparison between" }
+  r += u1 & u2; // { dg-warning "bitwise operation between different enumeration types" "" { target c++20 } }
+
+  return r;
+}
+
+double
+enum_float (bool b)
+{
+  double r = 0.0;
+
+  r += e1 - d; // { dg-warning "arithmetic between enumeration type .E1. and floating-point type .double." "" { target c++20 } }
+  r += d - e1; // { dg-warning "arithmetic between floating-point type .double. and enumeration type .E1." "" { target c++20 } }
+  r += e1 + d; // { dg-warning "arithmetic between enumeration type .E1. and floating-point type .double." "" { target c++20 } }
+  r += d + e1; // { dg-warning "arithmetic between floating-point type .double. and enumeration type .E1." "" { target c++20 } }
+  r += e1 * d; // { dg-warning "arithmetic between enumeration type .E1. and floating-point type .double." "" { target c++20 } }
+  r += d * e1; // { dg-warning "arithmetic between floating-point type .double. and enumeration type .E1." "" { target c++20 } }
+  r += u1 * d; // { dg-warning "arithmetic between enumeration type" "" { target c++20 } }
+  r += d * u1; // { dg-warning "arithmetic between floating-point type" "" { target c++20 } }
+
+  r += e1 < d;  // { dg-warning "comparison of enumeration type .E1. with floating-point type .double." "" { target c++20 } }
+  r += d < e1;  // { dg-warning "comparison of floating-point type .double. with enumeration type .E1." "" { target c++20 } }
+  r += d == e1; // { dg-warning "comparison of floating-point type .double. with enumeration type .E1." "" { target c++20 } }
+  r += e1 == d; // { dg-warning "comparison of enumeration type .E1. with floating-point type .double." "" { target c++20 } }
+  r += u1 == d; // { dg-warning "comparison of enumeration type" "" { target c++20 } }
+  r += d == u1; // { dg-warning "comparison of floating-point type" "" { target c++20 } }
+
+  r += b ? e1 : d; // { dg-warning "conditional expression between enumeration type .E1. and floating-point type .double." "" { target c++20 } }
+  r += b ? d : e1; // { dg-warning "conditional expression between floating-point type .double. and enumeration type .E1." "" { target c++20 } }
+  r += b ? d : u1; // { dg-warning "conditional expression between" "" { target c++20 } }
+  r += b ? u1 : d; // { dg-warning "conditional expression between" "" { target c++20 } }
+
+  // FIXME should be error
+  // e1 <=> d;
+
+  d += e1; // { dg-warning "arithmetic between floating-point type .double. and enumeration type .E1." "" { target c++20 } }
+  d = e1;
+
+  return r;
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/enum-conv2.C b/gcc/testsuite/g++.dg/cpp2a/enum-conv2.C
new file mode 100644
index 00000000000..f15827bda14
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/enum-conv2.C
@@ -0,0 +1,115 @@ 
+// PR c++/97573
+// { dg-do compile { target c++20 } }
+// { dg-options "-Wno-deprecated -Wno-enum-compare" }
+
+enum E1 { e } e1;
+enum E2 { f } e2;
+__extension__ static enum { } u1;
+__extension__ static enum { } u2;
+static double d;
+
+void
+conv ()
+{
+  bool b1 = e == e1;
+  bool b2 = e == f;
+  bool b3 = e == 0.0;
+  bool b4 = 0.0 == f;
+  int n1 = true ? e : f;
+  int n2 = true ? e : 0.0;
+}
+
+int
+enum_enum (bool b)
+{
+  int r = 0;
+  const E1 e1c = e;
+
+  r += e - e;
+  r += e - e1;
+  r += e - f;
+  r += f - e;
+
+  r += f + f;
+  r += f + e;
+  r += e + f;
+
+  r += e1 - e2;
+  r += e1 - e1c;
+  r += e1c - e1;
+
+  r += e * f;
+  r += f * e;
+  r += e * e;
+
+  r += e1 < e1c;
+  r += e < e1;
+  r += e1 < e2;
+  r += e < f;
+  r += f < e;
+
+  r += e1 == e1c;
+  r += e == e1;
+  r += e == f;
+  r += f == e;
+  r += e1 == e2;
+  r += e2 == e1;
+
+  r += b ? e1 : e1c;
+  r += b ? e1 : e;
+  r += b ? f : e;
+  r += b ? e1 : e2;
+
+  r += e | f;
+  r += e ^ f;
+  r += e & f;
+  r += !e;
+  r += e1 | e;
+
+  r += e << f;
+  r += e >> f;
+  r += e || f;
+  r += e && f;
+  e1 = e1c;
+
+  // Anonymous enum.
+  r += u1 - u1;
+  r += u1 + u2;
+  r += u1 * u2;
+  r += u1 == u2;
+  r += u1 & u2;
+
+  return r;
+}
+
+double
+enum_float (bool b)
+{
+  double r = 0.0;
+
+  r += e1 - d;
+  r += d - e1;
+  r += e1 + d;
+  r += d + e1;
+  r += e1 * d;
+  r += d * e1;
+  r += u1 * d;
+  r += d * u1;
+
+  r += e1 < d;
+  r += d < e1;
+  r += d == e1;
+  r += e1 == d;
+  r += u1 == d;
+  r += d == u1;
+
+  r += b ? e1 : d;
+  r += b ? d : e1;
+  r += b ? d : u1;
+  r += b ? u1 : d;
+
+  d += e1;
+  d = e1;
+
+  return r;
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/enum-conv3.C b/gcc/testsuite/g++.dg/cpp2a/enum-conv3.C
new file mode 100644
index 00000000000..67bdf1600d7
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/enum-conv3.C
@@ -0,0 +1,115 @@ 
+// PR c++/97573
+// { dg-do compile { target { c++17_down } } }
+// { dg-options "-Wenum-conversion" }
+
+enum E1 { e } e1;
+enum E2 { f } e2;
+__extension__ static enum { } u1;
+__extension__ static enum { } u2;
+static double d;
+
+void
+conv ()
+{
+  bool b1 = e == e1;
+  bool b2 = e == f; // { dg-warning "comparison between .enum E1. and .enum E2." }
+  bool b3 = e == 0.0; // { dg-warning "comparison of enumeration type .E1. with floating-point type .double." }
+  bool b4 = 0.0 == f; // { dg-warning "comparison of floating-point type .double. with enumeration type .E2." }
+  int n1 = true ? e : f; // { dg-warning "enumerated mismatch" }
+  int n2 = true ? e : 0.0; // { dg-warning "enumerated and non-enumerated type in conditional expression" }
+}
+
+int
+enum_enum (bool b)
+{
+  int r = 0;
+  const E1 e1c = e;
+
+  r += e - e;
+  r += e - e1;
+  r += e - f; // { dg-warning "arithmetic between different enumeration types .E1. and .E2." }
+  r += f - e; // { dg-warning "arithmetic between different enumeration types .E2. and .E1." }
+
+  r += f + f;
+  r += f + e; // { dg-warning "arithmetic between different enumeration types .E2. and .E1." }
+  r += e + f; // { dg-warning "arithmetic between different enumeration types .E1. and .E2." }
+
+  r += e1 - e2; // { dg-warning "arithmetic between different enumeration types .E1. and .E2." }
+  r += e1 - e1c;
+  r += e1c - e1;
+
+  r += e * f; // { dg-warning "arithmetic between different enumeration types .E1. and .E2." }
+  r += f * e; // { dg-warning "arithmetic between different enumeration types .E2. and .E1." }
+  r += e * e;
+
+  r += e1 < e1c;
+  r += e < e1;
+  r += e1 < e2; // { dg-warning "comparison between .enum E1. and .enum E2." }
+  r += e < f; // { dg-warning "comparison between .enum E1. and .enum E2." }
+  r += f < e; // { dg-warning "comparison between .enum E2. and .enum E1." }
+
+  r += e1 == e1c;
+  r += e == e1;
+  r += e == f; // { dg-warning "comparison between .enum E1. and .enum E2." }
+  r += f == e; // { dg-warning "comparison between .enum E2. and .enum E1." }
+  r += e1 == e2; // { dg-warning "comparison between .enum E1. and .enum E2." }
+  r += e2 == e1; // { dg-warning "comparison between .enum E2. and .enum E1." }
+
+  r += b ? e1 : e1c;
+  r += b ? e1 : e;
+  r += b ? f : e; // { dg-warning "enumerated mismatch in conditional expression: .E2. vs .E1." }
+  r += b ? e1 : e2; // { dg-warning "enumerated mismatch in conditional expression: .E1. vs .E2." }
+
+  r += e | f; // { dg-warning "bitwise operation between different enumeration types .E1. and .E2." }
+  r += e ^ f; // { dg-warning "bitwise operation between different enumeration types .E1. and .E2." }
+  r += e & f; // { dg-warning "bitwise operation between different enumeration types .E1. and .E2." }
+  r += !e;
+  r += e1 | e;
+
+  r += e << f;
+  r += e >> f;
+  r += e || f;
+  r += e && f;
+  e1 = e1c;
+
+  // Anonymous enum.
+  r += u1 - u1;
+  r += u1 + u2; // { dg-warning "arithmetic between different enumeration types" }
+  r += u1 * u2; // { dg-warning "arithmetic between different enumeration types" }
+  r += u1 == u2; // { dg-warning "comparison between" }
+  r += u1 & u2; // { dg-warning "bitwise operation between different enumeration types" }
+
+  return r;
+}
+
+double
+enum_float (bool b)
+{
+  double r = 0.0;
+
+  r += e1 - d; // { dg-warning "arithmetic between enumeration type .E1. and floating-point type .double." }
+  r += d - e1; // { dg-warning "arithmetic between floating-point type .double. and enumeration type .E1." }
+  r += e1 + d; // { dg-warning "arithmetic between enumeration type .E1. and floating-point type .double." }
+  r += d + e1; // { dg-warning "arithmetic between floating-point type .double. and enumeration type .E1." }
+  r += e1 * d; // { dg-warning "arithmetic between enumeration type .E1. and floating-point type .double." }
+  r += d * e1; // { dg-warning "arithmetic between floating-point type .double. and enumeration type .E1." }
+  r += u1 * d; // { dg-warning "arithmetic between enumeration type" }
+  r += d * u1; // { dg-warning "arithmetic between floating-point type" }
+
+  r += e1 < d;  // { dg-warning "comparison of enumeration type .E1. with floating-point type .double." }
+  r += d < e1;  // { dg-warning "comparison of floating-point type .double. with enumeration type .E1." }
+  r += d == e1; // { dg-warning "comparison of floating-point type .double. with enumeration type .E1." }
+  r += e1 == d; // { dg-warning "comparison of enumeration type .E1. with floating-point type .double." }
+  r += u1 == d; // { dg-warning "comparison of enumeration type" }
+  r += d == u1; // { dg-warning "comparison of floating-point type" }
+
+  r += b ? e1 : d; // { dg-warning "enumerated and non-enumerated type in conditional expression" }
+  r += b ? d : e1; // { dg-warning "enumerated and non-enumerated type in conditional expression" }
+  r += b ? d : u1; // { dg-warning "enumerated and non-enumerated type in conditional expression" }
+  r += b ? u1 : d; // { dg-warning "enumerated and non-enumerated type in conditional expression" }
+
+  d += e1; // { dg-warning "arithmetic between floating-point type .double. and enumeration type .E1." }
+  d = e1;
+
+  return r;
+}
diff --git a/gcc/testsuite/g++.dg/parse/attr3.C b/gcc/testsuite/g++.dg/parse/attr3.C
index 57fa60e130e..de095988015 100644
--- a/gcc/testsuite/g++.dg/parse/attr3.C
+++ b/gcc/testsuite/g++.dg/parse/attr3.C
@@ -10,5 +10,5 @@  int main () {
     S::F y;	// { dg-warning "'F' is deprecated" }
     y = S::f;
 
-    return x + y;
+    return x + y; // { dg-warning "arithmetic between different enumeration types" "" { target c++20 } }
 }