c++, v2: Implement C++26 P2864R2 - Remove Deprecated Arithmetic Conversion on Enumerations From C++26

Message ID ZVOwnzHtcJahNKGh@tucnak
State Unresolved
Headers
Series c++, v2: Implement C++26 P2864R2 - Remove Deprecated Arithmetic Conversion on Enumerations From C++26 |

Checks

Context Check Description
snail/gcc-patch-check warning Git am fail log

Commit Message

Jakub Jelinek Nov. 14, 2023, 5:38 p.m. UTC
  On Mon, Nov 13, 2023 at 10:59:52PM -0500, Jason Merrill wrote:
> On 11/13/23 06:50, Jakub Jelinek wrote:
> > The following patch implements C++26 P2864R2 by emitting pedwarn enabled by
> > the same options as the C++20 and later warnings (i.e. -Wenum-compare,
> > -Wdeprecated-enum-enum-conversion and -Wdeprecated-enum-float-conversion
> > which are all enabled by default).  I think we still want to allow users
> > some option workaround, so am not using directly error, but if that is
> > what you want instead, I can change it.
> 
> I agree, but we also need to return error_mark_node for these cases when
> SFINAE, i.e. !(complain & tf_warning_or_error)

So like this then?

Bootstrapped/regtested on x86_64-linux and i686-linux.

2023-11-14  Jakub Jelinek  <jakub@redhat.com>

gcc/cp/
	* typeck.cc: Implement C++26 P2864R2 - Remove Deprecated Arithmetic
	Conversion on Enumerations From C++26.
	(do_warn_enum_conversions): Return bool rather than void, add COMPLAIN
	argument.  Use pedwarn rather than warning_at for C++26 and remove
	" is deprecated" part of the diagnostics in that case.  For SFINAE
	in C++26 return true on newly erroneous cases.
	(cp_build_binary_op): For C++26 call do_warn_enum_conversions
	unconditionally, pass complain argument to it and if it returns true,
	return error_mark_node.
	* call.cc (build_conditional_expr): Use pedwarn rather than warning_at
	for C++26 and remove " is deprecated" part of the diagnostics in that
	case and check for complain & tf_warning_or_error.  Use emit_diagnostic
	with cxx_dialect >= cxx26 ? DK_PEDWARN : DK_WARNING.  For SFINAE in
	C++26 return error_mark_node on newly erroneous cases.
	(build_new_op): Use emit_diagnostic with cxx_dialect >= cxx26
	? DK_PEDWARN : DK_WARNING and complain & tf_warning_or_error check
	for C++26.  For SFINAE in C++26 return error_mark_node on newly
	erroneous cases.
gcc/testsuite/
	* g++.dg/cpp26/enum-conv1.C: New test.
	* g++.dg/cpp2a/enum-conv1.C: Adjust expected diagnostics in C++26.
	* g++.dg/diagnostic/enum3.C: Likewise.
	* g++.dg/parse/attr3.C: Likewise.
	* g++.dg/cpp0x/linkage2.C: Likewise.



	Jakub
  

Comments

Jason Merrill Nov. 14, 2023, 10:01 p.m. UTC | #1
On 11/14/23 12:38, Jakub Jelinek wrote:
> On Mon, Nov 13, 2023 at 10:59:52PM -0500, Jason Merrill wrote:
>> On 11/13/23 06:50, Jakub Jelinek wrote:
>>> The following patch implements C++26 P2864R2 by emitting pedwarn enabled by
>>> the same options as the C++20 and later warnings (i.e. -Wenum-compare,
>>> -Wdeprecated-enum-enum-conversion and -Wdeprecated-enum-float-conversion
>>> which are all enabled by default).  I think we still want to allow users
>>> some option workaround, so am not using directly error, but if that is
>>> what you want instead, I can change it.
>>
>> I agree, but we also need to return error_mark_node for these cases when
>> SFINAE, i.e. !(complain & tf_warning_or_error)
> 
> So like this then?
> 
> Bootstrapped/regtested on x86_64-linux and i686-linux.
> 
> 2023-11-14  Jakub Jelinek  <jakub@redhat.com>
> 
> gcc/cp/
> 	* typeck.cc: Implement C++26 P2864R2 - Remove Deprecated Arithmetic
> 	Conversion on Enumerations From C++26.
> 	(do_warn_enum_conversions): Return bool rather than void, add COMPLAIN
> 	argument.  Use pedwarn rather than warning_at for C++26 and remove
> 	" is deprecated" part of the diagnostics in that case.  For SFINAE
> 	in C++26 return true on newly erroneous cases.
> 	(cp_build_binary_op): For C++26 call do_warn_enum_conversions
> 	unconditionally, pass complain argument to it and if it returns true,
> 	return error_mark_node.
> 	* call.cc (build_conditional_expr): Use pedwarn rather than warning_at
> 	for C++26 and remove " is deprecated" part of the diagnostics in that
> 	case and check for complain & tf_warning_or_error.  Use emit_diagnostic
> 	with cxx_dialect >= cxx26 ? DK_PEDWARN : DK_WARNING.  For SFINAE in
> 	C++26 return error_mark_node on newly erroneous cases.
> 	(build_new_op): Use emit_diagnostic with cxx_dialect >= cxx26
> 	? DK_PEDWARN : DK_WARNING and complain & tf_warning_or_error check
> 	for C++26.  For SFINAE in C++26 return error_mark_node on newly
> 	erroneous cases.
> gcc/testsuite/
> 	* g++.dg/cpp26/enum-conv1.C: New test.
> 	* g++.dg/cpp2a/enum-conv1.C: Adjust expected diagnostics in C++26.
> 	* g++.dg/diagnostic/enum3.C: Likewise.
> 	* g++.dg/parse/attr3.C: Likewise.
> 	* g++.dg/cpp0x/linkage2.C: Likewise.
> 
> --- gcc/cp/typeck.cc.jj	2023-11-13 13:02:21.573785549 +0100
> +++ gcc/cp/typeck.cc	2023-11-14 09:49:31.026997048 +0100
> @@ -4940,16 +4940,25 @@ warn_for_null_address (location_t locati
>      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.
> +   For C++26 this is ill-formed rather than deprecated.
> +   Return true for SFINAE errors.
>      TODO: Consider combining this with -Wenum-compare in build_new_op_1.  */
>   
> -static void
> +static bool
>   do_warn_enum_conversions (location_t loc, enum tree_code code, tree type0,
> -			  tree type1)
> +			  tree type1, tsubst_flags_t complain)
>   {
>     if (TREE_CODE (type0) == ENUMERAL_TYPE
>         && TREE_CODE (type1) == ENUMERAL_TYPE
>         && TYPE_MAIN_VARIANT (type0) != TYPE_MAIN_VARIANT (type1))
>       {
> +      if (cxx_dialect >= cxx26)
> +	{
> +	  if ((complain & tf_warning_or_error) == 0)
> +	    return true;
> +	}
> +      else if ((complain & tf_warning) == 0)
> +	return false;
>         /* In C++20, -Wdeprecated-enum-enum-conversion is on by default.
>   	 Otherwise, warn if -Wenum-conversion is on.  */
>         enum opt_code opt;
> @@ -4958,7 +4967,7 @@ do_warn_enum_conversions (location_t loc
>         else if (warn_enum_conversion)
>   	opt = OPT_Wenum_conversion;
>         else
> -	return;
> +	return false;
>   
>         switch (code)
>   	{
> @@ -4969,21 +4978,29 @@ do_warn_enum_conversions (location_t loc
>   	case EQ_EXPR:
>   	case NE_EXPR:
>   	  /* Comparisons are handled by -Wenum-compare.  */
> -	  return;
> +	  return false;
>   	case SPACESHIP_EXPR:
>   	  /* This is invalid, don't warn.  */
> -	  return;
> +	  return false;
>   	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;
> +	  if (cxx_dialect >= cxx26)
> +	    pedwarn (loc, opt, "bitwise operation between different "
> +		     "enumeration types %qT and %qT", type0, type1);
> +	  else
> +	    warning_at (loc, opt, "bitwise operation between different "
> +			"enumeration types %qT and %qT is deprecated",
> +			type0, type1);
> +	  return false;
>   	default:
> -	  warning_at (loc, opt, "arithmetic between different enumeration "
> -		      "types %qT and %qT is deprecated", type0, type1);
> -	  return;
> +	  if (cxx_dialect >= cxx26)
> +	    pedwarn (loc, opt, "arithmetic between different enumeration "
> +		     "types %qT and %qT", type0, type1);
> +	  else
> +	    warning_at (loc, opt, "arithmetic between different enumeration "
> +			"types %qT and %qT is deprecated", type0, type1);
> +	  return false;
>   	}
>       }
>     else if ((TREE_CODE (type0) == ENUMERAL_TYPE
> @@ -4991,6 +5008,13 @@ do_warn_enum_conversions (location_t loc
>   	   || (SCALAR_FLOAT_TYPE_P (type0)
>   	       && TREE_CODE (type1) == ENUMERAL_TYPE))
>       {
> +      if (cxx_dialect >= cxx26)
> +	{
> +	  if ((complain & tf_warning_or_error) == 0)
> +	    return true;
> +	}
> +      else if ((complain & tf_warning) == 0)
> +	return false;
>         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.  */
> @@ -5000,7 +5024,7 @@ do_warn_enum_conversions (location_t loc
>         else if (warn_enum_conversion)
>   	opt = OPT_Wenum_conversion;
>         else
> -	return;
> +	return false;
>   
>         switch (code)
>   	{
> @@ -5010,7 +5034,13 @@ do_warn_enum_conversions (location_t loc
>   	case LE_EXPR:
>   	case EQ_EXPR:
>   	case NE_EXPR:
> -	  if (enum_first_p)
> +	  if (enum_first_p && cxx_dialect >= cxx26)
> +	    pedwarn (loc, opt, "comparison of enumeration type %qT with "
> +		     "floating-point type %qT", type0, type1);
> +	  else if (cxx_dialect >= cxx26)
> +	    pedwarn (loc, opt, "comparison of floating-point type %qT "
> +		      "with enumeration type %qT", type0, type1);
> +	  else if (enum_first_p)
>   	    warning_at (loc, opt, "comparison of enumeration type %qT with "
>   			"floating-point type %qT is deprecated",
>   			type0, type1);
> @@ -5018,12 +5048,18 @@ do_warn_enum_conversions (location_t loc
>   	    warning_at (loc, opt, "comparison of floating-point type %qT "
>   			"with enumeration type %qT is deprecated",
>   			type0, type1);
> -	  return;
> +	  return false;
>   	case SPACESHIP_EXPR:
>   	  /* This is invalid, don't warn.  */
> -	  return;
> +	  return false;
>   	default:
> -	  if (enum_first_p)
> +	  if (enum_first_p && cxx_dialect >= cxx26)
> +	    pedwarn (loc, opt, "arithmetic between enumeration type %qT "
> +		     "and floating-point type %qT", type0, type1);
> +	  else if (cxx_dialect >= cxx26)
> +	    pedwarn (loc, opt, "arithmetic between floating-point type %qT "
> +		     "and enumeration type %qT", type0, type1);
> +	  else if (enum_first_p)
>   	    warning_at (loc, opt, "arithmetic between enumeration type %qT "
>   			"and floating-point type %qT is deprecated",
>   			type0, type1);
> @@ -5031,9 +5067,10 @@ do_warn_enum_conversions (location_t loc
>   	    warning_at (loc, opt, "arithmetic between floating-point type %qT "
>   			"and enumeration type %qT is deprecated",
>   			type0, type1);
> -	  return;
> +	  return true;

Shouldn't this be return false?

OK with that change.

Jason
  

Patch

--- gcc/cp/typeck.cc.jj	2023-11-13 13:02:21.573785549 +0100
+++ gcc/cp/typeck.cc	2023-11-14 09:49:31.026997048 +0100
@@ -4940,16 +4940,25 @@  warn_for_null_address (location_t locati
    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.
+   For C++26 this is ill-formed rather than deprecated.
+   Return true for SFINAE errors.
    TODO: Consider combining this with -Wenum-compare in build_new_op_1.  */
 
-static void
+static bool
 do_warn_enum_conversions (location_t loc, enum tree_code code, tree type0,
-			  tree type1)
+			  tree type1, tsubst_flags_t complain)
 {
   if (TREE_CODE (type0) == ENUMERAL_TYPE
       && TREE_CODE (type1) == ENUMERAL_TYPE
       && TYPE_MAIN_VARIANT (type0) != TYPE_MAIN_VARIANT (type1))
     {
+      if (cxx_dialect >= cxx26)
+	{
+	  if ((complain & tf_warning_or_error) == 0)
+	    return true;
+	}
+      else if ((complain & tf_warning) == 0)
+	return false;
       /* In C++20, -Wdeprecated-enum-enum-conversion is on by default.
 	 Otherwise, warn if -Wenum-conversion is on.  */
       enum opt_code opt;
@@ -4958,7 +4967,7 @@  do_warn_enum_conversions (location_t loc
       else if (warn_enum_conversion)
 	opt = OPT_Wenum_conversion;
       else
-	return;
+	return false;
 
       switch (code)
 	{
@@ -4969,21 +4978,29 @@  do_warn_enum_conversions (location_t loc
 	case EQ_EXPR:
 	case NE_EXPR:
 	  /* Comparisons are handled by -Wenum-compare.  */
-	  return;
+	  return false;
 	case SPACESHIP_EXPR:
 	  /* This is invalid, don't warn.  */
-	  return;
+	  return false;
 	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;
+	  if (cxx_dialect >= cxx26)
+	    pedwarn (loc, opt, "bitwise operation between different "
+		     "enumeration types %qT and %qT", type0, type1);
+	  else
+	    warning_at (loc, opt, "bitwise operation between different "
+			"enumeration types %qT and %qT is deprecated",
+			type0, type1);
+	  return false;
 	default:
-	  warning_at (loc, opt, "arithmetic between different enumeration "
-		      "types %qT and %qT is deprecated", type0, type1);
-	  return;
+	  if (cxx_dialect >= cxx26)
+	    pedwarn (loc, opt, "arithmetic between different enumeration "
+		     "types %qT and %qT", type0, type1);
+	  else
+	    warning_at (loc, opt, "arithmetic between different enumeration "
+			"types %qT and %qT is deprecated", type0, type1);
+	  return false;
 	}
     }
   else if ((TREE_CODE (type0) == ENUMERAL_TYPE
@@ -4991,6 +5008,13 @@  do_warn_enum_conversions (location_t loc
 	   || (SCALAR_FLOAT_TYPE_P (type0)
 	       && TREE_CODE (type1) == ENUMERAL_TYPE))
     {
+      if (cxx_dialect >= cxx26)
+	{
+	  if ((complain & tf_warning_or_error) == 0)
+	    return true;
+	}
+      else if ((complain & tf_warning) == 0)
+	return false;
       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.  */
@@ -5000,7 +5024,7 @@  do_warn_enum_conversions (location_t loc
       else if (warn_enum_conversion)
 	opt = OPT_Wenum_conversion;
       else
-	return;
+	return false;
 
       switch (code)
 	{
@@ -5010,7 +5034,13 @@  do_warn_enum_conversions (location_t loc
 	case LE_EXPR:
 	case EQ_EXPR:
 	case NE_EXPR:
-	  if (enum_first_p)
+	  if (enum_first_p && cxx_dialect >= cxx26)
+	    pedwarn (loc, opt, "comparison of enumeration type %qT with "
+		     "floating-point type %qT", type0, type1);
+	  else if (cxx_dialect >= cxx26)
+	    pedwarn (loc, opt, "comparison of floating-point type %qT "
+		      "with enumeration type %qT", type0, type1);
+	  else if (enum_first_p)
 	    warning_at (loc, opt, "comparison of enumeration type %qT with "
 			"floating-point type %qT is deprecated",
 			type0, type1);
@@ -5018,12 +5048,18 @@  do_warn_enum_conversions (location_t loc
 	    warning_at (loc, opt, "comparison of floating-point type %qT "
 			"with enumeration type %qT is deprecated",
 			type0, type1);
-	  return;
+	  return false;
 	case SPACESHIP_EXPR:
 	  /* This is invalid, don't warn.  */
-	  return;
+	  return false;
 	default:
-	  if (enum_first_p)
+	  if (enum_first_p && cxx_dialect >= cxx26)
+	    pedwarn (loc, opt, "arithmetic between enumeration type %qT "
+		     "and floating-point type %qT", type0, type1);
+	  else if (cxx_dialect >= cxx26)
+	    pedwarn (loc, opt, "arithmetic between floating-point type %qT "
+		     "and enumeration type %qT", type0, type1);
+	  else if (enum_first_p)
 	    warning_at (loc, opt, "arithmetic between enumeration type %qT "
 			"and floating-point type %qT is deprecated",
 			type0, type1);
@@ -5031,9 +5067,10 @@  do_warn_enum_conversions (location_t loc
 	    warning_at (loc, opt, "arithmetic between floating-point type %qT "
 			"and enumeration type %qT is deprecated",
 			type0, type1);
-	  return;
+	  return true;
 	}
     }
+  return false;
 }
 
 /* Build a binary-operation expression without default conversions.
@@ -6163,15 +6200,13 @@  cp_build_binary_op (const op_location_t
 	  return error_mark_node;
 	}
       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_enum_conversions (location, code, TREE_TYPE (orig_op0),
-				    TREE_TYPE (orig_op1));
-	}
+	do_warn_double_promotion (result_type, type0, type1,
+				  "implicit conversion from %qH to %qI "
+				  "to match other operand of binary "
+				  "expression", location);
+      if (do_warn_enum_conversions (location, code, TREE_TYPE (orig_op0),
+				    TREE_TYPE (orig_op1), complain))
+	return error_mark_node;
     }
   if (may_need_excess_precision
       && (orig_type0 != type0 || orig_type1 != type1)
--- gcc/cp/call.cc.jj	2023-11-13 13:02:21.545785943 +0100
+++ gcc/cp/call.cc	2023-11-14 09:59:27.336644386 +0100
@@ -6163,19 +6163,36 @@  build_conditional_expr (const op_locatio
 		  == DECL_CONTEXT (stripped_orig_arg3)))
 	    /* Two enumerators from the same enumeration can have different
 	       types when the enumeration is still being defined.  */;
-          else if (complain & tf_warning)
-	    warning_at (loc, OPT_Wenum_compare, "enumerated mismatch "
-			"in conditional expression: %qT vs %qT",
-			arg2_type, arg3_type);
+	  else if (complain & (cxx_dialect >= cxx26
+			       ? tf_warning_or_error : tf_warning))
+	    emit_diagnostic (cxx_dialect >= cxx26 ? DK_PEDWARN : DK_WARNING,
+			     loc, OPT_Wenum_compare, "enumerated mismatch "
+			     "in conditional expression: %qT vs %qT",
+			     arg2_type, arg3_type);
+	  else if (cxx_dialect >= cxx26)
+	    return error_mark_node;
         }
-      else if ((complain & tf_warning)
-	       && warn_deprecated_enum_float_conv
+      else if ((((complain & (cxx_dialect >= cxx26
+			      ? tf_warning_or_error : tf_warning))
+		 && warn_deprecated_enum_float_conv)
+		|| (cxx_dialect >= cxx26
+		    && (complain & tf_warning_or_error) == 0))
 	       && ((TREE_CODE (arg2_type) == ENUMERAL_TYPE
 		    && SCALAR_FLOAT_TYPE_P (arg3_type))
 		   || (SCALAR_FLOAT_TYPE_P (arg2_type)
 		       && TREE_CODE (arg3_type) == ENUMERAL_TYPE)))
 	{
-	  if (TREE_CODE (arg2_type) == ENUMERAL_TYPE)
+	  if (cxx_dialect >= cxx26 && (complain & tf_warning_or_error) == 0)
+	    return error_mark_node;
+	  if (cxx_dialect >= cxx26 && TREE_CODE (arg2_type) == ENUMERAL_TYPE)
+	    pedwarn (loc, OPT_Wdeprecated_enum_float_conversion,
+		     "conditional expression between enumeration type "
+		     "%qT and floating-point type %qT", arg2_type, arg3_type);
+	  else if (cxx_dialect >= cxx26)
+	    pedwarn (loc, OPT_Wdeprecated_enum_float_conversion,
+		     "conditional expression between floating-point type "
+		     "%qT and enumeration type %qT", arg2_type, arg3_type);
+	  else 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",
@@ -7258,11 +7275,18 @@  build_new_op (const op_location_t &loc,
 	      if (TREE_CODE (arg1_type) == ENUMERAL_TYPE
 		  && TREE_CODE (arg2_type) == ENUMERAL_TYPE
 		  && (TYPE_MAIN_VARIANT (arg1_type)
-		      != TYPE_MAIN_VARIANT (arg2_type))
-		  && (complain & tf_warning))
-		warning_at (loc, OPT_Wenum_compare,
-			    "comparison between %q#T and %q#T",
-			    arg1_type, arg2_type);
+		      != TYPE_MAIN_VARIANT (arg2_type)))
+		{
+		  if (cxx_dialect >= cxx26
+		      && (complain & tf_warning_or_error) == 0)
+		    result = error_mark_node;
+		  else if (cxx_dialect >= cxx26 || (complain & tf_warning))
+		    emit_diagnostic (cxx_dialect >= cxx26
+				     ? DK_PEDWARN : DK_WARNING,
+				     loc, OPT_Wenum_compare,
+				     "comparison between %q#T and %q#T",
+				     arg1_type, arg2_type);
+		}
 	      break;
 	    default:
 	      break;
--- gcc/testsuite/g++.dg/diagnostic/enum3.C.jj	2023-11-13 13:02:21.617784930 +0100
+++ gcc/testsuite/g++.dg/diagnostic/enum3.C	2023-11-14 09:39:10.021090313 +0100
@@ -5,5 +5,6 @@  enum e1 { e1val };
 enum e2 { e3val };
 
 int main( int, char * [] ) {
-   if ( e1val == e3val ) return 1; // { dg-warning -Wenum-compare }
+   if ( e1val == e3val ) return 1; // { dg-warning "comparison between 'enum e1' and 'enum e2'" "" { target c++23_down } }
+				   // { dg-error "comparison between 'enum e1' and 'enum e2'" "" { target c++26 } .-1 }
 }
--- gcc/testsuite/g++.dg/cpp26/enum-conv1.C.jj	2023-11-14 10:08:14.505260200 +0100
+++ gcc/testsuite/g++.dg/cpp26/enum-conv1.C	2023-11-14 10:17:07.964758706 +0100
@@ -0,0 +1,39 @@ 
+// P2864R2 - Remove Deprecated Arithmetic Conversion on Enumerations From C++26
+// { dg-do compile { target c++20 } }
+
+enum A { a };
+enum B { b };
+
+template <auto X, auto Y> decltype (true ? X : Y) f1 () { throw 1; }
+// { dg-error "enumerated mismatch in conditional expression: 'A' vs 'B'" "" { target c++26 } .-1 }
+// { dg-error "conditional expression between enumeration type 'A' and floating-point type 'double'" "" { target c++26 } .-2 }
+// { dg-error "conditional expression between floating-point type 'double' and enumeration type 'A'" "" { target c++26 } .-3 }
+template <auto X, auto Y> decltype (X + Y) f2 () { throw 1; }
+// { dg-error "arithmetic between different enumeration types 'A' and 'B'" "" { target c++26 } .-1 }
+// { dg-error "arithmetic between enumeration type 'A' and floating-point type 'double'" "" { target c++26 } .-2 }
+// { dg-error "arithmetic between floating-point type 'double' and enumeration type 'A'" "" { target c++26 } .-3 }
+template <auto X, auto Y> decltype (X | Y) f3 () { throw 1; }
+// { dg-error "bitwise operation between different enumeration types 'A' and 'B'" "" { target c++26 } .-1 }
+template <auto X, auto Y> decltype (X < Y) f4 () { throw 1; }
+// { dg-error "comparison between 'enum A' and 'enum B'" "" { target c++26 } .-1 }
+// { dg-error "comparison of enumeration type 'A' with floating-point type 'double'" "" { target c++26 } .-2 }
+// { dg-error "comparison of floating-point type 'double' with enumeration type 'A'" "" { target c++26 } .-3 }
+
+int
+main ()
+{
+  f1<a, a> ();
+  f2<a, a> ();
+  f3<b, b> ();
+  f4<b, b> ();
+  f1<a, b> ();		// { dg-error "no matching function for call to" "" { target c++26 } }
+  f2<a, b> ();		// { dg-error "no matching function for call to" "" { target c++26 } }
+  f3<a, b> ();		// { dg-error "no matching function for call to" "" { target c++26 } }
+  f4<a, b> ();		// { dg-error "no matching function for call to" "" { target c++26 } }
+  f1<a, 0.0> ();	// { dg-error "no matching function for call to" "" { target c++26 } }
+  f2<a, 0.0> ();	// { dg-error "no matching function for call to" "" { target c++26 } }
+  f4<a, 0.0> ();	// { dg-error "no matching function for call to" "" { target c++26 } }
+  f1<0.0, a> ();	// { dg-error "no matching function for call to" "" { target c++26 } }
+  f2<0.0, a> ();	// { dg-error "no matching function for call to" "" { target c++26 } }
+  f4<0.0, a> ();	// { dg-error "no matching function for call to" "" { target c++26 } }
+}
--- gcc/testsuite/g++.dg/parse/attr3.C.jj	2023-11-13 13:02:21.627784789 +0100
+++ gcc/testsuite/g++.dg/parse/attr3.C	2023-11-14 09:39:10.030090178 +0100
@@ -10,5 +10,6 @@  int main () {
     S::F y;	// { dg-warning "'F' is deprecated" }
     y = S::f;
 
-    return x + y; // { dg-warning "arithmetic between different enumeration types" "" { target c++20 } }
+    return x + y; // { dg-warning "arithmetic between different enumeration types" "" { target { c++20 && c++23_down } } }
+		  // { dg-error "arithmetic between different enumeration types" "" { target c++26 } .-1 }
 }
--- gcc/testsuite/g++.dg/cpp2a/enum-conv1.C.jj	2023-11-13 13:02:21.614784972 +0100
+++ gcc/testsuite/g++.dg/cpp2a/enum-conv1.C	2023-11-14 09:39:10.001090611 +0100
@@ -14,11 +14,16 @@  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 } }
+  bool b2 = e == f; // { dg-warning "comparison between .enum E1. and .enum E2." "" { target c++23_down } }
+		    // { dg-error "comparison between .enum E1. and .enum E2." "" { target c++26 } .-1 }
+  bool b3 = e == 0.0; // { dg-warning "comparison of enumeration type .E1. with floating-point type .double." "" { target { c++20 && c++23_down } } }
+		      // { dg-error "comparison of enumeration type .E1. with floating-point type .double." "" { target c++26 } .-1 }
+  bool b4 = 0.0 == f; // { dg-warning "comparison of floating-point type .double. with enumeration type .E2." "" { target { c++20 && c++23_down } } }
+		      // { dg-error "comparison of floating-point type .double. with enumeration type .E2." "" { target c++26 } .-1 }
+  int n1 = true ? e : f; // { dg-warning "enumerated mismatch" "" { target c++23_down } }
+			 // { dg-error "enumerated mismatch" "" { target c++26 } .-1 }
+  int n2 = true ? e : 0.0; // { dg-warning "conditional expression between" "" { target { c++20 && c++23_down } } }
+			   // { dg-error "conditional expression between" "" { target c++26 } .-1 }
 }
 
 int
@@ -29,42 +34,60 @@  enum_enum (bool b)
 
   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 += e - f; // { dg-warning "arithmetic between different enumeration types .E1. and .E2." "" { target { c++20 && c++23_down } } }
+	      // { dg-error "arithmetic between different enumeration types .E1. and .E2." "" { target c++26 } .-1 }
+  r += f - e; // { dg-warning "arithmetic between different enumeration types .E2. and .E1." "" { target { c++20 && c++23_down } } }
+	      // { dg-error "arithmetic between different enumeration types .E2. and .E1." "" { target c++26 } .-1 }
   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 += f + e; // { dg-warning "arithmetic between different enumeration types .E2. and .E1." "" { target { c++20 && c++23_down } } }
+	      // { dg-error "arithmetic between different enumeration types .E2. and .E1." "" { target c++26 } .-1 }
+  r += e + f; // { dg-warning "arithmetic between different enumeration types .E1. and .E2." "" { target { c++20 && c++23_down } } }
+	      // { dg-error "arithmetic between different enumeration types .E1. and .E2." "" { target c++26 } .-1 }
 
-  r += e1 - e2; // { 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 && c++23_down } } }
+	      // { dg-error "arithmetic between different enumeration types .E1. and .E2." "" { target c++26 } .-1 }
   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 * f; // { dg-warning "arithmetic between different enumeration types .E1. and .E2." "" { target { c++20 && c++23_down } } }
+	      // { dg-error "arithmetic between different enumeration types .E1. and .E2." "" { target c++26 } .-1 }
+  r += f * e; // { dg-warning "arithmetic between different enumeration types .E2. and .E1." "" { target { c++20 && c++23_down } } }
+	      // { dg-error "arithmetic between different enumeration types .E2. and .E1." "" { target c++26 } .-1 }
   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 < e2; // { dg-warning "comparison between .enum E1. and .enum E2." "" { target c++23_down } }
+		// { dg-error "comparison between .enum E1. and .enum E2." "" { target c++26 } .-1 }
+  r += e < f; // { dg-warning "comparison between .enum E1. and .enum E2." "" { target c++23_down } }
+	      // { dg-error "comparison between .enum E1. and .enum E2." "" { target c++26 } .-1 }
+  r += f < e; // { dg-warning "comparison between .enum E2. and .enum E1." "" { target c++23_down } }
+	      // { dg-error "comparison between .enum E2. and .enum E1." "" { target c++26 } .-1 }
 
   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 += e == f; // { dg-warning "comparison between .enum E1. and .enum E2." "" { target c++23_down } }
+	       // { dg-error "comparison between .enum E1. and .enum E2." "" { target c++26 } .-1 }
+  r += f == e; // { dg-warning "comparison between .enum E2. and .enum E1." "" { target c++23_down } }
+	       // { dg-error "comparison between .enum E2. and .enum E1." "" { target c++26 } .-1 }
+  r += e1 == e2; // { dg-warning "comparison between .enum E1. and .enum E2." "" { target c++23_down } }
+		 // { dg-error "comparison between .enum E1. and .enum E2." "" { target c++26 } .-1 }
+  r += e2 == e1; // { dg-warning "comparison between .enum E2. and .enum E1." "" { target c++23_down } }
+		 // { dg-error "comparison between .enum E2. and .enum E1." "" { target c++26 } .-1 }
 
   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 += b ? f : e; // { dg-warning "enumerated mismatch in conditional expression: .E2. vs .E1." "" { target c++23_down } }
+		  // { dg-error "enumerated mismatch in conditional expression: .E2. vs .E1." "" { target c++26 } .-1 }
+  r += b ? e1 : e2; // { dg-warning "enumerated mismatch in conditional expression: .E1. vs .E2." "" { target c++23_down } }
+		    // { dg-error "enumerated mismatch in conditional expression: .E1. vs .E2." "" { target c++26 } .-1 }
+
+  r += e | f; // { dg-warning "bitwise operation between different enumeration types .E1. and .E2." "" { target { c++20 && c++23_down } } }
+	      // { dg-error "bitwise operation between different enumeration types .E1. and .E2." "" { target c++26 } .-1 }
+  r += e ^ f; // { dg-warning "bitwise operation between different enumeration types .E1. and .E2." "" { target { c++20 && c++23_down } } }
+	      // { dg-error "bitwise operation between different enumeration types .E1. and .E2." "" { target c++26 } .-1 }
+  r += e & f; // { dg-warning "bitwise operation between different enumeration types .E1. and .E2." "" { target { c++20 && c++23_down } } }
+	      // { dg-error "bitwise operation between different enumeration types .E1. and .E2." "" { target c++26 } .-1 }
   r += !e;
   r += e1 | e;
 
@@ -76,10 +99,14 @@  enum_enum (bool b)
 
   // 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 } }
+  r += u1 + u2; // { dg-warning "arithmetic between different enumeration types" "" { target { c++20 && c++23_down } } }
+		// { dg-error "arithmetic between different enumeration types" "" { target c++26 } .-1 }
+  r += u1 * u2; // { dg-warning "arithmetic between different enumeration types" "" { target { c++20 && c++23_down } } }
+		// { dg-error "arithmetic between different enumeration types" "" { target c++26 } .-1 }
+  r += u1 == u2; // { dg-warning "comparison between" "" { target c++23_down } }
+		 // { dg-error "comparison between" "" { target c++26 } .-1 }
+  r += u1 & u2; // { dg-warning "bitwise operation between different enumeration types" "" { target { c++20 && c++23_down } } }
+		// { dg-error "bitwise operation between different enumeration types" "" { target c++26 } .-1 }
 
   return r;
 }
@@ -89,28 +116,47 @@  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 } }
+  r += e1 - d; // { dg-warning "arithmetic between enumeration type .E1. and floating-point type .double." "" { target { c++20 && c++23_down } } }
+	       // { dg-error "arithmetic between enumeration type .E1. and floating-point type .double." "" { target c++26 } .-1 }
+  r += d - e1; // { dg-warning "arithmetic between floating-point type .double. and enumeration type .E1." "" { target { c++20 && c++23_down } } }
+	       // { dg-error "arithmetic between floating-point type .double. and enumeration type .E1." "" { target c++26 } .-1 }
+  r += e1 + d; // { dg-warning "arithmetic between enumeration type .E1. and floating-point type .double." "" { target { c++20 && c++23_down } } }
+	       // { dg-error "arithmetic between enumeration type .E1. and floating-point type .double." "" { target c++26 } .-1 }
+  r += d + e1; // { dg-warning "arithmetic between floating-point type .double. and enumeration type .E1." "" { target { c++20 && c++23_down } } }
+	       // { dg-error "arithmetic between floating-point type .double. and enumeration type .E1." "" { target c++26 } .-1 }
+  r += e1 * d; // { dg-warning "arithmetic between enumeration type .E1. and floating-point type .double." "" { target { c++20 && c++23_down } } }
+	       // { dg-error "arithmetic between enumeration type .E1. and floating-point type .double." "" { target c++26 } .-1 }
+  r += d * e1; // { dg-warning "arithmetic between floating-point type .double. and enumeration type .E1." "" { target { c++20 && c++23_down } } }
+	       // { dg-error "arithmetic between floating-point type .double. and enumeration type .E1." "" { target c++26 } .-1 }
+  r += u1 * d; // { dg-warning "arithmetic between enumeration type" "" { target { c++20 && c++23_down } } }
+	       // { dg-error "arithmetic between enumeration type" "" { target c++26 } .-1 }
+  r += d * u1; // { dg-warning "arithmetic between floating-point type" "" { target { c++20 && c++23_down } } }
+	       // { dg-error "arithmetic between floating-point type" "" { target c++26 } .-1 }
+
+  r += e1 < d;  // { dg-warning "comparison of enumeration type .E1. with floating-point type .double." "" { target { c++20 && c++23_down } } }
+		// { dg-error "comparison of enumeration type .E1. with floating-point type .double." "" { target c++26 } .-1 }
+  r += d < e1;  // { dg-warning "comparison of floating-point type .double. with enumeration type .E1." "" { target { c++20 && c++23_down } } }
+		// { dg-error "comparison of floating-point type .double. with enumeration type .E1." "" { target c++26 } .-1 }
+  r += d == e1; // { dg-warning "comparison of floating-point type .double. with enumeration type .E1." "" { target { c++20 && c++23_down } } }
+		// { dg-error "comparison of floating-point type .double. with enumeration type .E1." "" { target c++26 } .-1 }
+  r += e1 == d; // { dg-warning "comparison of enumeration type .E1. with floating-point type .double." "" { target { c++20 && c++23_down } } }
+		// { dg-error "comparison of enumeration type .E1. with floating-point type .double." "" { target c++26 } .-1 }
+  r += u1 == d; // { dg-warning "comparison of enumeration type" "" { target { c++20 && c++23_down } } }
+		// { dg-error "comparison of enumeration type" "" { target c++26 } .-1 }
+  r += d == u1; // { dg-warning "comparison of floating-point type" "" { target { c++20 && c++23_down } } }
+		// { dg-error "comparison of floating-point type" "" { target c++26 } .-1 }
+
+  r += b ? e1 : d; // { dg-warning "conditional expression between enumeration type .E1. and floating-point type .double." "" { target { c++20 && c++23_down } } }
+		   // { dg-error "conditional expression between enumeration type .E1. and floating-point type .double." "" { target c++26 } .-1 }
+  r += b ? d : e1; // { dg-warning "conditional expression between floating-point type .double. and enumeration type .E1." "" { target { c++20 && c++23_down } } }
+		   // { dg-error "conditional expression between floating-point type .double. and enumeration type .E1." "" { target c++26 } .-1 }
+  r += b ? d : u1; // { dg-warning "conditional expression between" "" { target { c++20 && c++23_down } } }
+		   // { dg-error "conditional expression between" "" { target c++26 } .-1 }
+  r += b ? u1 : d; // { dg-warning "conditional expression between" "" { target { c++20 && c++23_down } } }
+		   // { dg-error "conditional expression between" "" { target c++26 } .-1 }
 
-  d += e1; // { dg-warning "arithmetic between floating-point type .double. and enumeration type .E1." "" { target c++20 } }
+  d += e1; // { dg-warning "arithmetic between floating-point type .double. and enumeration type .E1." "" { target { c++20 && c++23_down } } }
+	   // { dg-error "arithmetic between floating-point type .double. and enumeration type .E1." "" { target c++26 } .-1 }
   d = e1;
 
   return r;
--- gcc/testsuite/g++.dg/cpp0x/linkage2.C.jj	2023-11-13 13:02:21.587785352 +0100
+++ gcc/testsuite/g++.dg/cpp0x/linkage2.C	2023-11-14 09:39:10.038090059 +0100
@@ -29,5 +29,6 @@  void f() {
   ba.g(a);              // OK
   ba.h(a);              // error, B<T>::h never defined
   i(ba, a);             // OK
-  e1+e2+e3; // { dg-warning "arithmetic between different enumeration types" "" { target c++20 } }
+  e1+e2+e3; // { dg-warning "arithmetic between different enumeration types" "" { target { c++20 && c++23_down } } }
+	    // { dg-error "arithmetic between different enumeration types" "" { target c++26 } .-1 }
 }