From: Bart Van Assche <bvanassche@acm.org>
To: linux-sparse@vger.kernel.org
Cc: Linus Torvalds <torvalds@linux-foundation.org>,
Bart Van Assche <bvanassche@acm.org>
Subject: [PATCH 51/73] c23: Support enumerations with fixed underlying types
Date: Mon, 9 Nov 2026 12:32:37 -0800 [thread overview]
Message-ID: <20261109203259.362845-52-bvanassche@acm.org> (raw)
In-Reply-To: <20261109203259.362845-1-bvanassche@acm.org>
Implement ISO C23 enumerations with fixed underlying types
('enum [tag] : type { ... }' and standalone 'enum tag : type;'):
- Parse and canonicalize fixed integer underlying types, enforce
redeclaration compatibility, and reject non-standalone opaque
specifiers per C23 6.7.2.2p5.
- Disambiguate fixed-type ': type' from unnamed enum bit-fields and
_Generic 'enum tag: expr' associations.
- Type enumerators per C23 6.7.2.2p12-13 in C23 mode (giving fixed-enum
members the underlying type during and after the brace list, and giving
unfixed-enum members 'int' only when the entire enum fits in 'int'),
while preserving per-enumerator GNU typing in pre-C23 modes.
- Support bool-backed enums and diagnose ULLONG_MAX auto-increment
overflow before wraparound.
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
evaluate.c | 2 +-
expand.c | 13 +-
parse.c | 269 +++++++++++++++++++++++++------
symbol.c | 19 ++-
symbol.h | 38 ++---
validation/c23-enum-type-cast.c | 70 ++++++++
validation/c23-enum-type-error.c | 40 +++++
validation/c23-enum-type.c | 116 +++++++++++++
8 files changed, 494 insertions(+), 73 deletions(-)
create mode 100644 validation/c23-enum-type-cast.c
create mode 100644 validation/c23-enum-type-error.c
create mode 100644 validation/c23-enum-type.c
diff --git a/evaluate.c b/evaluate.c
index b7e352f55e6c..43ad5f59b860 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -2486,7 +2486,7 @@ static struct symbol *evaluate_sizeof(struct expression *expr)
}
if (is_bool_type(type)) {
- if (Wsizeof_bool)
+ if (Wsizeof_bool && cbase_type(type) == &bool_ctype)
warning(expr->pos, "expression using sizeof _Bool");
size = bits_to_bytes(bits_in_bool) * bits_in_char;
}
diff --git a/expand.c b/expand.c
index 73b37b93534e..13a3bc0457ba 100644
--- a/expand.c
+++ b/expand.c
@@ -106,12 +106,23 @@ void cast_value(struct expression *expr, struct symbol *newtype, struct expressi
// For pointers and integers, we can just move the value around
expr->type = EXPR_VALUE;
expr->taint = old->taint;
- if (old_size == new_size) {
+ if (old_size == new_size && !is_bool_type(newtype)) {
expr->value = old->value;
expr->ctype = newtype;
return;
}
+ if (is_bool_type(newtype)) {
+ bool non_zero = old->value != 0;
+ bool is_one = old->value == 1;
+ expr->ctype = newtype;
+ expr->value = non_zero;
+ if (!conservative && non_zero && !is_one)
+ warning(old->pos, "odd constant _Bool cast (%llx becomes 1)",
+ old->value);
+ return;
+ }
+
// expand it to the full "long long" value
value = get_longlong(old);
expr->ctype = newtype;
diff --git a/parse.c b/parse.c
index a7d31fad1a10..48793dd51671 100644
--- a/parse.c
+++ b/parse.c
@@ -50,6 +50,7 @@ struct statement_list *function_computed_goto_list;
static struct token *statement(struct token *token, struct statement **tree);
static struct token *handle_attributes(struct token *token, struct decl_state *ctx);
+static struct token *skip_attributes(struct token *token);
typedef struct token *declarator_t(struct token *, struct symbol *, struct decl_state *);
static declarator_t
@@ -765,6 +766,32 @@ struct symbol *label_symbol(struct token *token, int used)
return sym;
}
+static struct symbol *canonical_enum_underlying_type(struct symbol *sym)
+{
+ sym = cbase_type(sym);
+ if (sym == &sshort_ctype)
+ return &short_ctype;
+ if (sym == &sint_ctype)
+ return &int_ctype;
+ if (sym == &slong_ctype)
+ return &long_ctype;
+ if (sym == &sllong_ctype)
+ return &llong_ctype;
+ if (sym == &sint128_ctype)
+ return &int128_ctype;
+ return sym;
+}
+
+static bool has_enum_type_specifier(struct token *token)
+{
+ struct token *next;
+
+ if (!match_op(token, ':'))
+ return false;
+ next = skip_attributes(token->next);
+ return lookup_type(next) != 0;
+}
+
static struct token *struct_union_enum_specifier(enum type type,
struct token *token, struct decl_state *ctx,
struct token *(*parse)(struct token *, struct symbol *))
@@ -778,7 +805,10 @@ static struct token *struct_union_enum_specifier(enum type type,
sym = lookup_symbol(token->ident, NS_STRUCT);
if (!sym ||
(is_outer_scope(sym->scope) &&
- (match_op(token->next,';') || match_op(token->next,'{')))) {
+ (match_op(token->next, ';') ||
+ match_op(token->next, '{') ||
+ (type == SYM_ENUM &&
+ has_enum_type_specifier(token->next))))) {
// Either a new symbol, or else an out-of-scope
// symbol being redefined.
sym = alloc_symbol(token->pos, type);
@@ -789,6 +819,60 @@ static struct token *struct_union_enum_specifier(enum type type,
ctx->ctype.base_type = sym;
repos = &token->pos;
token = token->next;
+
+ if (type == SYM_ENUM && has_enum_type_specifier(token)) {
+ struct position colon_pos = token->pos;
+ struct symbol *underlying = NULL;
+ if (standard < STANDARD_C23 && Wpedantic)
+ warning(token->pos,
+ "ISO C does not support specifying 'enum' underlying types before C23");
+ token = typename(token->next, &underlying, NULL);
+ underlying = examine_symbol_type(underlying);
+ if (!underlying || !is_int_type(underlying) ||
+ is_enum_type(underlying)) {
+ sparse_error(token->pos,
+ "invalid 'enum' underlying type");
+ underlying = &int_ctype;
+ }
+ underlying = canonical_enum_underlying_type(underlying);
+ if (sym->fixed_type) {
+ if (canonical_enum_underlying_type(sym->ctype.base_type) !=
+ underlying)
+ sparse_error(
+ token->pos,
+ "'enum' underlying type incompatible with previous declaration");
+ } else if (sym->examined || sym->symbol_list ||
+ (sym->ctype.base_type ==
+ &incomplete_ctype)) {
+ sparse_error(
+ token->pos,
+ "'enum' declared both with and without fixed underlying type");
+ } else {
+ sym->fixed_type = 1;
+ sym->ctype.base_type = underlying;
+ sym->ctype.modifiers |=
+ (underlying->ctype.modifiers &
+ MOD_UNSIGNED);
+ if (underlying == &bool_ctype)
+ sym->bit_size = bits_to_bytes(bits_in_bool) * bits_in_char;
+ else
+ sym->bit_size = underlying->bit_size;
+ sym->ctype.alignment =
+ underlying->ctype.alignment;
+ sym->rank = underlying->rank;
+ }
+ token = handle_attributes(token, &attr);
+ if (!match_op(token, '{')) {
+ ctx->opaque_fixed_enum = 1;
+ ctx->opaque_fixed_enum_pos = colon_pos;
+ }
+ } else if (type == SYM_ENUM && sym->fixed_type &&
+ match_op(token, '{')) {
+ sparse_error(
+ token->pos,
+ "'enum' declared with but defined without fixed underlying type");
+ }
+
if (!match_op(token, '{')) {
if (attr.designated_init) {
if (sym->type == SYM_STRUCT)
@@ -814,11 +898,44 @@ static struct token *struct_union_enum_specifier(enum type type,
// Mark the structure as needing re-examination
sym->examined = 0;
- } else if (match_op(token, '{')) {
+ } else if (match_op(token, '{') ||
+ (type == SYM_ENUM && match_op(token, ':'))) {
// private struct/union/enum type
sym = alloc_symbol(token->pos, type);
set_current_scope(sym); // used by dissect
ctx->ctype.base_type = sym;
+ if (type == SYM_ENUM && match_op(token, ':')) {
+ struct symbol *underlying = NULL;
+ if (standard < STANDARD_C23 && Wpedantic)
+ warning(token->pos,
+ "ISO C does not support specifying 'enum' underlying types before C23");
+ token = typename(token->next, &underlying, NULL);
+ underlying = examine_symbol_type(underlying);
+ if (!underlying || !is_int_type(underlying) ||
+ is_enum_type(underlying)) {
+ sparse_error(token->pos,
+ "invalid 'enum' underlying type");
+ underlying = &int_ctype;
+ }
+ underlying = canonical_enum_underlying_type(underlying);
+ sym->fixed_type = 1;
+ sym->ctype.base_type = underlying;
+ sym->ctype.modifiers |=
+ (underlying->ctype.modifiers & MOD_UNSIGNED);
+ if (underlying == &bool_ctype)
+ sym->bit_size = bits_to_bytes(bits_in_bool) * bits_in_char;
+ else
+ sym->bit_size = underlying->bit_size;
+ sym->ctype.alignment = underlying->ctype.alignment;
+ sym->rank = underlying->rank;
+ token = handle_attributes(token, &attr);
+ if (!match_op(token, '{')) {
+ sparse_error(
+ token->pos,
+ "expected '{' after enum type specifier");
+ return token;
+ }
+ }
} else {
sparse_error(token->pos, "expected declaration");
ctx->ctype.base_type = &bad_ctype;
@@ -941,8 +1058,8 @@ static struct range type_range(struct symbol *type)
long long min;
if (is_signed_type(type)) {
- min = sign_bit(size);
- max = min - 1;
+ min = sign_extend(sign_bit(size), size);
+ max = sign_mask(size);
} else {
min = 0;
max = bits_mask(size);
@@ -963,24 +1080,37 @@ static int val_in_range(struct range *range, long long sval, struct symbol *vtyp
return uval <= range->pos;
}
-static void cast_enum_list(struct symbol_list *list, struct symbol *base_type)
+static void cast_enum_list(struct symbol_list *list, struct symbol *base_type,
+ struct range range)
{
struct range irange = type_range(&int_ctype);
+ struct symbol *target = base_type;
struct symbol *sym;
+ if (is_int_type(base_type) && range.neg >= irange.neg &&
+ range.pos <= irange.pos)
+ target = &int_ctype;
+
FOR_EACH_PTR(list, sym) {
struct expression *expr = sym->initializer;
struct symbol *ctype;
- long long val;
+ struct symbol *etype = target;
+
if (expr->type != EXPR_VALUE)
continue;
ctype = expr->ctype;
- val = get_expression_value(expr);
- if (is_int_type(ctype) && val_in_range(&irange, val, ctype)) {
- expr->ctype = &int_ctype;
- continue;
+ if (standard < STANDARD_C23 && etype != &int_ctype &&
+ is_int_type(ctype)) {
+ long long val = get_expression_value(expr);
+
+ if (!(is_signed_type(ctype) && val < 0) &&
+ (unsigned long long)val <= irange.pos)
+ etype = &int_ctype;
}
- cast_value(expr, base_type, expr);
+ if (!is_int_type(ctype) && etype == &int_ctype)
+ continue;
+ cast_value(expr, etype, expr);
+ expr->ctype = etype;
} END_FOR_EACH_PTR(sym);
}
@@ -992,13 +1122,16 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
int mix_bitwise = 0;
parent->examined = 1;
- parent->ctype.base_type = &int_ctype;
+ if (parent->fixed_type)
+ base_type = parent->ctype.base_type;
+ else
+ parent->ctype.base_type = &int_ctype;
while (token_type(token) == TOKEN_IDENT) {
struct expression *expr = NULL;
struct token *next = token->next;
struct decl_state ctx = { };
struct symbol *sym;
- bool overflow = false;
+ bool overflowed = false;
// FIXME: only 'deprecated' should be accepted
next = handle_attributes(next, &ctx);
@@ -1016,7 +1149,7 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
ctype = &int_ctype;
}
} else if (!ctype) {
- ctype = &int_ctype;
+ ctype = parent->fixed_type ? base_type : &int_ctype;
} else if (is_int_type(ctype)) {
struct range crange = type_range(ctype);
if (is_signed_type(ctype) && (long long)lastval == crange.pos) {
@@ -1038,13 +1171,20 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
else if (ctype->bit_size < bits_in_longlong)
ctype = &ullong_ctype;
else {
- sparse_error(token->pos,
- "enumerator value for '%s' overflows",
- show_ident(token->ident));
- overflow = true;
+ if (parent->fixed_type)
+ sparse_error(
+ token->pos,
+ "enumerator value outside the range of underlying type");
+ else
+ sparse_error(
+ token->pos,
+ "enumerator value for '%s' overflows",
+ show_ident(token->ident));
+ ctype = &ullong_ctype;
+ overflowed = true;
}
}
- if (!overflow)
+ if (!overflowed)
lastval++;
} else {
error_die(token->pos, "can't increment the last enum member");
@@ -1056,6 +1196,16 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
expr->ctype = ctype;
}
+ if (parent->fixed_type) {
+ struct range urange = type_range(base_type);
+ if (!overflowed && !val_in_range(&urange, lastval, ctype))
+ sparse_error(
+ token->pos,
+ "enumerator value outside the range of underlying type");
+ cast_value(expr, base_type, expr);
+ ctype = base_type;
+ }
+
sym = alloc_symbol(token->pos, SYM_NODE);
bind_symbol(sym, token->ident, NS_SYMBOL);
sym->ctype.modifiers &= ~MOD_ADDRESSABLE;
@@ -1064,7 +1214,7 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
sym->ctype.base_type = parent;
add_ptr_list(&parent->symbol_list, sym);
- if (base_type != &bad_ctype) {
+ if (!parent->fixed_type && base_type != &bad_ctype) {
if (ctype->type == SYM_NODE)
ctype = ctype->ctype.base_type;
if (ctype->type == SYM_ENUM) {
@@ -1104,7 +1254,7 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
}
parent->ctype.base_type = base_type;
}
- if (is_int_type(base_type)) {
+ if (!parent->fixed_type && is_int_type(base_type)) {
update_range(&range, lastval, ctype);
}
token = next;
@@ -1115,33 +1265,40 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
break;
token = token->next;
}
- if (!base_type) {
- sparse_error(token->pos, "empty enum definition");
- base_type = &bad_ctype;
- }
- else if (!is_int_type(base_type))
- ;
- else if (type_is_ok(&uint_ctype, range))
- base_type = &uint_ctype;
- else if (type_is_ok(&int_ctype, range))
- base_type = &int_ctype;
- else if (type_is_ok(&ulong_ctype, range))
- base_type = &ulong_ctype;
- else if (type_is_ok(&long_ctype, range))
- base_type = &long_ctype;
- else if (type_is_ok(&ullong_ctype, range))
- base_type = &ullong_ctype;
- else if (type_is_ok(&llong_ctype, range))
- base_type = &llong_ctype;
- else
- base_type = &bad_ctype;
- parent->ctype.base_type = base_type;
- parent->ctype.modifiers |= (base_type->ctype.modifiers & MOD_UNSIGNED);
- parent->examined = 0;
+ if (!parent->fixed_type) {
+ if (!base_type) {
+ sparse_error(token->pos, "empty enum definition");
+ base_type = &bad_ctype;
+ } else if (!is_int_type(base_type))
+ ;
+ else if (type_is_ok(&uint_ctype, range))
+ base_type = &uint_ctype;
+ else if (type_is_ok(&int_ctype, range))
+ base_type = &int_ctype;
+ else if (type_is_ok(&ulong_ctype, range))
+ base_type = &ulong_ctype;
+ else if (type_is_ok(&long_ctype, range))
+ base_type = &long_ctype;
+ else if (type_is_ok(&ullong_ctype, range))
+ base_type = &ullong_ctype;
+ else if (type_is_ok(&llong_ctype, range))
+ base_type = &llong_ctype;
+ else
+ base_type = &bad_ctype;
+ parent->ctype.base_type = base_type;
+ parent->ctype.modifiers |=
+ (base_type->ctype.modifiers & MOD_UNSIGNED);
+ parent->examined = 0;
- if (mix_bitwise)
- return token;
- cast_enum_list(parent->symbol_list, base_type);
+ if (mix_bitwise)
+ return token;
+ cast_enum_list(parent->symbol_list, base_type, range);
+ } else {
+ if (!parent->symbol_list) {
+ sparse_error(token->pos, "empty enum definition");
+ }
+ parent->examined = 0;
+ }
return token;
}
@@ -2130,6 +2287,9 @@ static struct token *declaration_list(struct token *token, struct symbol_list **
unsigned long mod;
token = declaration_specifiers(token, &ctx);
+ if (ctx.opaque_fixed_enum)
+ sparse_error(ctx.opaque_fixed_enum_pos,
+ "'enum' underlying type may not be specified here");
mod = decl_modifiers(&ctx);
saved = ctx.ctype;
for (;;) {
@@ -2200,6 +2360,9 @@ static struct token *parameter_declaration(struct token *token, struct symbol *s
struct decl_state ctx = {.prefer_abstract = 1};
token = declaration_specifiers(token, &ctx);
+ if (ctx.opaque_fixed_enum)
+ sparse_error(ctx.opaque_fixed_enum_pos,
+ "'enum' underlying type may not be specified here");
ctx.ident = &sym->ident;
token = declarator(token, &ctx);
token = handle_attributes(token, &ctx);
@@ -2240,6 +2403,9 @@ struct token *typename(struct token *token, struct symbol **p, int *forced)
token = declaration_specifiers(token, &ctx);
if (ctx.has_alignas)
sparse_error(sym->pos, "alignment specified for type name");
+ if (ctx.opaque_fixed_enum)
+ sparse_error(ctx.opaque_fixed_enum_pos,
+ "'enum' underlying type may not be specified here");
if (ctx.autotype) {
sparse_error(sym->pos, "'%s' in type name",
ctx.has_auto ? "auto" : "__auto_type");
@@ -3309,6 +3475,12 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
decl = alloc_symbol(token->pos, SYM_NODE);
/* Just a type declaration? */
if (match_op(token, ';')) {
+ if (ctx.opaque_fixed_enum &&
+ (ctx.storage_class || (mod & ~MOD_UNUSED) ||
+ ctx.ctype.modifiers || ctx.has_auto || ctx.is_constexpr))
+ sparse_error(
+ ctx.opaque_fixed_enum_pos,
+ "'enum' underlying type may not be specified here");
if (ctx.autotype)
sparse_error(token->pos, "%s in empty declaration",
ctx.has_auto ? "auto" : "__auto_type");
@@ -3318,6 +3490,9 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
apply_modifiers(token->pos, &ctx);
return token->next;
}
+ if (ctx.opaque_fixed_enum)
+ sparse_error(ctx.opaque_fixed_enum_pos,
+ "'enum' underlying type may not be specified here");
saved = ctx.ctype;
token = declarator(token, &ctx);
diff --git a/symbol.c b/symbol.c
index fc11e1bbcec3..28fa12352569 100644
--- a/symbol.c
+++ b/symbol.c
@@ -584,12 +584,21 @@ static struct symbol *examine_enum_type(struct symbol *sym)
struct symbol *base_type = examine_base_type(sym);
sym->ctype.modifiers |= (base_type->ctype.modifiers & MOD_SIGNEDNESS);
- sym->bit_size = bits_in_enum;
- if (base_type->bit_size > sym->bit_size)
- sym->bit_size = base_type->bit_size;
- sym->ctype.alignment = enum_alignment;
- if (base_type->ctype.alignment > sym->ctype.alignment)
+ if (sym->fixed_type) {
+ if (base_type == &bool_ctype)
+ sym->bit_size = bits_to_bytes(bits_in_bool) * bits_in_char;
+ else
+ sym->bit_size = base_type->bit_size;
sym->ctype.alignment = base_type->ctype.alignment;
+ } else {
+ sym->bit_size = bits_in_enum;
+ if (base_type->bit_size > sym->bit_size)
+ sym->bit_size = base_type->bit_size;
+ sym->ctype.alignment = enum_alignment;
+ if (base_type->ctype.alignment > sym->ctype.alignment)
+ sym->ctype.alignment = base_type->ctype.alignment;
+ }
+ sym->rank = base_type->rank;
return sym;
}
diff --git a/symbol.h b/symbol.h
index f8556fdf3b86..8035d9d21b3c 100644
--- a/symbol.h
+++ b/symbol.h
@@ -120,6 +120,8 @@ struct decl_state {
unsigned char is_constexpr;
unsigned char designated_init;
unsigned char transparent_union;
+ unsigned char opaque_fixed_enum;
+ struct position opaque_fixed_enum_pos;
};
struct pseudo;
@@ -186,22 +188,14 @@ struct symbol {
struct /* NS_SYMBOL */ {
unsigned long offset;
int bit_size;
- unsigned int bit_offset:8,
- bogus_linear:1,
- variadic:1,
- initialized:1,
- examined:1,
- expanding:1,
- evaluated:1,
- has_flex_array:1,
- string:1,
- designated_init:1,
- forced_arg:1,
- accessed:1,
- builtin:1,
- torename:1,
- packed:1,
- transparent_union:1;
+ unsigned int bit_offset : 8, bogus_linear : 1,
+ variadic : 1, initialized : 1, examined : 1,
+ expanding : 1, evaluated : 1,
+ has_flex_array : 1, string : 1,
+ designated_init : 1, forced_arg : 1,
+ accessed : 1, builtin : 1, torename : 1,
+ packed : 1, transparent_union : 1,
+ fixed_type : 1;
unsigned int has_alignas : 1;
unsigned int underspecified : 1;
unsigned int c23_auto : 1;
@@ -435,6 +429,13 @@ static inline int is_func_type(struct symbol *type)
return type->type == SYM_FN;
}
+static inline struct symbol *cbase_type(struct symbol *sym)
+{
+ while (sym && sym->type == SYM_NODE)
+ sym = sym->ctype.base_type;
+ return sym;
+}
+
static inline int is_array_type(struct symbol *type)
{
if (type->type == SYM_NODE)
@@ -484,9 +485,8 @@ static inline int is_void_type(struct symbol *type)
static inline int is_bool_type(struct symbol *type)
{
- if (type->type == SYM_NODE)
- type = type->ctype.base_type;
- if (type->type == SYM_BITFIELD)
+ while (type && (type->type == SYM_NODE || type->type == SYM_ENUM ||
+ type->type == SYM_BITFIELD))
type = type->ctype.base_type;
return type == &bool_ctype;
}
diff --git a/validation/c23-enum-type-cast.c b/validation/c23-enum-type-cast.c
new file mode 100644
index 000000000000..447a9882ca3e
--- /dev/null
+++ b/validation/c23-enum-type-cast.c
@@ -0,0 +1,70 @@
+/*
+ * Check enumerator types and explicit/implicit casts involving enums
+ * with fixed underlying types.
+ */
+
+enum E1 : unsigned long long {
+ A = 0,
+ B = 1ULL << 40,
+};
+
+_Static_assert(_Generic(A, unsigned long long: 1, default: 0) == 1, "A type");
+_Static_assert(_Generic(B, unsigned long long: 1, default: 0) == 1, "B type");
+_Static_assert(sizeof(A) == sizeof(unsigned long long), "A size");
+_Static_assert(sizeof(B) == sizeof(unsigned long long), "B size");
+_Static_assert(A == 0, "A value");
+_Static_assert(B == (1ULL << 40), "B value");
+_Static_assert((enum E1)(1ULL << 40) == B, "cast to E1");
+_Static_assert((unsigned long long)B == (1ULL << 40), "cast from B");
+_Static_assert((short)A == 0, "cast A to short");
+_Static_assert((unsigned short)B == 0, "narrow cast B to ushort");
+
+enum E2 : long long {
+ C = -1LL,
+ D = 1LL << 40,
+};
+
+_Static_assert(_Generic(C, long long: 1, default: 0) == 1, "C type");
+_Static_assert(_Generic(D, long long: 1, default: 0) == 1, "D type");
+_Static_assert(sizeof(C) == sizeof(long long), "C size");
+_Static_assert(sizeof(D) == sizeof(long long), "D size");
+_Static_assert((enum E2)-1 == C, "cast -1 to E2");
+
+enum U8 : unsigned char {
+ U8_ZERO = 0,
+};
+
+_Static_assert((enum U8)200 == 200, "narrow u8 cast");
+_Static_assert((enum U8)258 == 2, "out-of-range u8 cast wraps");
+_Static_assert((enum U8)-1 == 255, "negative u8 cast wraps");
+_Static_assert(_Generic((enum U8)200, enum U8: 1, int: 0, default: 0) == 1,
+ "cast yields enum U8");
+_Static_assert(_Generic((enum U8)200, unsigned char: 1, int: 0, default: 0) == 1,
+ "enum U8 compatible with unsigned char");
+
+enum S8 : signed char {
+ S8_NEG = -1,
+};
+
+_Static_assert((enum S8)-128 == -128, "narrow s8 cast");
+_Static_assert(_Generic((enum S8)0, signed char: 1, int: 0, default: 0) == 1,
+ "enum S8 compatible with signed char");
+
+enum B : bool {
+ B_FALSE = false,
+ B_TRUE = true,
+};
+
+_Static_assert((enum B)2 == 1, "bool enum cast from 2");
+_Static_assert((enum B)-5 == 1, "bool enum cast from -5");
+_Static_assert((enum B)0 == 0, "bool enum cast from 0");
+
+/*
+ * check-name: C23 enum underlying type casting
+ * check-command: sparse -std=c23 $file
+ *
+ * check-error-start
+c23-enum-type-cast.c:20:32: warning: cast truncates bits from constant value (10000000000 becomes 0)
+c23-enum-type-cast.c:38:25: warning: cast truncates bits from constant value (102 becomes 2)
+ * check-error-end
+ */
diff --git a/validation/c23-enum-type-error.c b/validation/c23-enum-type-error.c
new file mode 100644
index 000000000000..3d2a9d5ec6a4
--- /dev/null
+++ b/validation/c23-enum-type-error.c
@@ -0,0 +1,40 @@
+enum E1 : char { A = 300 };
+enum E2 : unsigned char { B = -1 };
+enum E3 : unsigned char { C = 255, D };
+enum E4 : short;
+enum E4 : int;
+enum E5;
+enum E5 : short;
+enum E6 : short;
+enum E6 { F };
+enum E7 : float;
+enum E8 : bool { B0 = 0, B1, B2 };
+enum E9 : unsigned long long { UMAX = ~0ULL, UOVER };
+enum E10 : int bad_var;
+typedef enum E11 : short e11_t;
+void bad_enum_param(enum E12 : int p);
+int bad_enum_sizeof = sizeof(enum E13 : int);
+enum E14 : char;
+enum E14 : signed char;
+
+/*
+ * check-name: C23 enum with fixed underlying type errors
+ * check-command: sparse -std=c23 $file
+ *
+ * check-error-start
+c23-enum-type-error.c:1:18: error: enumerator value outside the range of underlying type
+c23-enum-type-error.c:2:27: error: enumerator value outside the range of underlying type
+c23-enum-type-error.c:3:36: error: enumerator value outside the range of underlying type
+c23-enum-type-error.c:5:14: error: 'enum' underlying type incompatible with previous declaration
+c23-enum-type-error.c:7:16: error: 'enum' declared both with and without fixed underlying type
+c23-enum-type-error.c:9:9: error: 'enum' declared with but defined without fixed underlying type
+c23-enum-type-error.c:10:16: error: invalid 'enum' underlying type
+c23-enum-type-error.c:11:30: error: enumerator value outside the range of underlying type
+c23-enum-type-error.c:12:46: error: enumerator value outside the range of underlying type
+c23-enum-type-error.c:13:10: error: 'enum' underlying type may not be specified here
+c23-enum-type-error.c:14:18: error: 'enum' underlying type may not be specified here
+c23-enum-type-error.c:15:30: error: 'enum' underlying type may not be specified here
+c23-enum-type-error.c:16:39: error: 'enum' underlying type may not be specified here
+c23-enum-type-error.c:18:23: error: 'enum' underlying type incompatible with previous declaration
+ * check-error-end
+ */
diff --git a/validation/c23-enum-type.c b/validation/c23-enum-type.c
new file mode 100644
index 000000000000..03571bc99e06
--- /dev/null
+++ b/validation/c23-enum-type.c
@@ -0,0 +1,116 @@
+enum E_char : char {
+ A = 0,
+ B,
+};
+static_assert(sizeof(enum E_char) == sizeof(char), "sizeof E_char");
+static_assert(sizeof(A) == sizeof(char), "sizeof A");
+static_assert(A == 0, "val A");
+static_assert(B == 1, "val B");
+
+enum E_short : short;
+static_assert(sizeof(enum E_short) == sizeof(short), "sizeof E_short fwd");
+enum E_short : short {
+ C = 10,
+ D,
+};
+static_assert(sizeof(enum E_short) == sizeof(short), "sizeof E_short def");
+static_assert(sizeof(C) == sizeof(short), "sizeof C");
+static_assert(C == 10, "val C");
+static_assert(D == 11, "val D");
+
+enum : unsigned long long {
+ ULL1 = 0x100000000ULL,
+ ULL2,
+};
+static_assert(sizeof(ULL1) == sizeof(unsigned long long), "sizeof ULL1");
+static_assert(ULL1 == 0x100000000ULL, "val ULL1");
+static_assert(ULL2 == 0x100000001ULL, "val ULL2");
+
+enum E_redecl : int;
+enum E_redecl : signed int;
+enum E_redecl : int {
+ R1 = 100,
+ R2 = 200,
+};
+static_assert(sizeof(enum E_redecl) == sizeof(int), "sizeof E_redecl");
+static_assert(R1 == 100, "val R1");
+static_assert(R2 == 200, "val R2");
+
+enum E_compat : signed short;
+enum E_compat : short {
+ EC1 = -10,
+};
+static_assert(sizeof(enum E_compat) == sizeof(short), "sizeof E_compat");
+static_assert(EC1 == -10, "val EC1");
+
+enum E_signed : signed char {
+ NEG = -5,
+ NEG_PLUS_ONE,
+};
+static_assert(sizeof(enum E_signed) == 1, "sizeof E_signed");
+static_assert(NEG == -5, "val NEG");
+static_assert(NEG_PLUS_ONE == -4, "val NEG_PLUS_ONE");
+
+enum E_uchar : unsigned char {
+ MAX_BYTE = 255,
+};
+static_assert(sizeof(enum E_uchar) == 1, "sizeof E_uchar");
+static_assert(MAX_BYTE == 255, "val MAX_BYTE");
+
+typedef unsigned short uint16_t;
+enum E_typedef : uint16_t {
+ T1 = 65535,
+};
+static_assert(sizeof(enum E_typedef) == sizeof(uint16_t), "sizeof E_typedef");
+static_assert(T1 == 65535, "val T1");
+
+enum E_bool : bool {
+ EB_FALSE,
+ EB_TRUE,
+};
+static_assert(sizeof(enum E_bool) == sizeof(bool), "sizeof E_bool");
+static_assert(EB_FALSE == 0 && EB_TRUE == 1, "val E_bool");
+
+enum E_qual : const unsigned int {
+ EQ1 = 42,
+};
+static_assert(_Generic(EQ1, unsigned int: 1, default: 0) == 1, "EQ1 type");
+
+enum E_brace : unsigned long long {
+ EB_A = 1,
+ EB_B = _Generic(EB_A, unsigned long long: 2, default: 0),
+};
+static_assert(EB_B == 2, "inside-brace fixed type");
+static_assert(_Generic(EB_A, unsigned long long: 1, default: 0) == 1,
+ "after-brace fixed type");
+
+struct E_unnamed_bf {
+ enum E_uchar : 8;
+ int x;
+};
+static_assert(sizeof(struct E_unnamed_bf) >= sizeof(int), "unnamed enum bitfield");
+
+enum E_unfixed_fit {
+ UF_A = 1U,
+};
+static_assert(_Generic(UF_A, int: 1, default: 0) == 1, "unfixed fits int");
+
+enum E_unfixed_wide {
+ UW_A = 1,
+ UW_B = ~0U,
+};
+static_assert(_Generic(UW_A, unsigned int: 1, default: 0) == 1,
+ "C23 unfixed wide enum gives all members underlying type");
+static_assert(_Generic(UW_B, unsigned int: 1, default: 0) == 1,
+ "C23 unfixed wide enum second member");
+
+enum FwdFixed : int;
+static int add_fwd_fixed_enum(enum FwdFixed x)
+{
+ return x + 1;
+}
+
+/*
+ * check-name: C23 enumerations with fixed underlying types
+ * check-command: sparse -std=c23 $file
+ */
next prev parent reply other threads:[~2026-11-09 20:33 UTC|newest]
Thread overview: 74+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-09 20:31 Bart Van Assche
2026-11-09 20:31 ` [PATCH 01/73] Makefile: Pass '--' to rm in clean target to handle dash-prefixed files Bart Van Assche
2026-11-09 20:31 ` [PATCH 02/73] sparse-llvm: Use LLVMCreateBuilderForModule() with LLVM 24 and later Bart Van Assche
2026-11-09 20:31 ` [PATCH 03/73] typeof_unqual: Strip qualifiers and address space from types and arrays Bart Van Assche
2026-11-09 20:31 ` [PATCH 04/73] bitfield: Pack consecutive _Bool bitfields into byte containers Bart Van Assche
2026-11-09 20:31 ` [PATCH 05/73] allocate: Harden slab allocation bounds, zeroing, and mmap checks Bart Van Assche
2026-11-09 20:31 ` [PATCH 06/73] tokenize: Support 4096-byte identifiers and fix escape bounds Bart Van Assche
2026-11-09 20:31 ` [PATCH 07/73] pre-process: Fix macro expansion and include directive handling Bart Van Assche
2026-11-09 20:31 ` [PATCH 08/73] options, lib: Harden warning buffers and command-line option parsing Bart Van Assche
2026-11-09 20:31 ` [PATCH 09/73] builtin: Fix atomic builtin type checking and object_size bounds Bart Van Assche
2026-11-09 20:31 ` [PATCH 10/73] parse, show-parse: Fix attribute propagation, enum overflow, and bounds Bart Van Assche
2026-11-09 20:31 ` [PATCH 11/73] target-openrisc: Set target_openrisc.mach to MACH_OPENRISC Bart Van Assche
2026-11-09 20:31 ` [PATCH 12/73] evaluate: Fix pointer arithmetic, incomplete types, and qualifiers Bart Van Assche
2026-11-09 20:31 ` [PATCH 13/73] expand: Fix static initializer folding and floating-point casts Bart Van Assche
2026-11-09 20:32 ` [PATCH 14/73] inline: Copy union cast types and VLA bounds when inlining Bart Van Assche
2026-11-09 20:32 ` [PATCH 15/73] linearize, flow: Fix asm operands, switch case bounds, and DSE Bart Van Assche
2026-11-09 20:32 ` [PATCH 16/73] memops, ssa: Walk parent insns in reverse and skip mixed-size SSA Bart Van Assche
2026-11-09 20:32 ` [PATCH 17/73] simplify: Prevent overflow on LLONG_MIN negation and fix simplifications Bart Van Assche
2026-11-09 20:32 ` [PATCH 18/73] sparse: Gate context failure warning on Wcontext and catch underflow Bart Van Assche
2026-11-09 20:32 ` [PATCH 19/73] ast-model: Return AST_N_COLUMNS and inspect child nodes lazily Bart Van Assche
2026-11-09 20:32 ` [PATCH 20/73] c2xml: Guard against recursive symbols and free xmlGetProp() string Bart Van Assche
2026-11-09 20:32 ` [PATCH 21/73] compile-i386: Fix buffer bounds, unsigned '<=' codegen, and null checks Bart Van Assche
2026-11-09 20:32 ` [PATCH 22/73] ctags: Fix line comparison order and handle SYM_RESTRICT and SYM_LABEL Bart Van Assche
2026-11-09 20:32 ` [PATCH 23/73] dissect: Unwrap array element type via base_type() Bart Van Assche
2026-11-09 20:32 ` [PATCH 24/73] example: Fix show_op() rotating buffer dimensions Bart Van Assche
2026-11-09 20:32 ` [PATCH 25/73] graph: Check PSEUDO_SYM before dereferencing insn->func->sym Bart Van Assche
2026-11-09 20:32 ` [PATCH 26/73] scheck: Fix slice() upper bit index calculation Bart Van Assche
2026-11-09 20:32 ` [PATCH 27/73] sparse-llvm: Support arbitrary struct member counts and fix operand types Bart Van Assche
2026-11-09 20:32 ` [PATCH 28/73] inline: Duplicate generic expressions instead of evaluating them Bart Van Assche
2026-11-09 20:32 ` [PATCH 29/73] lib: Avoid mutating fmt before va_start() in warning() Bart Van Assche
2026-11-09 20:32 ` [PATCH 30/73] cse, canon: Cast pointers to uintptr_t before relational comparison Bart Van Assche
2026-11-09 20:32 ` [PATCH 31/73] context: Add "conditional context increment" model Bart Van Assche
2026-11-09 20:32 ` [PATCH 32/73] expand: Simplify signed constants early Bart Van Assche
2026-11-09 20:32 ` [PATCH 33/73] bitwise: Allow all-bits-set as a restricted value Bart Van Assche
2026-11-09 20:32 ` [PATCH 34/73] bitwise: Allow restricted ordered compares with unrestricted values Bart Van Assche
2026-11-09 20:32 ` [PATCH 35/73] bitwise: Keep unrestricted values unrestricted after a cast Bart Van Assche
2026-11-09 20:32 ` [PATCH 36/73] bitwise: Don't remove MOD_SPECIFIER from 'bitwise' types Bart Van Assche
2026-11-09 20:32 ` [PATCH 37/73] bitwise: Allow both regular casts and forced casts for restricted values Bart Van Assche
2026-11-09 20:32 ` [PATCH 38/73] clang-format: Add .clang-format configuration file Bart Van Assche
2026-11-09 20:32 ` [PATCH 39/73] options: Add support for -std=c23, -std=c2x and -std=gnu23 Bart Van Assche
2026-11-09 20:32 ` [PATCH 40/73] preprocessor: Add support for #elifdef and #elifndef Bart Van Assche
2026-11-09 20:32 ` [PATCH 41/73] preprocessor: Add support for __has_include and __has_include_next Bart Van Assche
2026-11-09 20:32 ` [PATCH 42/73] c23: Add support for static_assert, alignas, alignof, thread_local Bart Van Assche
2026-11-09 20:32 ` [PATCH 43/73] c23: Add support for bool, true, and false keywords Bart Van Assche
2026-11-09 20:32 ` [PATCH 44/73] c23: Add support for nullptr and nullptr_t Bart Van Assche
2026-11-09 20:32 ` [PATCH 45/73] c23: Add support for digit separators in numeric literals Bart Van Assche
2026-11-09 20:32 ` [PATCH 46/73] c23: Support unnamed parameters in function definitions Bart Van Assche
2026-11-09 20:32 ` [PATCH 47/73] c23: Support variadic functions without named parameters Bart Van Assche
2026-11-09 20:32 ` [PATCH 48/73] c23: Support labels before declarations and at block ends Bart Van Assche
2026-11-09 20:32 ` [PATCH 49/73] c23: Support auto type inference Bart Van Assche
2026-11-09 20:32 ` [PATCH 50/73] c23: Support constexpr object specifier Bart Van Assche
2026-11-09 20:32 ` Bart Van Assche [this message]
2026-11-09 20:32 ` [PATCH 52/73] c23: Support standard [[]] attribute syntax Bart Van Assche
2026-11-09 20:32 ` [PATCH 53/73] preprocessor: Add support for __has_c_attribute Bart Van Assche
2026-11-09 20:32 ` [PATCH 54/73] c23: Support empty initializer braces {} Bart Van Assche
2026-11-09 20:32 ` [PATCH 55/73] preprocessor: Add support for __has_embed Bart Van Assche
2026-11-09 20:32 ` [PATCH 56/73] preprocessor: Add support for #embed directive Bart Van Assche
2026-11-09 20:32 ` [PATCH 57/73] c23: Add support for u8 character constants and string literals Bart Van Assche
2026-11-09 20:32 ` [PATCH 58/73] preprocessor: Add support for __VA_OPT__ Bart Van Assche
2026-11-09 20:32 ` [PATCH 59/73] c23: Treat empty parameter list () as (void) and reject K&R functions Bart Van Assche
2026-11-09 20:32 ` [PATCH 60/73] c23: Support storage-class specifiers in compound literals Bart Van Assche
2026-11-09 20:32 ` [PATCH 61/73] c23: Add support for _BitInt(N) Bart Van Assche
2026-11-09 20:32 ` [PATCH 62/73] enum: Support machine mode attributes on enumeration types Bart Van Assche
2026-11-09 20:32 ` [PATCH 63/73] enum: Support __attribute__((packed)) " Bart Van Assche
2026-11-09 20:32 ` [PATCH 64/73] expand: Ignore top-level non-array address space in compare_types() Bart Van Assche
2026-11-09 20:32 ` [PATCH 65/73] builtin: Support __builtin_infer_alloc_token() as a size_t ICE Bart Van Assche
2026-11-09 20:32 ` [PATCH 66/73] builtin: Add support for Linux kernel compiler builtins Bart Van Assche
2026-11-09 20:32 ` [PATCH 67/73] builtin: Add support for __builtin_has_attribute() Bart Van Assche
2026-11-09 20:32 ` [PATCH 68/73] cleanup: Support __attribute__((cleanup(...))) in linearize and -Wcontext Bart Van Assche
2026-11-09 20:32 ` [PATCH 69/73] builtin: Do not evaluate __builtin_clz(0) and __builtin_ctz(0) Bart Van Assche
2026-11-09 20:32 ` [PATCH 70/73] pre-process: Support long token and string expansions dynamically Bart Van Assche
2026-11-09 20:32 ` [PATCH 71/73] x86: Undefine conflicting bitness macros when parsing -m flags Bart Van Assche
2026-11-09 20:32 ` [PATCH 72/73] evaluate: Accept conditional expressions with only one void side Bart Van Assche
2026-11-09 20:32 ` [PATCH 73/73] ci: Add Linux kernel GitHub Actions workflow and build script Bart Van Assche
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261109203259.362845-52-bvanassche@acm.org \
--to=bvanassche@acm.org \
--cc=linux-sparse@vger.kernel.org \
--cc=torvalds@linux-foundation.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox