From a98ef5541f952407b46e179270e5a886f418b353 Mon Sep 17 00:00:00 2001 From: Ehab Maali <107338078+71iq@users.noreply.github.com> Date: Sat, 7 Nov 2026 23:15:44 +0200 Subject: [PATCH] C-generating back end: scoped enumerations with a signed underlying type [GH #485] The C-generating back end emitted a scoped enumeration as a C enum type, which the C compiler could make unsigned, so a negative value of a scoped enumeration with a signed underlying type was converted to a large positive value. Use the underlying integer type instead, as is already done for an enumeration with an explicit enum-base. For a bit field of an enumeration type with a fixed underlying type (a scoped enumeration, or one with an explicit enum-base), take the signedness from the underlying type up front, treating bool as unsigned, and use it to compute the number of bits needed. This replaces the override of the signedness at the end of check_enum_type_for_bit_field, so a bool-based enumeration bit field is now unsigned, and the "bit field cannot contain all values" warning is issued consistently for both kinds of fixed underlying type. --- src/class_decl.c | 27 ++++++++++++------- src/il_to_str.c | 2 ++ .../gh-485-scoped-enum-signedness.txt | 18 +++++++++++++ .../default.1.1.txt | 8 ++++++ .../default.1.1.txt | 9 +++++++ .../default.2.1.txt | 9 +++++++ .../regressions/.gh-485.rto/default.1.1.txt | 1 + .../regressions/.gh-485.rto/default.1.2.txt | 1 + .../regressions/.gh-485.rto/default.2.1.txt | 1 + .../regressions/.gh-485.rto/default.2.2.txt | 1 + .../regressions/gh-485-bit-field-size.sft.cpp | 10 +++++++ tests/tests/regressions/gh-485.sft.cpp | 22 +++++++++++++++ 12 files changed, 99 insertions(+), 10 deletions(-) create mode 100644 src/pending_changes/gh-485-scoped-enum-signedness.txt create mode 100644 tests/tests/regressions/.gh-485-bit-field-size.rto/default.1.1.txt create mode 100644 tests/tests/regressions/.gh-485-bit-field-size.rto/default.2.1.txt create mode 100644 tests/tests/regressions/.gh-485.rto/default.1.1.txt create mode 100644 tests/tests/regressions/.gh-485.rto/default.1.2.txt create mode 100644 tests/tests/regressions/.gh-485.rto/default.2.1.txt create mode 100644 tests/tests/regressions/.gh-485.rto/default.2.2.txt create mode 100644 tests/tests/regressions/gh-485-bit-field-size.sft.cpp create mode 100644 tests/tests/regressions/gh-485.sft.cpp diff --git a/src/class_decl.c b/src/class_decl.c index 4e16b53c9f..e4d09ca43e 100644 --- a/src/class_decl.c +++ b/src/class_decl.c @@ -20662,7 +20662,18 @@ must be unsigned. a_constant_ptr smallest, largest; size_t bits_needed, bits_needed_largest, bits_needed_smallest; a_constant_ptr enum_con; - + a_boolean fixed_underlying_type = + bit_field_type->variant.integer.has_explicit_enum_base || + integer_type_is_scoped_enum(bit_field_type); + + if (fixed_underlying_type) { + /* The enum has a fixed underlying type (an explicit enum base, or a + scoped enum), so the bit field has the signedness of that type, + regardless of the values of the enumeration constants. A bool + underlying type is treated as unsigned. */ + use_signed = !enum_has_bool_underlying_type(bit_field_type) && + int_type_is_signed(bit_field_type); + } /* if */ enum_con = enum_constants(bit_field_type); if (enum_con == NULL) { /* There are no enumeration constants, so no bits are needed to @@ -20689,7 +20700,7 @@ must be unsigned. bits_required_to_represent_integer_constant(largest); /* See if the smallest value is negative. */ smallest_is_negative = (sign_of_integer_constant(smallest) < 0); - if (targ_enum_bit_fields_are_always_unsigned) { + if (targ_enum_bit_fields_are_always_unsigned && !fixed_underlying_type) { /* Enum bit fields are always unsigned (many ABIs require this). */ use_signed = FALSE; bits_needed = bits_needed_largest; @@ -20699,7 +20710,9 @@ must be unsigned. for efficiency reasons: if the enum values just fit in the bit field size, an unsigned field might be necessary even though a signed type was a good choice for the enum type. */ - if (smallest_is_negative) { + if (fixed_underlying_type) { + /* The signedness was determined above from the underlying type. */ + } else if (smallest_is_negative) { /* Some enum values are negative, so a signed type is required. The enum type must already be signed. */ use_signed = TRUE; @@ -20734,17 +20747,11 @@ must be unsigned. pos_warning(ec_enum_bit_field_too_small, &error_position); } /* if */ if (targ_enum_bit_fields_are_always_unsigned && smallest_is_negative && - !bit_field_type->variant.integer.has_explicit_enum_base) { + !fixed_underlying_type) { type_warning(ec_unsigned_enum_bit_field_with_signed_enumerator, bit_field_type); } /* if */ } /* if */ - if (bit_field_type->variant.integer.has_explicit_enum_base) { - /* For an explicit underlying type, ignore the constants and just rely on - the underlying type. (We still potentially issue a warning above, - however.) */ - use_signed = int_type_is_signed(bit_field_type); - } /* if */ *need_signed_type = use_signed; } /* check_enum_type_for_bit_field */ diff --git a/src/il_to_str.c b/src/il_to_str.c index 1abaece6e5..701b026422 100644 --- a/src/il_to_str.c +++ b/src/il_to_str.c @@ -2391,11 +2391,13 @@ by octl. back end. Specifically: - don't generate enums when generating pcc mode - don't generate enums with an explicit underlying type + - don't generate scoped enums (their underlying type is fixed) - don't generate empty enums (valid in C++, but not in C) In these cases, the underlying integer type is used instead. */ !(is_for_c_gen_be(octl) && (octl->gen_pcc_code || type->variant.integer.has_explicit_enum_base || + type->variant.integer.is_scoped_enum || enum_constants(type) == NULL))) { /* Output a reference to the enum type. */ form_tag_reference(type, octl); diff --git a/src/pending_changes/gh-485-scoped-enum-signedness.txt b/src/pending_changes/gh-485-scoped-enum-signedness.txt new file mode 100644 index 0000000000..7215761202 --- /dev/null +++ b/src/pending_changes/gh-485-scoped-enum-signedness.txt @@ -0,0 +1,18 @@ +[GH #485] +C-generating back end: scoped enumerations with a signed underlying type + +The C-generating back end emitted a scoped enumeration as a C enum type. The +C compiler could give that enum type an unsigned representation, so a negative +value of a scoped enumeration whose underlying type is signed (int, when no +enum-base is specified) was converted to a large positive value. The +underlying integer type is now used instead, as it already was for enums with +an explicit enum-base. Bit fields of an enumeration type with a fixed +underlying type (a scoped enumeration, or one with an explicit enum-base) now +also take their signedness from that type, so a bit field of an enumeration +with underlying type bool can hold the value true. For example: + + enum class E { ZERO = 0 }; + int main() { + E e = static_cast(-1); + long widened = static_cast(e); // Was 4294967295, now -1. + } diff --git a/tests/tests/imported/clang/cpp/SemaCXX/.warn-msvc-enum-bitfield.rto/default.1.1.txt b/tests/tests/imported/clang/cpp/SemaCXX/.warn-msvc-enum-bitfield.rto/default.1.1.txt index 948d363a86..fd7cb4eb5a 100644 --- a/tests/tests/imported/clang/cpp/SemaCXX/.warn-msvc-enum-bitfield.rto/default.1.1.txt +++ b/tests/tests/imported/clang/cpp/SemaCXX/.warn-msvc-enum-bitfield.rto/default.1.1.txt @@ -3,6 +3,14 @@ fe_only -DTEST_NUMBER=1 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers struct { E e1 : 1; E e2; F f1 : 1; F f2; } s; ^ +"Test_name.c", line 22: warning: bit field cannot contain all values of the enumerated type + struct { E e1 : 1; E e2; F f1 : 1; F f2; } s; + ^ + +"Test_name.c", line 22: warning: bit field cannot contain all values of the enumerated type + struct { E e1 : 1; E e2; F f1 : 1; F f2; } s; + ^ + "Test_name.c", line 22: warning: variable "s" was set but never used struct { E e1 : 1; E e2; F f1 : 1; F f2; } s; ^ diff --git a/tests/tests/regressions/.gh-485-bit-field-size.rto/default.1.1.txt b/tests/tests/regressions/.gh-485-bit-field-size.rto/default.1.1.txt new file mode 100644 index 0000000000..2427e8ddad --- /dev/null +++ b/tests/tests/regressions/.gh-485-bit-field-size.rto/default.1.1.txt @@ -0,0 +1,9 @@ +fe_only -DTEST_NUMBER=1 --c++20 --g++ --gnu_version=130300 Test_name.c +"Test_name.c", line 8: warning: bit field cannot contain all values of the enumerated type + struct S { F f : 2; }; + ^ + +"Test_name.c", line 10: warning: bit field cannot contain all values of the enumerated type + struct T { E e : 2; }; + ^ + diff --git a/tests/tests/regressions/.gh-485-bit-field-size.rto/default.2.1.txt b/tests/tests/regressions/.gh-485-bit-field-size.rto/default.2.1.txt new file mode 100644 index 0000000000..6baffc987f --- /dev/null +++ b/tests/tests/regressions/.gh-485-bit-field-size.rto/default.2.1.txt @@ -0,0 +1,9 @@ +fe_only -DTEST_NUMBER=2 --c++20 --strict Test_name.c +"Test_name.c", line 8: warning: bit field cannot contain all values of the enumerated type + struct S { F f : 2; }; + ^ + +"Test_name.c", line 10: warning: bit field cannot contain all values of the enumerated type + struct T { E e : 2; }; + ^ + diff --git a/tests/tests/regressions/.gh-485.rto/default.1.1.txt b/tests/tests/regressions/.gh-485.rto/default.1.1.txt new file mode 100644 index 0000000000..285a052375 --- /dev/null +++ b/tests/tests/regressions/.gh-485.rto/default.1.1.txt @@ -0,0 +1 @@ +eccp -DTEST_NUMBER=1 --c++20 --g++ --gnu_version=130300 Test_name.c diff --git a/tests/tests/regressions/.gh-485.rto/default.1.2.txt b/tests/tests/regressions/.gh-485.rto/default.1.2.txt new file mode 100644 index 0000000000..ff2642c6d5 --- /dev/null +++ b/tests/tests/regressions/.gh-485.rto/default.1.2.txt @@ -0,0 +1 @@ +./a.out diff --git a/tests/tests/regressions/.gh-485.rto/default.2.1.txt b/tests/tests/regressions/.gh-485.rto/default.2.1.txt new file mode 100644 index 0000000000..8f7e41e84c --- /dev/null +++ b/tests/tests/regressions/.gh-485.rto/default.2.1.txt @@ -0,0 +1 @@ +eccp -DTEST_NUMBER=2 --c++20 --strict Test_name.c diff --git a/tests/tests/regressions/.gh-485.rto/default.2.2.txt b/tests/tests/regressions/.gh-485.rto/default.2.2.txt new file mode 100644 index 0000000000..ff2642c6d5 --- /dev/null +++ b/tests/tests/regressions/.gh-485.rto/default.2.2.txt @@ -0,0 +1 @@ +./a.out diff --git a/tests/tests/regressions/gh-485-bit-field-size.sft.cpp b/tests/tests/regressions/gh-485-bit-field-size.sft.cpp new file mode 100644 index 0000000000..aa635ffeea --- /dev/null +++ b/tests/tests/regressions/gh-485-bit-field-size.sft.cpp @@ -0,0 +1,10 @@ +//type:fp +//options_all:--c++20 +//options:--g++ --gnu_version=130300:--strict +// An enum with a fixed underlying type gives its bit fields the signedness of +// that type, whether the enum is scoped or has an explicit enum base, so both +// of these bit fields are too small to hold the value 3. +enum class F { f3 = 3 }; +struct S { F f : 2; }; +enum E : int { e3 = 3 }; +struct T { E e : 2; }; diff --git a/tests/tests/regressions/gh-485.sft.cpp b/tests/tests/regressions/gh-485.sft.cpp new file mode 100644 index 0000000000..809e33d009 --- /dev/null +++ b/tests/tests/regressions/gh-485.sft.cpp @@ -0,0 +1,22 @@ +//type:rp +//options_all:--c++20 +//options:--g++ --gnu_version=130300:--strict +// Test the emitted C representation as well as the front-end type. +enum class Signed { zero = 0 }; +enum struct Empty {}; +enum class Unsigned : unsigned { zero = 0 }; +enum class Boolean : bool { zero = false, one = true }; +struct Fields { Signed s : 3; Unsigned u : 3; Boolean b : 1; }; +#ifdef __GNUC__ +static_assert(__is_same(__underlying_type(Signed), int), "signed base"); +static_assert(__is_same(__underlying_type(Empty), int), "empty base"); +#endif /* __GNUC__ */ +volatile int minus_one = -1; +int main() { + Signed s = static_cast(minus_one); + Empty e = static_cast(minus_one); + Fields f{static_cast(minus_one), Unsigned::zero, Boolean::one}; + return static_cast(s) != -1 || static_cast(e) != -1 || + static_cast(f.s) != -1 || static_cast(f.u) != 0 || + static_cast(f.b) != 1; +}