diff mbox

[C++] PR 51312

Message ID 53E359BC.10008@oracle.com
State New
Headers show

Commit Message

Paolo Carlini Aug. 7, 2014, 10:49 a.m. UTC
Hi,

this is a rejects valid issue with constexpr conversion operators and 
enums, eg:

struct X0
{
   constexpr operator int() const { return 1; }
};

enum E0 { e0 = X0() };

Given build_expr_type_conversion, we can handle the bug rather easily by 
using it when the enum doesn't have an underlying type, and directly 
perform_implicit_conversion_flags otherwise (as also suggested by Marc 
in the audit trail time ago). Some details:
1- Evidently, we can't just do everything with 
build_expr_type_conversion, similarly, eg, to finish_switch_cond, 
because we would not do the right thing for tests like F5 below, it 
would result in an ambiguous conversion.
2- I moved up, inside the value == NULL_TREE conditional, the existing 
code dealing with fixed underlying type enums, because now it's actually 
used only when we automatically compute the current value starting from 
the previous one (thus the tweaks to the existing testcases)
3- I'm taking the occasion to replace a pair of errors in 
build_expr_type_conversion with error + inform.

Thanks,
Paolo.

////////////////////////
/cp
2014-08-07  Paolo Carlini  <paolo.carlini@oracle.com>

	PR c++/51312
	* decl.c (build_enumerator): Handle class types with conversion
	operators via build_expr_type_conversion.

	* cvt.c (build_expr_type_conversion): Replace pair of errors
	with error + inform.

/testsuite
2014-08-07  Paolo Carlini  <paolo.carlini@oracle.com>

	PR c++/51312
	* g++.dg/cpp0x/enum29.C: New.
	* g++.dg/cpp0x/enum27.C: Adjust.
	* g++.dg/cpp0x/enum_base.C: Likewise.

Comments

Jason Merrill Aug. 7, 2014, 1:24 p.m. UTC | #1
On 08/07/2014 06:49 AM, Paolo Carlini wrote:
> +	  if (ENUM_UNDERLYING_TYPE (enumtype))
> +	    value = perform_implicit_conversion_flags
> +	      (ENUM_UNDERLYING_TYPE (enumtype), value, tf_warning_or_error,
> +	       LOOKUP_IMPLICIT | LOOKUP_NO_NARROWING);

It seems like doing this unconditionally will suppress this error in 
some cases:

> +	      if (!int_fits_type_p (value, ENUM_UNDERLYING_TYPE (enumtype)))
> +		error ("enumerator value %E is outside the range of underlying "
> +		       "type %<%T%>", value, ENUM_UNDERLYING_TYPE (enumtype));

Maybe only do the conversion above if the expression isn't already an 
integer or enumeration?

Jason
Paolo Carlini Aug. 7, 2014, 1:58 p.m. UTC | #2
Hi,

On 08/07/2014 03:24 PM, Jason Merrill wrote:
> On 08/07/2014 06:49 AM, Paolo Carlini wrote:
>> +      if (ENUM_UNDERLYING_TYPE (enumtype))
>> +        value = perform_implicit_conversion_flags
>> +          (ENUM_UNDERLYING_TYPE (enumtype), value, tf_warning_or_error,
>> +           LOOKUP_IMPLICIT | LOOKUP_NO_NARROWING);
>
> It seems like doing this unconditionally will suppress this error in 
> some cases:
>
>> +          if (!int_fits_type_p (value, ENUM_UNDERLYING_TYPE 
>> (enumtype)))
>> +        error ("enumerator value %E is outside the range of 
>> underlying "
>> +               "type %<%T%>", value, ENUM_UNDERLYING_TYPE (enumtype));
>
> Maybe only do the conversion above if the expression isn't already an 
> integer or enumeration?
If we do that, I have also to move back the latter check to the original 
position, otherwise we regress, ie, we don't reject anymore, those two 
testcases I tweaked. That means that when we are handling an enum with 
fixed underlying type we always do that additional final check and 
conversion, which, in my understanding, is redundant if we just call 
perform_implicit_conversion_flags (with LOOKUP_NO_NARROWING!) on top. 
Also, I was thinking earlier today that conceptually the check pasted 
above should check cases different from the cases handled by 
perform_implicit_conversion_flags, thus, eg, *not* handle enum27.C, 
because it's an hard error, isn't our standard (and suppressible) 
narrowing diagnostic. Seems more correct to use it only to diagnose that 
the internally computed next enumerator overflows. See what I mean?

Thanks,
Paolo.
Jason Merrill Aug. 7, 2014, 3:28 p.m. UTC | #3
On 08/07/2014 09:58 AM, Paolo Carlini wrote:
> Also, I was thinking earlier today that conceptually the check pasted
> above should check cases different from the cases handled by
> perform_implicit_conversion_flags, thus, eg, *not* handle enum27.C,
> because it's an hard error, isn't our standard (and suppressible)
> narrowing diagnostic. Seems more correct to use it only to diagnose that
> the internally computed next enumerator overflows. See what I mean?

-Wno-narrowing should prevent an error on enum27; the narrowing 
conversion makes it not a converted constant expression, so the 
initializer is ill-formed.  This probably needs some tweaking in the 
constexpr code.

And we should use the same code path for explicit and implicit values.

Jason
Jason Merrill Aug. 7, 2014, 4:32 p.m. UTC | #4
On 08/07/2014 11:28 AM, Jason Merrill wrote:
> -Wno-narrowing should prevent an error on enum27

Er, should *not*.

Jason
diff mbox

Patch

Index: cp/cvt.c
===================================================================
--- cp/cvt.c	(revision 213692)
+++ cp/cvt.c	(working copy)
@@ -1658,8 +1658,9 @@  build_expr_type_conversion (int desires, tree expr
 		    {
 		      error ("ambiguous default type conversion from %qT",
 			     basetype);
-		      error ("  candidate conversions include %qD and %qD",
-			     winner, cand);
+		      inform (input_location,
+			      "  candidate conversions include %qD and %qD",
+			      winner, cand);
 		    }
 		  return error_mark_node;
 		}
Index: cp/decl.c
===================================================================
--- cp/decl.c	(revision 213692)
+++ cp/decl.c	(working copy)
@@ -12966,14 +12966,32 @@  build_enumerator (tree name, tree value, tree enum
       /* Validate and default VALUE.  */
       if (value != NULL_TREE)
 	{
-	  value = cxx_constant_value (value);
+	  if (ENUM_UNDERLYING_TYPE (enumtype))
+	    value = perform_implicit_conversion_flags
+	      (ENUM_UNDERLYING_TYPE (enumtype), value, tf_warning_or_error,
+	       LOOKUP_IMPLICIT | LOOKUP_NO_NARROWING);
+	  else
+	    {
+	      tree tmp_value = build_expr_type_conversion (WANT_INT | WANT_ENUM,
+							   value, true);
+	      if (tmp_value)
+		value = tmp_value;
+	    }
 
-	  if (TREE_CODE (value) != INTEGER_CST
-	      || ! INTEGRAL_OR_ENUMERATION_TYPE_P (TREE_TYPE (value)))
+	  if (value == error_mark_node)
+	    value = NULL_TREE;
+
+	  if (value != NULL_TREE)
 	    {
-	      error ("enumerator value for %qD is not an integer constant",
-		     name);
-	      value = NULL_TREE;
+	      value = cxx_constant_value (value);
+
+	      if (TREE_CODE (value) != INTEGER_CST
+		  || ! INTEGRAL_OR_ENUMERATION_TYPE_P (TREE_TYPE (value)))
+		{
+		  error ("enumerator value for %qD is not an integer constant",
+			 name);
+		  value = NULL_TREE;
+		}
 	    }
 	}
 
@@ -13036,25 +13054,25 @@  incremented enumerator value is too large for %<lo
 	    }
 	  else
 	    value = integer_zero_node;
-	}
 
-      /* Remove no-op casts from the value.  */
-      STRIP_TYPE_NOPS (value);
+	  /* Remove no-op casts from the value.  */
+	  STRIP_TYPE_NOPS (value);
 
-      /* If the underlying type of the enum is fixed, check whether
-         the enumerator values fits in the underlying type.  If it
-         does not fit, the program is ill-formed [C++0x dcl.enum].  */
-      if (ENUM_UNDERLYING_TYPE (enumtype)
-          && value
-          && TREE_CODE (value) == INTEGER_CST)
-        {
-	  if (!int_fits_type_p (value, ENUM_UNDERLYING_TYPE (enumtype)))
-	    error ("enumerator value %E is outside the range of underlying "
-		   "type %<%T%>", value, ENUM_UNDERLYING_TYPE (enumtype));
+	  /* If the underlying type of the enum is fixed, check whether
+	     the enumerator values fits in the underlying type.  If it
+	     does not fit, the program is ill-formed [C++0x dcl.enum].  */
+	  if (ENUM_UNDERLYING_TYPE (enumtype)
+	      && value
+	      && TREE_CODE (value) == INTEGER_CST)
+	    {
+	      if (!int_fits_type_p (value, ENUM_UNDERLYING_TYPE (enumtype)))
+		error ("enumerator value %E is outside the range of underlying "
+		       "type %<%T%>", value, ENUM_UNDERLYING_TYPE (enumtype));
 
-          /* Convert the value to the appropriate type.  */
-          value = convert (ENUM_UNDERLYING_TYPE (enumtype), value);
-        }
+	      /* Convert the value to the appropriate type.  */
+	      value = convert (ENUM_UNDERLYING_TYPE (enumtype), value);
+	    }
+	}
     }
 
   /* C++ associates enums with global, function, or class declarations.  */
Index: testsuite/g++.dg/cpp0x/enum27.C
===================================================================
--- testsuite/g++.dg/cpp0x/enum27.C	(revision 213692)
+++ testsuite/g++.dg/cpp0x/enum27.C	(working copy)
@@ -1,4 +1,4 @@ 
 // PR c++/53745
 // { dg-do compile { target c++11 } }
 
-enum E : unsigned { e = -1 };  // { dg-error "outside the range" }
+enum E : unsigned { e = -1 };  // { dg-error "narrowing" }
Index: testsuite/g++.dg/cpp0x/enum29.C
===================================================================
--- testsuite/g++.dg/cpp0x/enum29.C	(revision 0)
+++ testsuite/g++.dg/cpp0x/enum29.C	(working copy)
@@ -0,0 +1,58 @@ 
+// PR c++/51312
+// { dg-do compile { target c++11 } }
+
+struct X0
+{
+  constexpr operator int() const { return 1; }
+};
+
+struct X1
+{
+  enum RE1 { re1 = 1 };
+  constexpr operator RE1() const { return re1; }
+};
+
+struct X2
+{
+  constexpr operator int() const { return __INT_MAX__; }
+};
+
+struct X3
+{
+  enum RE3 { re3 = __INT_MAX__ };
+  constexpr operator RE3() const { return re3; }
+};
+
+struct X4
+{
+  constexpr operator double() const { return 1.0; }
+};
+
+struct X5
+{
+  constexpr operator int() const { return __INT_MAX__; }
+  constexpr operator unsigned() const { return __INT_MAX__ * 2U + 1; }
+};
+
+enum E0 { e0 = X0() };
+enum E1 { e1 = X1() };
+enum E2 { e2 = X2() };
+enum E3 { e3 = X3() };
+enum E4 { e4 = X4() };  // { dg-error "integer constant" }
+enum E5 { e5 = X5() };  // { dg-error "ambiguous" }
+
+enum F0 : int { f0 = X0() };
+enum F1 : int { f1 = X1() };
+enum F2 : int { f2 = X2() };
+enum F3 : int { f3 = X3() };
+enum F4 : int { f4 = X4() };  // { dg-error "narrowing" }
+enum F5 : int { f5 = X5() };
+
+enum G0 : signed char { g0 = X0() };
+enum G1 : signed char { g1 = X1() };
+enum G2 : signed char { g2 = X2() };  // { dg-error "narrowing" }
+// { dg-warning "overflow" "" { target *-*-* } 53 }
+enum G3 : signed char { g3 = X3() };  // { dg-error "narrowing" }
+// { dg-warning "overflow" "" { target *-*-* } 55 }
+enum G4 : signed char { g4 = X4() };  // { dg-error "narrowing" }
+enum G5 : signed char { g5 = X5() };  // { dg-error "ambiguous" }
Index: testsuite/g++.dg/cpp0x/enum_base.C
===================================================================
--- testsuite/g++.dg/cpp0x/enum_base.C	(revision 213692)
+++ testsuite/g++.dg/cpp0x/enum_base.C	(working copy)
@@ -6,11 +6,13 @@  enum E1 : char { };
 enum E2 : signed const short { };
 enum E3 : uvlonglong { };
 enum E4 : char { 
-  val = 500 // { dg-error "outside the range" }
+  val = 500 // { dg-error "narrowing" }
+// { dg-warning "overflow" "" { target *-*-* } 9 }
 };
 
 enum class E5 {
-  val = (unsigned long long)-1 // { dg-error "outside the range" }
+  val = (unsigned long long)-1 // { dg-error "narrowing" }
+// { dg-warning "overflow" "" { target *-*-* } 14 }
 };
 
 typedef float Float;