kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 9f6c53254dc71957f285663f22f230c6e50ff64a
parent c69c97e24cedb77bac58398c95fec87a0c06328b
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri, 12 Jun 2026 09:43:01 -0700

feat(c): support C23 enum with a fixed underlying type

Parse `enum [tag] : T` (C23 §6.7.2.2) in both the defining and forward-declaring
forms, adopting T as the enum's representation type (so sizeof matches). A bodyless
`enum E : T;` is recorded as an incomplete forward declaration that a later
`enum E : T { ... }` completes. Needed to compile modern SDK headers (e.g.
`typedef enum __enum_options : uint64_t { ... }`).

Diffstat:
Mlang/c/parse/parse_type.c | 28++++++++++++++++++++++++++--
Atest/parse/cases/6_7_2_2_01_enum_fixed_underlying_type.c | 19+++++++++++++++++++
Atest/parse/cases/6_7_2_2_01_enum_fixed_underlying_type.expected | 1+
3 files changed, 46 insertions(+), 2 deletions(-)

diff --git a/lang/c/parse/parse_type.c b/lang/c/parse/parse_type.c @@ -1143,6 +1143,16 @@ const Type* parse_enum(Parser* p, Attr** anon_attrs_out) { tag_name = p->cur.v.ident; advance(p); } + /* C23 §6.7.2.2: an optional fixed underlying type — `enum [tag] : T` — + * may follow the tag (or `enum`), in both the defining and the + * forward-declaring forms. T is an integer type-specifier list; we reuse + * the type-name parser and adopt T as the enum's representation type. */ + const Type* underlying = ty_int(p); + int has_fixed_type = 0; + if (accept_punct(p, ':')) { + underlying = parse_type_name(p); + has_fixed_type = 1; + } int has_body = is_punct(&p->cur, '{'); if (!has_body && tag_name == 0) { perr(p, "expected tag name or '{' after enum"); @@ -1156,10 +1166,24 @@ const Type* parse_enum(Parser* p, Attr** anon_attrs_out) { if (e) { perr(p, "tag redeclared with wrong kind"); } - perr(p, "enum tag declared without definition"); + if (!has_fixed_type) { + perr(p, "enum tag declared without definition"); + } + /* C23 `enum E : T;` with no enumerators is a forward declaration carrying a + * fixed underlying type. Record an *incomplete* tag (so the type knows T) + * that a later `enum E : T { ... }` can complete; a redefinition is only + * diagnosed once an enumerator body actually arrives. */ + { + TagId ftid = type_tag_new(p->pool, TAG_ENUM, tag_name, tag_loc); + const Type* fet = type_enum(p->pool, ftid, tag_name, underlying); + TagEntry* te = + tag_define(p, tag_name, TAG_ENUM, (Type*)fet, /*complete=*/0); + attr_list_append(&te->attrs, rec_attrs); + return fet; + } } TagId tid = type_tag_new(p->pool, TAG_ENUM, tag_name, tag_loc); - const Type* et = type_enum(p->pool, tid, tag_name, ty_int(p)); + const Type* et = type_enum(p->pool, tid, tag_name, underlying); expect_punct(p, '{', "'{'"); i64 next_val = 0; for (;;) { diff --git a/test/parse/cases/6_7_2_2_01_enum_fixed_underlying_type.c b/test/parse/cases/6_7_2_2_01_enum_fixed_underlying_type.c @@ -0,0 +1,19 @@ +/* C23 §6.7.2.2: enum with a fixed underlying type — `enum E : T { ... }`. + * The underlying type sets the enum's representation (and thus sizeof); the + * enumerator values and a char-backed sizeof are checked. No sysroot needed + * (builtin integer types only), so this runs on every lane. */ +enum Color : unsigned char { RED = 1, GREEN = 2, BLUE = 39 }; +enum Wide : long long { LO = 40 }; + +/* Forward `enum E : T;` is a complete type in C23 even with no body. */ +enum Fwd : int; +enum Fwd : int { F0 = 7 }; + +int test_main(void) { + enum Color c = BLUE; /* 39 */ + enum Wide w = LO; /* 40 */ + return (int)c /* 39 */ + + (w == 40) /* + 1 */ + + (sizeof(enum Color) == 1) /* + 1 (char-backed) */ + + (F0 == 7) - (sizeof(enum Wide) != 8); /* + 1 - 0 */ +} diff --git a/test/parse/cases/6_7_2_2_01_enum_fixed_underlying_type.expected b/test/parse/cases/6_7_2_2_01_enum_fixed_underlying_type.expected @@ -0,0 +1 @@ +42