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