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:
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 |