kit

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

commit 1f72786aa4fc1d69eb8c0742efcd5c831b1ce3d9
parent 78a82037e8216feafcc01711a22b21322ac64164
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sun, 10 May 2026 11:37:01 -0700

parse: Phase 8 — qualifiers, alignment, typedefs

- typedef binds via SEK_TYPEDEF; parse_decl_specs consumes typedef-names
  as type specs; file-scope typedef goes through parse_declarator_full
  so compound targets (funcptr, array, qualified, struct) round-trip
- _Atomic(T) parses as a type-spec form (returns Q_ATOMIC-qualified T);
  bare _Atomic stays a qualifier
- _Alignas(N|type) recorded on DeclSpecs.align; threaded through to
  make_local_aligned and define_static_object (strictest wins)
- inline accepted as a function specifier (DF_INLINE)
- _Static_assert at file and block scope; eval_const_int grew
  comparisons (< > <= >= == !=), sizeof(type-name), _Alignof(type-name),
  and integer casts so the §6.7.10 corpus bodies lower
- cg_convert: pointer↔integer reinterprets piggy-back on int↔int
  (same-size retag, narrow → TRUNC, widen → ZEXT) so the alignas test's
  (unsigned long)buf cast no longer hits the BITCAST same-class gap

Diffstat:
Mdoc/parser-status.md | 63++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Msrc/cg/cg.c | 16++++++++++++----
Msrc/parse/parse.c | 258+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mtest/parse/CORPUS.md | 20++++++++++----------
4 files changed, 316 insertions(+), 41 deletions(-)

diff --git a/doc/parser-status.md b/doc/parser-status.md @@ -336,18 +336,63 @@ without an implicit conversion at store time. --- -## Phase 8 — Qualifiers, alignment, typedefs ⬜ +## Phase 8 — Qualifiers, alignment, typedefs ✅ Remaining declaration-side features. -- [ ] `_Atomic` qualifier (parse + plumb to cg) -- [ ] `_Alignas(T)` and `_Alignas(N)` on objects -- [ ] `inline` (header-only definitions) -- [ ] `typedef` (already partially landed; promote) -- [ ] Compound typedef targets (struct, function pointer, array) -- [ ] `_Static_assert` at file and block scope - -Unlocks: `6_7_3_05`, `6_7_5_*`, `6_7_8_*`, `6_7_10_*`. +- [x] `_Atomic` qualifier (parse + plumb to cg) — `_Atomic int` is a + qualified int (Q_ATOMIC); `_Atomic(T)` parses as the type-spec + form and yields the same qualified type. +- [x] `_Alignas(T)` and `_Alignas(N)` on objects — strictest alignment + override carried on `DeclSpecs.align`, applied to both frame + slots (`make_local_aligned`) and static-storage objects + (`define_static_object`). +- [x] `inline` (header-only definitions) — recognized as a function + specifier and recorded via `DF_INLINE` on `DeclSpecs.flags`. The + corpus row only exercises `static inline`, which already lowers + as a regular static function; lazy-emission for non-static + inline definitions waits until a row needs it. +- [x] `typedef` (already partially landed; promote) — `KW_TYPEDEF` in + decl-specs now selects `DS_TYPEDEF`; both + `parse_init_declarator` (block scope) and `parse_external_decl` + (file scope) bind the declarator name as `SEK_TYPEDEF` instead + of allocating storage. `parse_decl_specs` consumes a typedef-name + as a type specifier when no other type spec is in flight, and + `starts_type_name` reports true for typedef-names so the + cast/sizeof/_Alignof paths recognize them too. +- [x] Compound typedef targets (struct, function pointer, array) — + file-scope typedef goes through `parse_declarator_full` so + `typedef int (*FP)(int)` and `typedef int A[3]` round-trip; the + inner-paren disambiguator in `parse_declarator_full` peeks the + symbol table to distinguish `(declarator)` from `(typedef-name)` + (function-suffix) cases. +- [x] `_Static_assert` at file and block scope — new + `parse_static_assert` consumes `(expr , "msg") ;` and panics with + the message when the expression evaluates to zero. `eval_const_int` + grew comparison operators (`< > <= >= == !=`), `sizeof(type-name)`, + `_Alignof(type-name)`, and casts `(T)expr` so the §6.7.10 corpus + bodies (`sizeof(int) >= 2`, `1+1 == 2`) lower as constants. + +Phase 8 also added: + - `cg_convert` now treats pointer↔integer reinterprets the same way + as int↔int: same-size is a retag, narrowing is `CV_TRUNC`, + widening is `CV_ZEXT`. Without this the `_Alignas` corpus row + (`(unsigned long)buf & 15`) hit "aarch64 convert BITCAST: + same-class not yet supported" — the aarch64 backend only knows + INT↔FP bitcasts, and same-class GPR↔GPR reinterprets don't need an + instruction. + - `DeclSpecs` carries a new `align` field. Parsers that own the + declarator-to-storage step (`parse_init_declarator`, + `parse_external_decl`) thread it into `make_local_aligned` / + `define_static_object`; the strictest `_Alignas` argument wins + against the natural type alignment (no narrowing). + +Unlocks (status as landed): `6_7_01_typedef` ★, `6_7_3_05_atomic` ★ (was +already passing as a qualifier; promoted to deliberate after the +`_Atomic(T)` form landed), `6_7_4_01_inline` ★, `6_7_5_01–02` ★, +`6_7_8_01–04` ★, `6_7_10_01–02` ★, plus the `cases_err/` +`6_7_10_static_assert_fail` row which now hits the new failure +diagnostic. --- diff --git a/src/cg/cg.c b/src/cg/cg.c @@ -1025,14 +1025,22 @@ void cg_convert(CG* g, const Type* dst_ty) { u32 s_sz = sty ? abi_sizeof(g->abi, sty) : 0; u32 d_sz = dst_ty ? abi_sizeof(g->abi, dst_ty) : 0; int s_signed = sty ? abi_type_info(g->abi, sty).signed_ : 0; - if (s_int && d_int) { + int s_ptr = type_is_ptr(sty); + int d_ptr = type_is_ptr(dst_ty); + /* Pointers are scalar GPR-class values that convert to/from integers + * the same way an unsigned of equal width would: same-size is a + * retag, narrowing is a TRUNC, widening is a ZEXT. Treat them as int + * for the purposes of selecting a ConvKind. */ + int s_int_or_ptr = s_int || s_ptr; + int d_int_or_ptr = d_int || d_ptr; + if (s_int_or_ptr && d_int_or_ptr) { if (d_sz < s_sz) { ck = CV_TRUNC; } else if (d_sz > s_sz) { - ck = s_signed ? CV_SEXT : CV_ZEXT; + ck = (s_int && s_signed) ? CV_SEXT : CV_ZEXT; } else { - /* Same-size integer reinterpret (e.g. signed↔unsigned). The bit - * pattern is unchanged; just retag the C type and push back. */ + /* Same-size reinterpret (signed↔unsigned, ptr↔int, ptr↔ptr). The + * bit pattern is unchanged; just retag the C type and push back. */ v.type = dst_ty; v.op.type = dst_ty; push(g, v); diff --git a/src/parse/parse.c b/src/parse/parse.c @@ -536,7 +536,8 @@ typedef struct DeclSpecs { const Type* type; DeclStorage storage; u32 flags; /* DeclFlag */ - u16 quals; /* TypeQual bits seen in the decl-spec list */ + u16 quals; /* TypeQual bits seen in the decl-spec list */ + u32 align; /* explicit alignment from `_Alignas`; 0 if none */ } DeclSpecs; static int parse_decl_specs(Parser* p, DeclSpecs* out); @@ -549,6 +550,7 @@ static const Type* parse_declarator_full(Parser* p, const Type* base, int allow_abstract, Sym* name_out, SrcLoc* loc_out); static int starts_type_name(const Parser* p, const Tok* t); +static const Type* parse_type_name(Parser* p); static i64 parse_int_literal(Parser* p, const Tok* t); static i64 decode_char_literal(Parser* p, const Tok* t); @@ -623,6 +625,7 @@ static int parse_decl_specs(Parser* p, DeclSpecs* out) { out->storage = DS_AUTO; out->flags = DF_NONE; out->quals = 0; + out->align = 0; loc = tok_loc(&p->cur); for (;;) { Tok t = p->cur; @@ -678,12 +681,64 @@ static int parse_decl_specs(Parser* p, DeclSpecs* out) { } else if (is_kw(p, &t, KW_RESTRICT)) { out->quals |= Q_RESTRICT; advance(p); seen = 1; } else if (is_kw(p, &t, KW_ATOMIC)) { + /* `_Atomic(type-name)` is a type specifier; bare `_Atomic` is a + * qualifier (§6.7.2.4). Disambiguate on the next token. */ + Tok n = peek1(p); + if (is_punct(&n, '(')) { + const Type* inner; + if (tagged_ty || acc.saw_explicit_type) { + perr(p, "conflicting type specifiers (_Atomic(T) mixed)"); + } + advance(p); /* `_Atomic` */ + advance(p); /* `(` */ + inner = parse_type_name(p); + expect_punct(p, ')', "')' after _Atomic type"); + tagged_ty = type_qualified(p->pool, inner, Q_ATOMIC); + acc.saw_explicit_type = 1; + seen = 1; + continue; + } out->quals |= Q_ATOMIC; advance(p); seen = 1; - } else if (is_kw(p, &t, KW_INLINE) || - is_kw(p, &t, KW_NORETURN) || is_kw(p, &t, KW_REGISTER) || + } else if (is_kw(p, &t, KW_TYPEDEF)) { + out->storage = DS_TYPEDEF; advance(p); seen = 1; + } else if (is_kw(p, &t, KW_ALIGNAS)) { + /* `_Alignas(N)` or `_Alignas(type-name)`. Either form yields a + * byte alignment that overrides the natural alignment of the + * declared object. Multiple specifiers take the strictest. */ + u32 a = 0; + advance(p); /* `_Alignas` */ + expect_punct(p, '(', "'(' after _Alignas"); + if (starts_type_name(p, &p->cur)) { + const Type* tn = parse_type_name(p); + a = abi_alignof(p->abi, tn); + } else { + i64 v = eval_const_int(p, tok_loc(&p->cur)); + if (v <= 0) perr(p, "_Alignas requires a positive alignment"); + a = (u32)v; + } + expect_punct(p, ')', "')' after _Alignas argument"); + if (a > out->align) out->align = a; + seen = 1; + } else if (is_kw(p, &t, KW_INLINE)) { + out->flags |= DF_INLINE; advance(p); seen = 1; + } else if (is_kw(p, &t, KW_NORETURN) || is_kw(p, &t, KW_REGISTER) || is_kw(p, &t, KW_AUTO)) { /* Recognized but currently no-op at this slice. */ advance(p); seen = 1; + } else if (!acc.saw_explicit_type && !tagged_ty && + t.kind == TOK_IDENT && ident_kw(p, t.v.ident) == KW_NONE) { + /* Typedef-name as a type specifier. Only consumed when no other + * type specifier has been seen — otherwise this IDENT is the + * declarator name. */ + SymEntry* e = scope_lookup(p, t.v.ident); + if (e && e->kind == SEK_TYPEDEF) { + tagged_ty = e->type; + acc.saw_explicit_type = 1; + advance(p); + seen = 1; + continue; + } + break; } else { break; } @@ -770,11 +825,33 @@ static i64 cexpr_shift(Parser* p, SrcLoc loc) { } return v; } -static i64 cexpr_band(Parser* p, SrcLoc loc) { +static i64 cexpr_rel(Parser* p, SrcLoc loc) { i64 v = cexpr_shift(p, loc); + for (;;) { + if (accept_punct(p, P_LE)) v = v <= cexpr_shift(p, loc); + else if (accept_punct(p, P_GE)) v = v >= cexpr_shift(p, loc); + else if (is_punct(&p->cur, '<')) { + advance(p); v = v < cexpr_shift(p, loc); + } else if (is_punct(&p->cur, '>')) { + advance(p); v = v > cexpr_shift(p, loc); + } else break; + } + return v; +} +static i64 cexpr_eq(Parser* p, SrcLoc loc) { + i64 v = cexpr_rel(p, loc); + for (;;) { + if (accept_punct(p, P_EQ)) v = (v == cexpr_rel(p, loc)); + else if (accept_punct(p, P_NE)) v = (v != cexpr_rel(p, loc)); + else break; + } + return v; +} +static i64 cexpr_band(Parser* p, SrcLoc loc) { + i64 v = cexpr_eq(p, loc); while (is_punct(&p->cur, '&') && !is_punct(&p->cur, P_AND)) { advance(p); - v = v & cexpr_shift(p, loc); + v = v & cexpr_eq(p, loc); } return v; } @@ -796,10 +873,55 @@ static i64 cexpr_unary(Parser* p, SrcLoc loc) { if (accept_punct(p, '-')) return -cexpr_unary(p, loc); if (accept_punct(p, '~')) return ~cexpr_unary(p, loc); if (accept_punct(p, '!')) return cexpr_unary(p, loc) ? 0 : 1; + if (accept_kw(p, KW_SIZEOF)) { + /* `sizeof(type-name)` is the only form we admit in a constant + * expression — a side-effecting operand would itself need full + * expression evaluation. */ + expect_punct(p, '(', "'(' after sizeof in constant expression"); + { + const Type* t = parse_type_name(p); + expect_punct(p, ')', "')' after sizeof type-name"); + return (i64)abi_sizeof(p->abi, t); + } + } + if (accept_kw(p, KW_ALIGNOF)) { + expect_punct(p, '(', "'(' after _Alignof in constant expression"); + { + const Type* t = parse_type_name(p); + expect_punct(p, ')', "')' after _Alignof type-name"); + return (i64)abi_alignof(p->abi, t); + } + } if (accept_punct(p, '(')) { - i64 v = cexpr_bor(p, loc); - expect_punct(p, ')', "')' in constant expression"); - return v; + /* `(type-name) cexpr` is an explicit cast in a constant context; for + * the §6.7.10 corpus the casts we see are integer→integer, so the + * mask-to-width is sufficient. Otherwise the parens enclose a + * sub-expression. */ + if (starts_type_name(p, &p->cur)) { + const Type* t = parse_type_name(p); + expect_punct(p, ')', "')' after cast type-name"); + { + i64 v = cexpr_unary(p, loc); + u32 sz = abi_sizeof(p->abi, t); + int is_signed = abi_type_info(p->abi, t).signed_; + if (sz < 8) { + u64 mask = (1ull << (sz * 8u)) - 1ull; + u64 uv = (u64)v & mask; + if (is_signed) { + u64 sign = 1ull << (sz * 8u - 1u); + v = (i64)((uv ^ sign) - sign); + } else { + v = (i64)uv; + } + } + return v; + } + } + { + i64 v = cexpr_bor(p, loc); + expect_punct(p, ')', "')' in constant expression"); + return v; + } } if (p->cur.kind == TOK_NUM) { i64 v = parse_int_literal(p, &p->cur); @@ -1112,7 +1234,14 @@ static int starts_type_name(const Parser* p, const Tok* t) { case KW_REGISTER: case KW_AUTO: case KW_TYPEDEF: + case KW_ALIGNAS: return 1; + case KW_NONE: { + /* Typedef-name. Cast away const for the lookup helper, which only + * reads scope state. */ + SymEntry* e = scope_lookup((Parser*)p, t->v.ident); + return e && e->kind == SEK_TYPEDEF; + } default: return 0; } @@ -2542,16 +2671,18 @@ static void parse_compound_stmt(Parser* p); /* Allocate a frame slot for a local variable of `type` and bind `name` * into the current scope. */ -static FrameSlot make_local(Parser* p, Sym name, const Type* type, SrcLoc loc) { +static FrameSlot make_local_aligned(Parser* p, Sym name, const Type* type, + SrcLoc loc, u32 align_override) { FrameSlotDesc fsd; FrameSlot s; SymEntry* e; + u32 nat = abi_alignof(p->abi, type); memset(&fsd, 0, sizeof fsd); fsd.type = type; fsd.name = name; fsd.loc = loc; fsd.size = abi_sizeof(p->abi, type); - fsd.align = abi_alignof(p->abi, type); + fsd.align = (align_override > nat) ? align_override : nat; fsd.kind = FS_LOCAL; fsd.flags = FSF_NONE; s = cg_local(p->cg, &fsd); @@ -2560,6 +2691,10 @@ static FrameSlot make_local(Parser* p, Sym name, const Type* type, SrcLoc loc) { return s; } +static FrameSlot make_local(Parser* p, Sym name, const Type* type, SrcLoc loc) { + return make_local_aligned(p, name, type, loc, 0); +} + /* Forward decls for declarator components. */ typedef enum DSuffKind { DS_ARRAY, DS_FUNC } DSuffKind; typedef struct ParamInfo ParamInfo; @@ -2723,7 +2858,11 @@ static const Type* parse_declarator_full(Parser* p, const Type* base, if (is_punct(&n, '*')) { is_inner = 1; } else if (n.kind == TOK_IDENT && ident_kw(p, n.v.ident) == KW_NONE) { - is_inner = 1; + /* Plain IDENT could be a declarator name OR a typedef-name (which + * makes the parens a function-parameter list). Disambiguate by + * peeking at the symbol table. */ + SymEntry* e = scope_lookup(p, n.v.ident); + if (!(e && e->kind == SEK_TYPEDEF)) is_inner = 1; } if (is_inner) { has_inner_parens = 1; @@ -3378,12 +3517,16 @@ static ObjSecId pick_object_section(Parser* p, u16 quals, int has_nonzero) { /* Define a static-storage object: allocate the byte buffer, parse the * (optional) initializer into it, route to .rodata / .data / .bss, and call - * obj_symbol_define. Used for both file-scope objects and static locals. */ + * obj_symbol_define. Used for both file-scope objects and static locals. + * `align_override` is the strictest `_Alignas` argument the declarator + * collected, or 0 for the natural type alignment. */ static void define_static_object(Parser* p, ObjSymId sym, const Type* var_ty, - u16 quals, int has_init, SrcLoc loc) { + u16 quals, int has_init, SrcLoc loc, + u32 align_override) { ObjBuilder* ob = decl_obj(p->decls); u32 size = abi_sizeof(p->abi, var_ty); u32 align = abi_alignof(p->abi, var_ty); + if (align_override > align) align = align_override; u8* buf = NULL; int has_nonzero = 0; ObjSecId override_sec; @@ -3517,6 +3660,22 @@ static void parse_init_declarator(Parser* p, const DeclSpecs* specs) { Sym name; const Type* var_ty = parse_declarator(p, specs->type, &name, &loc); + /* Typedef declaration: bind the name as SEK_TYPEDEF in the current + * scope so subsequent decl-spec sites can recognize it as a type + * specifier. No storage is allocated and an initializer is not + * permitted. */ + if (specs->storage == DS_TYPEDEF) { + if (is_punct(&p->cur, '=')) { + perr(p, "typedef declarator cannot have initializer"); + } + { + SymEntry* e = scope_define(p, name, SEK_TYPEDEF, var_ty); + (void)e; + } + (void)loc; + return; + } + /* Static-storage locals are promoted to a globally-visible symbol with * internal linkage; the local scope binds to that symbol so subsequent * uses load through cg_push_global. The variable's storage persists @@ -3541,7 +3700,8 @@ static void parse_init_declarator(Parser* p, const DeclSpecs* specs) { e = scope_define(p, name, SEK_GLOBAL, var_ty); e->v.sym = sym; has_init = accept_punct(p, '='); - define_static_object(p, sym, var_ty, specs->quals, has_init, loc); + define_static_object(p, sym, var_ty, specs->quals, has_init, loc, + specs->align); return; } @@ -3609,12 +3769,12 @@ static void parse_init_declarator(Parser* p, const DeclSpecs* specs) { * has to wait until after sizing, so move it inside this branch. */ advance(p); /* '=' */ var_ty = complete_incomplete_array(p, var_ty); - s = make_local(p, name, var_ty, loc); + s = make_local_aligned(p, name, var_ty, loc, specs->align); cg_set_loc(p->cg, loc); init_at(p, s, var_ty, 0, var_ty); return; } - s = make_local(p, name, var_ty, loc); + s = make_local_aligned(p, name, var_ty, loc, specs->align); if (accept_punct(p, '=')) { cg_set_loc(p->cg, loc); if (var_ty->kind == TY_ARRAY || var_ty->kind == TY_STRUCT || @@ -3952,6 +4112,36 @@ static void parse_switch_stmt(Parser* p) { cg_label_place(p->cg, L_end); } +/* `_Static_assert ( constant-expression , string-literal ) ;` (§6.7.10). + * The expression is evaluated at compile time; failure aborts parsing + * with a diagnostic that includes the user's message. The C11 spec + * requires the message; C2x makes it optional, but we follow C11 here. */ +static void parse_static_assert(Parser* p) { + SrcLoc loc = tok_loc(&p->cur); + i64 v; + if (!accept_kw(p, KW_STATIC_ASSERT)) { + perr(p, "expected _Static_assert"); + } + expect_punct(p, '(', "'(' after _Static_assert"); + v = eval_const_int(p, tok_loc(&p->cur)); + expect_punct(p, ',', "',' separating _Static_assert args"); + if (p->cur.kind != TOK_STR) { + perr(p, "expected string literal as _Static_assert message"); + } + { + Tok msg = p->cur; + advance(p); + expect_punct(p, ')', "')' after _Static_assert"); + expect_punct(p, ';', "';' after _Static_assert"); + if (!v) { + size_t mlen = 0; + const char* mstr = pool_str(p->pool, msg.spelling, &mlen); + compiler_panic(p->c, loc, "static assertion failed: %.*s", + (int)mlen, mstr ? mstr : ""); + } + } +} + static void parse_compound_stmt(Parser* p) { expect_punct(p, '{', "'{'"); scope_push(p); @@ -3962,6 +4152,10 @@ static void parse_compound_stmt(Parser* p) { advance(p); continue; } + if (is_kw(p, &p->cur, KW_STATIC_ASSERT)) { + parse_static_assert(p); + continue; + } { DeclSpecs specs; Tok save_tok = p->cur; /* nothing to roll back yet — accept reused below */ @@ -4277,6 +4471,28 @@ static void parse_external_decl(Parser* p) { * etc. The decl-specs registered the tag; nothing else to do. */ if (accept_punct(p, ';')) return; + /* `typedef` at file scope: bind one-or-more declarator names as + * SEK_TYPEDEF in the current (file) scope. Goes through + * parse_declarator_full so compound targets (`typedef int (*FP)(int)`, + * `typedef int A[3]`) lower correctly. */ + if (specs.storage == DS_TYPEDEF) { + for (;;) { + Sym tname = 0; + SrcLoc tloc = {0, 0, 0}; + const Type* tty = parse_declarator_full(p, specs.type, + /*allow_abstract=*/0, + &tname, &tloc); + if (is_punct(&p->cur, '=')) { + perr(p, "typedef declarator cannot have initializer"); + } + scope_define(p, tname, SEK_TYPEDEF, tty); + (void)tloc; + if (!accept_punct(p, ',')) break; + } + expect_punct(p, ';', "';' after typedef declaration"); + return; + } + /* Parse the declarator's pointer prefix and IDENT. Function and array * declarator suffixes are recognized inline below. */ base_ty = parse_pointer_layer(p, specs.type); @@ -4373,13 +4589,13 @@ static void parse_external_decl(Parser* p) { if (has_init) { advance(p); /* '=' */ define_static_object(p, sym, base_ty, specs.quals, /*has_init=*/1, - loc); + loc, specs.align); } else if (!is_pure_extern) { /* Tentative def: emit a BSS reservation now. End-of-TU coalescing of * multiple tentative defs into one is a Phase 4 follow-up; the * Phase 4 corpus only has a single tentative def per TU. */ define_static_object(p, sym, base_ty, specs.quals, /*has_init=*/0, - loc); + loc, specs.align); } (void)e; @@ -4403,12 +4619,18 @@ static void parse_external_decl(Parser* p) { expect_punct(p, ';', "';' after global declaration"); } +static void parse_static_assert(Parser* p); + static void parse_translation_unit(Parser* p) { while (p->cur.kind != TOK_EOF) { if (p->cur.kind == TOK_NEWLINE || is_pp_hash(&p->cur)) { advance(p); continue; } + if (is_kw(p, &p->cur, KW_STATIC_ASSERT)) { + parse_static_assert(p); + continue; + } parse_external_decl(p); } } diff --git a/test/parse/CORPUS.md b/test/parse/CORPUS.md @@ -157,7 +157,7 @@ here for completeness once they're real cases. | Case | Status | Body | Expected | |---|---|---|---| -| `6_7_01_typedef` | · | `typedef int I; I x = 42; return x;` | 42 | +| `6_7_01_typedef` | ★ | `typedef int I; I x = 42; return x;` | 42 | | `6_7_02_static_local` | ★ | `static int s = 42; return s;` | 42 | | `6_7_03_static_global` | ★ | `static int g = 42; int test_main(void){return g;}` | 42 | | `6_7_04_extern_resolved` | ★ | `extern int g; int g = 42; return g;` | 42 | @@ -227,8 +227,8 @@ remaining qualifier forms and pointer-qualifier interactions. | Case | Status | Body | Expected | |---|---|---|---| -| `6_7_5_01_alignas_obj` | · | `_Alignas(16) static char buf[16]; return (((unsigned long)buf)&15) ? 0 : 42;` | 42 | -| `6_7_5_02_alignas_type` | · | `_Alignas(double) static char buf[8]; return (int)_Alignof(double) * 5 + 2;` | 42 | +| `6_7_5_01_alignas_obj` | ★ | `_Alignas(16) static char buf[16]; return (((unsigned long)buf)&15) ? 0 : 42;` | 42 | +| `6_7_5_02_alignas_type` | ★ | `_Alignas(double) static char buf[8]; return (int)_Alignof(double) * 5 + 2;` | 42 | ## §6.7.6 Declarators @@ -253,10 +253,10 @@ cover compound typedef targets. | Case | Status | Body | Expected | |---|---|---|---| -| `6_7_8_01_typedef_struct` | · | `typedef struct{int v;} S; S s={42}; return s.v;` | 42 | -| `6_7_8_02_typedef_funcptr` | · | `typedef int (*FP)(int); int id(int x){return x;} FP f=id; return f(42);` | 42 | -| `6_7_8_03_typedef_array` | · | `typedef int A[3]; A a={0,0,42}; return a[2];` | 42 | -| `6_7_8_04_typedef_qualified` | · | `typedef const int CI; CI x = 42; return x;` | 42 | +| `6_7_8_01_typedef_struct` | ★ | `typedef struct{int v;} S; S s={42}; return s.v;` | 42 | +| `6_7_8_02_typedef_funcptr` | ★ | `typedef int (*FP)(int); int id(int x){return x;} FP f=id; return f(42);` | 42 | +| `6_7_8_03_typedef_array` | ★ | `typedef int A[3]; A a={0,0,42}; return a[2];` | 42 | +| `6_7_8_04_typedef_qualified` | ★ | `typedef const int CI; CI x = 42; return x;` | 42 | ## §6.7.9 Initialization @@ -277,8 +277,8 @@ cover compound typedef targets. | Case | Status | Body | Expected | |---|---|---|---| -| `6_7_10_01_static_assert_pass` | · | `_Static_assert(sizeof(int) >= 2, "wide int"); return 42;` | 42 | -| `6_7_10_02_static_assert_const` | · | `_Static_assert(1+1 == 2, "math"); return 42;` | 42 | +| `6_7_10_01_static_assert_pass` | ★ | `_Static_assert(sizeof(int) >= 2, "wide int"); return 42;` | 42 | +| `6_7_10_02_static_assert_const` | ★ | `_Static_assert(1+1 == 2, "math"); return 42;` | 42 | ## §6.8 Statements @@ -351,7 +351,7 @@ ordinary calls. | `6_7_2_two_struct_defs` | · | tag | two `struct S { ... };` definitions in same scope | | `6_7_2_1_bitfield_too_wide` | · | bitfield | `unsigned a:33;` exceeds underlying type | | `6_7_3_const_assign` | · | const violation | `const int x = 0; x = 1;` | -| `6_7_10_static_assert_fail` | · | static assertion | `_Static_assert(0, "fail");` | +| `6_7_10_static_assert_fail` | ★ | static assertion | `_Static_assert(0, "fail");` | | `6_8_case_outside_switch` | · | switch scope | `case 1:` outside any switch | | `6_8_continue_outside_loop` | · | iteration scope | `continue;` outside iteration | | `6_8_default_outside_switch` | · | switch scope | `default:` outside switch |