kit

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

commit 47228f1c901f89406c2bfbf0b8e59eba7f4f0284
parent 8c0db5d16871b34e32f57be0a35b43b3186fe50b
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 11 May 2026 11:41:10 -0700

parse: fix sizeof/alignof expr forms + block-scope static[] inference

Three parser gaps surfaced in STAGE2 fallout:

- `sizeof expr` and `sizeof(expr)` failed inside constant-expression
  contexts (e.g. `int a[sizeof x]`) — cexpr_unary only admitted the
  type-name form. Disambiguate at the `(` like parse_unary does and
  route the expression case through parse_unary, reading the operand
  type off the cg stack.
- `__alignof__(expr)` (GNU expression-operand extension) likewise only
  accepted a type-name in both parse_unary and cexpr_unary.
- Block-scope `static T name[] = {...}` errored "too many initializers"
  — B6 only patched the file-scope incomplete-array completion path;
  the static-local branch in parse_init_declarator skipped the same
  complete_incomplete_array call.

Tests cover all three.

Diffstat:
Msrc/parse/parse.c | 76+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Atest/parse/cases/6_2_5_03_block_static_incomplete_array.c | 7+++++++
Atest/parse/cases/6_2_5_03_block_static_incomplete_array.expected | 1+
Atest/parse/cases/6_5_3_4_02_sizeof_expr_no_parens.c | 7+++++++
Atest/parse/cases/6_5_3_4_02_sizeof_expr_no_parens.expected | 1+
Atest/parse/cases/6_5_3_4_03_alignof_expr.c | 8++++++++
Atest/parse/cases/6_5_3_4_03_alignof_expr.expected | 1+
7 files changed, 88 insertions(+), 13 deletions(-)

diff --git a/src/parse/parse.c b/src/parse/parse.c @@ -1115,6 +1115,7 @@ static int parse_decl_specs(Parser* p, DeclSpecs* out) { * Returns the evaluated value; on parse-fail or non-constant operand it * panics with `loc` as the diagnostic site. */ static i64 cexpr_unary(Parser* p, SrcLoc loc); +static void parse_unary(Parser* p); static i64 cexpr_mul(Parser* p, SrcLoc loc) { i64 v = cexpr_unary(p, loc); for (;;) { @@ -1198,22 +1199,52 @@ 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) ? 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"); + /* `sizeof ( type-name )` resolves to a pure type-name lookup. The + * `sizeof ( expression )` and `sizeof unary-expression` forms run the + * operand through parse_unary (operand is not evaluated per §6.5.3.4, + * matching parse_unary's handling) and read the resulting type off the + * cg stack, then drop. */ + if (is_punct(&p->cur, '(')) { + Tok n = peek1(p); + if (starts_type_name(p, &n)) { + advance(p); + { + const Type* t = parse_type_name(p); + expect_punct(p, ')', "')' after sizeof type-name"); + return (i64)abi_sizeof(p->abi, t); + } + } + } + parse_unary(p); { - const Type* t = parse_type_name(p); - expect_punct(p, ')', "')' after sizeof type-name"); - return (i64)abi_sizeof(p->abi, t); + const Type* ty = cg_top_type(p->cg); + i64 sz = (i64)abi_sizeof(p->abi, ty); + cg_drop(p->cg); + return sz; } } if (accept_kw(p, KW_ALIGNOF)) { - expect_punct(p, '(', "'(' after _Alignof in constant expression"); + /* `_Alignof` is type-name only per §6.5.3.4. The GNU `__alignof__` + * alias additionally accepts an expression, mirroring sizeof. We + * disambiguate at the `(`: type-name → parse_type_name, otherwise + * route through parse_unary and read the operand type. */ + if (is_punct(&p->cur, '(')) { + Tok n = peek1(p); + if (starts_type_name(p, &n)) { + advance(p); + { + const Type* t = parse_type_name(p); + expect_punct(p, ')', "')' after _Alignof type-name"); + return (i64)abi_alignof(p->abi, t); + } + } + } + parse_unary(p); { - const Type* t = parse_type_name(p); - expect_punct(p, ')', "')' after _Alignof type-name"); - return (i64)abi_alignof(p->abi, t); + const Type* ty = cg_top_type(p->cg); + i64 al = (i64)abi_alignof(p->abi, ty); + cg_drop(p->cg); + return al; } } if (accept_punct(p, '(')) { @@ -3350,11 +3381,20 @@ static void parse_unary(Parser* p) { return; } if (is_kw(p, &t, KW_ALIGNOF)) { - /* _Alignof is type-name only (per §6.5.3.4 ¶1). */ + /* `_Alignof ( type-name )` per §6.5.3.4 ¶1. The GNU `__alignof__` + * alias additionally accepts an expression operand, mirroring sizeof. + * Disambiguate at the `(`: type-name → parse_type_name; otherwise + * route through parse_unary, read the operand's type, drop. */ const Type* ty; advance(p); expect_punct(p, '(', "'('"); - ty = parse_type_name(p); + if (starts_type_name(p, &p->cur)) { + ty = parse_type_name(p); + } else { + parse_unary(p); + ty = cg_top_type(p->cg); + cg_drop(p->cg); + } expect_punct(p, ')', "')'"); cg_push_int(p->cg, (i64)abi_alignof(p->abi, ty), ty_size_t(p)); return; @@ -5244,6 +5284,16 @@ 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, '='); + /* `static T name[] = {...}` at block scope: peek the initializer to + * deduce the element count before emitting, the same as the file-scope + * path above (search for the matching call in parse_global_decl). */ + if (has_init && var_ty && var_ty->kind == TY_ARRAY && var_ty->arr.incomplete) { + const Type* completed = complete_incomplete_array(p, var_ty); + if (completed != var_ty) { + var_ty = completed; + e->type = var_ty; + } + } align_eff = (specs->align > decl_in.align) ? specs->align : decl_in.align; define_static_object(p, sym, var_ty, specs->quals, has_init, loc, align_eff); diff --git a/test/parse/cases/6_2_5_03_block_static_incomplete_array.c b/test/parse/cases/6_2_5_03_block_static_incomplete_array.c @@ -0,0 +1,7 @@ +/* Block-scope `static T name[] = { ... }` must complete the incomplete + * array length from the initializer count, the same as the file-scope + * path. Regression: B6 patched only the file-scope walker. */ +int test_main(void) { + static const char* m[] = {"a", "b", "c"}; + return (int)(sizeof(m) / sizeof(m[0])); +} diff --git a/test/parse/cases/6_2_5_03_block_static_incomplete_array.expected b/test/parse/cases/6_2_5_03_block_static_incomplete_array.expected @@ -0,0 +1 @@ +3 diff --git a/test/parse/cases/6_5_3_4_02_sizeof_expr_no_parens.c b/test/parse/cases/6_5_3_4_02_sizeof_expr_no_parens.c @@ -0,0 +1,7 @@ +/* `sizeof unary-expression` (no parens) must work in constant-expression + * contexts too — e.g., as an array bound. §6.5.3.4. */ +int test_main(void) { + int x; + char a[sizeof x]; + return (int)sizeof a + 38; +} diff --git a/test/parse/cases/6_5_3_4_02_sizeof_expr_no_parens.expected b/test/parse/cases/6_5_3_4_02_sizeof_expr_no_parens.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/6_5_3_4_03_alignof_expr.c b/test/parse/cases/6_5_3_4_03_alignof_expr.c @@ -0,0 +1,8 @@ +/* GNU extension: `__alignof__(expr)` accepts an expression operand, not + * just a type-name. §6.5.3.4 specifies type-name only for ISO `_Alignof`, + * but `__alignof__` mirrors `sizeof` and accepts an expression. */ +int test_main(void) { + int x; + (void)__alignof__(x); + return 0; +} diff --git a/test/parse/cases/6_5_3_4_03_alignof_expr.expected b/test/parse/cases/6_5_3_4_03_alignof_expr.expected @@ -0,0 +1 @@ +0