Message ID | 194e5cd68595c5c923b869be2d875ed0991b98f0.camel@tugraz.at |
---|---|
State | New |
Headers | show |
Series | [C,v2] Fix for redeclared enumerator initialized with different type [PR115109] | expand |
On Sun, 19 May 2024, Martin Uecker wrote: > c23 specifies that the type of a redeclared enumerator is the one of the > previous declaration. Convert initializers with different type accordingly > and add -Woverflow warning. It doesn't make sense to use -Woverflow. Either the value is the same (in which case it fits in the desired type), or it's different (and you should get the "conflicting redeclaration of enumerator" error or some equivalent error, whether or not the value in the redeclaration fits in the previous type). Note that this includes both explicit values and values determined by adding 1 implicitly. E.g. enum e { A = 0, B = UINT_MAX }; enum e { B = UINT_MAX, A }; is not valid, because in the redefinition, A gets the value 1 greater than UINT_MAX (which is not representable in unsigned int) - there is *not* an addition in type unsigned int, or in type enum e. The constraint violated is the general one "If an identifier has no linkage, there shall be no more than one declaration of the identifier (in a declarator or type specifier) with the same scope and in the same name space, except that: ... enumeration constants and tags may be redeclared as specified in 6.7.3.3 and 6.7.3.4, respectively." (where 6.7.3.3 says "Enumeration constants can be redefined in the same scope with the same value as part of a redeclaration of the same enumerated type." - as the redefinition is not with the same value, the "as specified in 6.7.3.3" is not satisfied and so the general constraint against redeclarations with no linkage applies).
Am Montag, dem 20.05.2024 um 21:30 +0000 schrieb Joseph Myers: > On Sun, 19 May 2024, Martin Uecker wrote: > > > c23 specifies that the type of a redeclared enumerator is the one of the > > previous declaration. Convert initializers with different type accordingly > > and add -Woverflow warning. > > It doesn't make sense to use -Woverflow. Either the value is the same (in > which case it fits in the desired type), or it's different (and you should > get the "conflicting redeclaration of enumerator" error or some equivalent > error, whether or not the value in the redeclaration fits in the previous > type). > > Note that this includes both explicit values and values determined by > adding 1 implicitly. E.g. > > enum e { A = 0, B = UINT_MAX }; > enum e { B = UINT_MAX, A }; > > is not valid, because in the redefinition, A gets the value 1 greater than > UINT_MAX (which is not representable in unsigned int) - there is *not* an > addition in type unsigned int, or in type enum e. > > The constraint violated is the general one "If an identifier has no > linkage, there shall be no more than one declaration of the identifier (in > a declarator or type specifier) with the same scope and in the same name > space, except that: ... enumeration constants and tags may be redeclared > as specified in 6.7.3.3 and 6.7.3.4, respectively." (where 6.7.3.3 says > "Enumeration constants can be redefined in the same scope with the same > value as part of a redeclaration of the same enumerated type." - as the > redefinition is not with the same value, the "as specified in 6.7.3.3" is > not satisfied and so the general constraint against redeclarations with no > linkage applies). This assumes that the value in question is the one of the initializer and not the one after initialization (with no clear rules how this works in this case), which is probably not how this wording would be understood in other contexts. But I agree that your interpretation is probably closer to what was intended and makes more sense in this case. Martin >
On Tue, 21 May 2024, Martin Uecker wrote: > > The constraint violated is the general one "If an identifier has no > > linkage, there shall be no more than one declaration of the identifier (in > > a declarator or type specifier) with the same scope and in the same name > > space, except that: ... enumeration constants and tags may be redeclared > > as specified in 6.7.3.3 and 6.7.3.4, respectively." (where 6.7.3.3 says > > "Enumeration constants can be redefined in the same scope with the same > > value as part of a redeclaration of the same enumerated type." - as the > > redefinition is not with the same value, the "as specified in 6.7.3.3" is > > not satisfied and so the general constraint against redeclarations with no > > linkage applies). > > This assumes that the value in question is the one of the initializer and not the > one after initialization (with no clear rules how this works in this case), There are no initializers here. The constant-expression after '=' in the syntax for enumerator is not an initializer, and none of the rules for initializers apply to it. (If the initializer with an out-of-range value also gets used in an expression, the constraint on constants would be violated, "Each constant shall have a type and the value of a constant shall be in the range of representable values for its type." - but the constraint on multiple declarations applies whether it's used or not.)
diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc index b691b91b3db..540927a8df6 100644 --- a/gcc/c/c-decl.cc +++ b/gcc/c/c-decl.cc @@ -10209,6 +10209,7 @@ build_enumerator (location_t decl_loc, location_t loc, struct c_enum_contents *the_enum, tree name, tree value) { tree decl; + tree old_decl; /* Validate and default VALUE. */ @@ -10268,6 +10269,24 @@ build_enumerator (location_t decl_loc, location_t loc, definition. */ value = convert (the_enum->enum_type, value); } + else if (flag_isoc23 + && (old_decl = lookup_name_in_scope (name, current_scope)) + && old_decl != error_mark_node + && TREE_TYPE (old_decl) + && TREE_TYPE (TREE_TYPE (old_decl)) + && TREE_CODE (old_decl) == CONST_DECL) + { + /* Enumeration constants in a redeclaration have the previous type. */ + tree previous_type = TREE_TYPE (DECL_INITIAL (old_decl)); + if (!int_fits_type_p (value, previous_type)) + { + warning_at (loc, OPT_Woverflow, + "value of redeclared enumerator outside the range of " + "the previous type %qT", previous_type); + locate_old_decl (old_decl); + } + value = convert (previous_type, value); + } else { /* Even though the underlying type of an enum is unspecified, the @@ -10334,9 +10353,14 @@ build_enumerator (location_t decl_loc, location_t loc, false); } else - the_enum->enum_next_value - = build_binary_op (EXPR_LOC_OR_LOC (value, input_location), - PLUS_EXPR, value, integer_one_node, false); + { + /* In a redeclaration the type can already be the enumeral type. */ + if (TREE_CODE (TREE_TYPE (value)) == ENUMERAL_TYPE) + value = convert (ENUM_UNDERLYING_TYPE (TREE_TYPE (value)), value); + the_enum->enum_next_value + = build_binary_op (EXPR_LOC_OR_LOC (value, input_location), + PLUS_EXPR, value, integer_one_node, false); + } the_enum->enum_overflow = tree_int_cst_lt (the_enum->enum_next_value, value); if (the_enum->enum_overflow && !ENUM_FIXED_UNDERLYING_TYPE_P (the_enum->enum_type)) diff --git a/gcc/testsuite/gcc.dg/c23-tag-enum-6.c b/gcc/testsuite/gcc.dg/c23-tag-enum-6.c new file mode 100644 index 00000000000..ff9ec89775e --- /dev/null +++ b/gcc/testsuite/gcc.dg/c23-tag-enum-6.c @@ -0,0 +1,14 @@ +/* { dg-do compile } */ +/* { dg-options "-std=c23" } */ + +#include <limits.h> + +enum E : int { a = 1, b = 2 }; +enum E : int { b = _Generic(a, enum E: 2), a = 1 }; + +enum H { x = 1 }; +enum H { x = 2UL + UINT_MAX }; /* { dg-warning "outside the range" } */ + +enum K : int { z = 1 }; +enum K : int { z = 2UL + UINT_MAX }; /* { dg-error "outside the range" } */ + diff --git a/gcc/testsuite/gcc.dg/c23-tag-enum-7.c b/gcc/testsuite/gcc.dg/c23-tag-enum-7.c new file mode 100644 index 00000000000..4a5b4bc63f6 --- /dev/null +++ b/gcc/testsuite/gcc.dg/c23-tag-enum-7.c @@ -0,0 +1,41 @@ +/* { dg-do compile } + * { dg-options "-std=c23" } */ + +#include <limits.h> + +// enumerators are all representable in int +enum E { a = 1UL, b = _Generic(a, int: 2) }; +static_assert(_Generic(a, int: 1)); +static_assert(_Generic(b, int: 1)); +enum E { a = 1UL, b = _Generic(a, int: 2) }; +static_assert(_Generic(a, int: 1)); +static_assert(_Generic(b, int: 1)); + +// enumerators are not representable in int +enum H { c = 1UL << (UINT_WIDTH + 1), d = 2 }; +static_assert(_Generic(c, enum H: 1)); +static_assert(_Generic(d, enum H: 1)); +enum H { c = 1UL << (UINT_WIDTH + 1), d = _Generic(c, enum H: 2) }; +static_assert(_Generic(c, enum H: 1)); +static_assert(_Generic(d, enum H: 1)); + +// there is an overflow in the first redeclaration +enum K { e = UINT_MAX, f, g = _Generic(e, unsigned int: 0) + _Generic(f, unsigned long: 1) }; +static_assert(_Generic(e, enum K: 1)); +static_assert(_Generic(f, enum K: 1)); +static_assert(_Generic(g, enum K: 1)); +enum K { e = UINT_MAX, f, g = _Generic(e, enum K: 0) + _Generic(f, enum K: 1) }; +static_assert(_Generic(e, enum K: 1)); +static_assert(_Generic(f, enum K: 1)); +static_assert(_Generic(g, enum K: 1)); + +// there is an overflow in the first redeclaration +enum U { k = INT_MAX, l, m = _Generic(k, int: 0) + _Generic(l, long: 1) }; +static_assert(_Generic(k, enum U: 1)); +static_assert(_Generic(l, enum U: 1)); +static_assert(_Generic(m, enum U: 1)); +enum U { k = INT_MAX, l, m = _Generic(k, enum U: 0) + _Generic(l, enum U: 1) }; +static_assert(_Generic(k, enum U: 1)); +static_assert(_Generic(l, enum U: 1)); +static_assert(_Generic(m, enum U: 1)); + diff --git a/gcc/testsuite/gcc.dg/pr115109.c b/gcc/testsuite/gcc.dg/pr115109.c new file mode 100644 index 00000000000..961197d0c71 --- /dev/null +++ b/gcc/testsuite/gcc.dg/pr115109.c @@ -0,0 +1,8 @@ +/* { dg-do compile } */ +/* { dg-options "-std=c23" } */ + +#include <limits.h> + +enum E { a = 1UL << (ULONG_WIDTH - 5), b = 2 }; +enum E { a = 1ULL << (ULONG_WIDTH - 5), b = _Generic(a, enum E: 2) }; +