kit

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

commit b30bada9e8811cac46685ca575d8d1acd6f69479
parent bbe0c3e30b210857cf082638b7cc4ecfa2b3e022
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue, 19 May 2026 11:30:51 -0700

Fix parser constraint diagnostics

Diffstat:
Mlang/c/parse/cg_adapter.c | 11+++++++++++
Mlang/c/parse/cg_public_compat.h | 2++
Mlang/c/parse/parse.c | 39++++++++++++++++++++++++++++++++++++++-
Mlang/c/parse/parse_expr.c | 9+++++++++
Mlang/c/parse/parse_priv.h | 9+++++++++
Mlang/c/parse/parse_stmt.c | 14++++++++++++++
Mlang/c/parse/parse_type.c | 163+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
7 files changed, 242 insertions(+), 5 deletions(-)

diff --git a/lang/c/parse/cg_adapter.c b/lang/c/parse/cg_adapter.c @@ -16,6 +16,7 @@ static u32 pcg_alignof(Parser* p, const Type* ty) { #define PCG_VALUE_MODIFIABLE 2u #define PCG_VALUE_BITFIELD 4u #define PCG_VALUE_NULL_PTR_CONST 8u +#define PCG_VALUE_REGISTER 16u static u8 pcg_lvalue_flags_for_type(const Type* ty) { u8 flags = PCG_VALUE_LVALUE; @@ -126,6 +127,16 @@ void pcg_set_top_bitfield(Parser* p) { if (p->cg_type_sp) p->cg_value_flags[p->cg_type_sp - 1u] |= PCG_VALUE_BITFIELD; } +int pcg_top_is_register(Parser* p) { + return p->cg_type_sp && + (p->cg_value_flags[p->cg_type_sp - 1u] & PCG_VALUE_REGISTER) != 0; +} + +void pcg_set_top_register(Parser* p) { + if (p->cg_type_sp) + p->cg_value_flags[p->cg_type_sp - 1u] |= PCG_VALUE_REGISTER; +} + int pcg_top_is_lvalue(Parser* p) { return p->cg_type_sp && (p->cg_value_flags[p->cg_type_sp - 1u] & PCG_VALUE_LVALUE) != 0; diff --git a/lang/c/parse/cg_public_compat.h b/lang/c/parse/cg_public_compat.h @@ -178,6 +178,8 @@ void pcg_dup_type(Parser*); void pcg_swap_type(Parser*); int pcg_top_is_bitfield(Parser*); void pcg_set_top_bitfield(Parser*); +int pcg_top_is_register(Parser*); +void pcg_set_top_register(Parser*); int pcg_top_is_lvalue(Parser*); int pcg_top_is_modifiable_lvalue(Parser*); int pcg_top_is_null_ptr_const(Parser*); diff --git a/lang/c/parse/parse.c b/lang/c/parse/parse.c @@ -291,13 +291,17 @@ Scope* scope_new(Parser* p, Scope* parent) { s->entries = NULL; s->tags = NULL; s->parent = parent; + s->saved_vla_mark = p->vla_mark; return s; } void scope_push(Parser* p) { p->scope = scope_new(p, p->scope); } void scope_pop(Parser* p) { - if (p->scope) p->scope = p->scope->parent; + if (p->scope) { + p->vla_mark = p->scope->saved_vla_mark; + p->scope = p->scope->parent; + } } SymEntry* scope_define(Parser* p, Sym name, SymEntryKind kind, @@ -469,6 +473,13 @@ static void parse_init_declarator(Parser* p, const DeclSpecs* specs) { SrcLoc loc; Sym name; const Type* var_ty = parse_declarator(p, specs->type, &name, &loc); + if ((specs->flags & DF_THREAD) && + specs->storage != DS_STATIC && specs->storage != DS_EXTERN) { + perr(p, "block-scope _Thread_local requires static or extern"); + } + validate_decl_type_constraints(p, specs, var_ty, + var_ty && var_ty->kind == TY_FUNC, + /*is_member=*/0); if (specs->storage == DS_TYPEDEF) { if (is_punct(&p->cur, '=')) { @@ -613,6 +624,7 @@ static void parse_init_declarator(Parser* p, const DeclSpecs* specs) { bsd.align = c_abi_alignof(p->abi, bsd.type); bsd.kind = FS_LOCAL; byte_slot = cg_local(p->cg, &bsd); + ++p->vla_mark; p->vla_pending = 0; p->vla_pending_count_slot = FRAME_SLOT_NONE; cg_set_loc(p->cg, loc); @@ -656,11 +668,19 @@ static void parse_init_declarator(Parser* p, const DeclSpecs* specs) { advance(p); /* '=' */ var_ty = complete_incomplete_array(p, var_ty); s = make_local_aligned(p, name, var_ty, loc, specs->align); + if (specs->storage == DS_REGISTER) { + SymEntry* e = scope_lookup_current(p, name); + if (e && e->kind == SEK_LOCAL) e->storage = DS_REGISTER; + } cg_set_loc(p->cg, loc); init_at(p, s, var_ty, 0, var_ty); return; } s = make_local_aligned(p, name, var_ty, loc, specs->align); + if (specs->storage == DS_REGISTER) { + SymEntry* e = scope_lookup_current(p, name); + if (e && e->kind == SEK_LOCAL) e->storage = DS_REGISTER; + } if (accept_punct(p, '=')) { cg_set_loc(p->cg, loc); if ((var_ty->kind == TY_STRUCT || var_ty->kind == TY_UNION) && @@ -735,6 +755,10 @@ void parse_param_list(Parser* p, ParamInfo** infos_out, u16* nparams_out, if (!parse_decl_specs(p, &specs)) { perr(p, "expected parameter type"); } + if ((specs.storage_explicit && specs.storage != DS_REGISTER) || + specs.storage == DS_TYPEDEF || (specs.flags & DF_THREAD)) { + perr(p, "invalid storage-class specifier in parameter declaration"); + } p->in_param_decl++; pty = parse_declarator_full(p, specs.type, /*allow_abstract=*/1, &pname, &ploc); @@ -747,6 +771,8 @@ void parse_param_list(Parser* p, ParamInfo** infos_out, u16* nparams_out, if (pty && pty->kind == TY_VOID) { perr(p, "'void' must be the only parameter"); } + validate_decl_type_constraints(p, &specs, pty, /*is_function=*/0, + /*is_member=*/0); if (pname) { for (u32 pi = 0; pi < n; ++pi) { if (infos[pi].name == pname) perr(p, "redefinition of parameter"); @@ -918,6 +944,10 @@ static void parse_external_decl(Parser* p) { if (!parse_decl_specs(p, &specs)) { perr(p, "expected declaration"); } + if (specs.storage == DS_REGISTER || + (specs.storage == DS_AUTO && specs.storage_explicit)) { + perr(p, "invalid storage-class specifier at file scope"); + } if (accept_punct(p, ';')) return; @@ -928,6 +958,9 @@ static void parse_external_decl(Parser* p) { const Type* tty = parse_declarator_full(p, specs.type, /*allow_abstract=*/0, &tname, &tloc); + validate_decl_type_constraints(p, &specs, tty, + tty && tty->kind == TY_FUNC, + /*is_member=*/0); if (is_punct(&p->cur, '=')) { perr(p, "typedef declarator cannot have initializer"); } @@ -983,6 +1016,8 @@ static void parse_external_decl(Parser* p) { for (u16 i = 0; i < nparams; ++i) ptypes[i] = infos[i].type; } fn_ty = type_func(p->pool, base_ty, ptypes, nparams, (int)variadic); + validate_decl_type_constraints(p, &specs, fn_ty, /*is_function=*/1, + /*is_member=*/0); abi = c_abi_func_info(p->abi, p->pool, fn_ty); ObjSecId fn_section_id; @@ -1047,6 +1082,8 @@ static void parse_external_decl(Parser* p) { if (existing && existing->kind != SEK_GLOBAL) { perr(p, "redefinition of identifier"); } + validate_decl_type_constraints(p, &specs, base_ty, /*is_function=*/0, + /*is_member=*/0); if (existing && existing->kind == SEK_GLOBAL) { const Type* composite = NULL; diff --git a/lang/c/parse/parse_expr.c b/lang/c/parse/parse_expr.c @@ -1825,6 +1825,7 @@ static void parse_primary(Parser* p) { switch (e->kind) { case SEK_LOCAL: cg_push_local_typed(p->cg, e->v.slot, e->type); + if (e->storage == DS_REGISTER) pcg_set_top_register(p); if (e->vla_byte_slot != FRAME_SLOT_NONE) { p->last_pushed_vla_slot = e->vla_byte_slot; } @@ -2172,6 +2173,8 @@ void parse_unary(Parser* p) { perr(p, "address-of requires lvalue operand"); } if (pcg_top_is_bitfield(p)) perr(p, "cannot take address of bit-field"); + if (pcg_top_is_register(p)) + perr(p, "cannot take address of register object"); cg_addr(p->cg); return; } @@ -2542,6 +2545,8 @@ static void emit_add_or_sub(Parser* p, BinOp bop) { perr(p, "invalid operands to binary +"); } if (l_is_ptr && type_is_int(rt)) { + if (lt->ptr.pointee && lt->ptr.pointee->kind == TY_VOID) + perr(p, "pointer arithmetic on void pointer"); u32 esz = c_abi_sizeof(p->abi, lt->ptr.pointee); if (esz != 1) { cg_push_int(p->cg, (i64)esz, ty_size_t(p)); @@ -2551,6 +2556,8 @@ static void emit_add_or_sub(Parser* p, BinOp bop) { return; } if (r_is_ptr && type_is_int(lt)) { + if (rt->ptr.pointee && rt->ptr.pointee->kind == TY_VOID) + perr(p, "pointer arithmetic on void pointer"); cg_swap(p->cg); u32 esz = c_abi_sizeof(p->abi, rt->ptr.pointee); if (esz != 1) { @@ -2562,6 +2569,8 @@ static void emit_add_or_sub(Parser* p, BinOp bop) { } } else { /* BO_ISUB */ if (l_is_ptr && type_is_int(rt)) { + if (lt->ptr.pointee && lt->ptr.pointee->kind == TY_VOID) + perr(p, "pointer arithmetic on void pointer"); u32 esz = c_abi_sizeof(p->abi, lt->ptr.pointee); if (esz != 1) { cg_push_int(p->cg, (i64)esz, ty_size_t(p)); diff --git a/lang/c/parse/parse_priv.h b/lang/c/parse/parse_priv.h @@ -123,6 +123,7 @@ struct Scope { SymEntry* entries; /* LIFO */ TagEntry* tags; /* LIFO */ Scope* parent; + u32 saved_vla_mark; }; /* ============================================================ @@ -152,6 +153,8 @@ struct GotoLabel { u8 placed; u8 pad[3]; SrcLoc first_use; + u32 min_forward_vla_mark; + u32 label_vla_mark; GotoLabel* next; }; @@ -240,6 +243,7 @@ typedef struct Parser { u8 vla_pending; FrameSlot vla_pending_count_slot; + u32 vla_mark; FrameSlot last_pushed_vla_slot; @@ -275,6 +279,8 @@ typedef struct DeclSpecs { DeclStorage storage; u32 flags; /* DeclFlag */ u16 quals; + u8 storage_explicit; + u8 pad; u32 align; FrameSlot vla_byte_slot; Attr* attrs; @@ -411,6 +417,9 @@ void attr_list_append(Attr** head, Attr* add); void parse_attrs_into(Parser* p, Attr** sink); int parse_decl_suffix(Parser* p, DeclSuffix* out); const Type* apply_decl_suffix(Parser* p, const Type* base, const DeclSuffix* s); +void validate_decl_type_constraints(Parser* p, const DeclSpecs* specs, + const Type* ty, int is_function, + int is_member); /* parse_expr.c */ void parse_expr(Parser* p); diff --git a/lang/c/parse/parse_stmt.c b/lang/c/parse/parse_stmt.c @@ -223,6 +223,8 @@ static GotoLabel* label_get_or_create(Parser* p, Sym name, SrcLoc loc) { gl->label = cg_label_new(p->cg); gl->placed = 0; gl->first_use = loc; + gl->min_forward_vla_mark = p->vla_mark; + gl->label_vla_mark = 0; gl->next = p->goto_labels; p->goto_labels = gl; return gl; @@ -240,6 +242,13 @@ static void parse_goto_stmt(Parser* p) { advance(p); expect_punct(p, ';', "';' after goto"); gl = label_get_or_create(p, name, loc); + if (gl->placed) { + if (p->vla_mark < gl->label_vla_mark) { + perr(p, "goto into scope of variably modified object"); + } + } else if (p->vla_mark < gl->min_forward_vla_mark) { + gl->min_forward_vla_mark = p->vla_mark; + } cg_jump(p->cg, gl->label); } @@ -251,7 +260,11 @@ static void parse_label_stmt(Parser* p) { advance(p); /* ':' */ gl = label_get_or_create(p, name, loc); if (gl->placed) perr(p, "duplicate label"); + if (gl->min_forward_vla_mark < p->vla_mark) { + perr(p, "goto into scope of variably modified object"); + } gl->placed = 1; + gl->label_vla_mark = p->vla_mark; cg_label_place(p->cg, gl->label); parse_stmt(p); } @@ -306,6 +319,7 @@ static void parse_switch_stmt(Parser* p) { to_rvalue(p); vty = cg_top_type(p->cg); if (!vty) vty = type_prim(p->pool, TY_INT); + if (!type_is_int(vty)) perr(p, "switch expression requires integer type"); expect_punct(p, ')', "')' after switch expression"); memset(&ctx, 0, sizeof ctx); diff --git a/lang/c/parse/parse_type.c b/lang/c/parse/parse_type.c @@ -366,12 +366,78 @@ u32 attrs_pick_aligned(const Attr* a) { return best; } +static int is_power_of_two_u32(u32 v) { + return v != 0 && (v & (v - 1u)) == 0; +} + +static void validate_atomic_operand(Parser* p, const Type* ty) { + if (!ty) perr(p, "_Atomic requires an object type"); + if (ty->qual) perr(p, "_Atomic operand must not be qualified"); + if (ty->kind == TY_ARRAY || ty->kind == TY_FUNC || ty->kind == TY_VOID) { + perr(p, "_Atomic operand must be an object type"); + } +} + +void validate_decl_type_constraints(Parser* p, const DeclSpecs* specs, + const Type* ty, int is_function, + int is_member) { + const Type* u = ty ? type_unqual(p->pool, ty) : NULL; + if (!u) return; + if (specs->quals & Q_RESTRICT) { + perr(p, "restrict requires pointer type"); + } + if ((ty->qual & Q_RESTRICT) && ty->kind != TY_PTR) { + perr(p, "restrict requires pointer type"); + } + if (is_member) { + if (specs->storage != DS_AUTO || specs->storage_explicit) { + perr(p, "storage-class specifier is invalid for struct member"); + } + if (specs->flags & DF_INLINE) perr(p, "inline is invalid for struct member"); + if (specs->flags & DF_NORETURN) + perr(p, "_Noreturn is invalid for struct member"); + if (specs->flags & DF_THREAD) + perr(p, "_Thread_local is invalid for struct member"); + } + if (u->kind == TY_VOID && !is_function) { + perr(p, "object may not have void type"); + } + if ((specs->flags & DF_INLINE) && !is_function) { + perr(p, "inline may only appear on a function declaration"); + } + if ((specs->flags & DF_NORETURN) && !is_function) { + perr(p, "_Noreturn may only appear on a function declaration"); + } + if ((specs->flags & DF_THREAD) && (is_function || specs->storage == DS_TYPEDEF)) { + perr(p, "_Thread_local may only appear on object declarations"); + } + if (specs->align) { + u32 natural = 0; + if (!is_power_of_two_u32(specs->align)) { + perr(p, "_Alignas requires a power-of-two alignment"); + } + if (is_function || specs->storage == DS_TYPEDEF) { + perr(p, "_Alignas is invalid on this declaration"); + } + if (u->kind == TY_VOID || u->kind == TY_FUNC) { + perr(p, "_Alignas requires an object type"); + } + natural = c_abi_alignof(p->abi, ty); + if (specs->align < natural) { + perr(p, "_Alignas cannot weaken natural alignment"); + } + } +} + /* ============================================================ * resolve_type_specs * ============================================================ */ const Type* resolve_type_specs(Parser* p, const TypeSpecAccum* a, SrcLoc loc) { if (!a->saw_explicit_type) return NULL; + if (a->long_count > 2) { + compiler_panic(p->c, loc, "too many long type specifiers"); + } if (a->saw_void) { if (a->saw_char || a->saw_int || a->saw_short || a->long_count || a->saw_signed || a->saw_unsigned || a->saw_bool || a->saw_float || @@ -381,18 +447,39 @@ const Type* resolve_type_specs(Parser* p, const TypeSpecAccum* a, SrcLoc loc) { return type_void(p->pool); } if (a->saw_bool) { + if (a->saw_char || a->saw_int || a->saw_short || a->long_count || + a->saw_signed || a->saw_unsigned || a->saw_float || a->saw_double) { + compiler_panic(p->c, loc, "conflicting type specifiers (_Bool mixed)"); + } return type_prim(p->pool, TY_BOOL); } if (a->saw_char) { + if (a->saw_int || a->saw_short || a->long_count || a->saw_float || + a->saw_double) { + compiler_panic(p->c, loc, "conflicting type specifiers (char mixed)"); + } if (a->saw_unsigned) return type_prim(p->pool, TY_UCHAR); if (a->saw_signed) return type_prim(p->pool, TY_SCHAR); return type_prim(p->pool, TY_CHAR); } - if (a->saw_float) return type_prim(p->pool, TY_FLOAT); + if (a->saw_float) { + if (a->saw_int || a->saw_short || a->long_count || a->saw_signed || + a->saw_unsigned || a->saw_double) { + compiler_panic(p->c, loc, "conflicting type specifiers (float mixed)"); + } + return type_prim(p->pool, TY_FLOAT); + } if (a->saw_double) { + if (a->saw_int || a->saw_short || a->saw_signed || a->saw_unsigned || + a->long_count > 1) { + compiler_panic(p->c, loc, "conflicting type specifiers (double mixed)"); + } return type_prim(p->pool, a->long_count ? TY_LDOUBLE : TY_DOUBLE); } if (a->saw_short) { + if (a->long_count) { + compiler_panic(p->c, loc, "conflicting type specifiers (short long)"); + } return type_prim(p->pool, a->saw_unsigned ? TY_USHORT : TY_SHORT); } if (a->saw_int128) { @@ -424,6 +511,8 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) { out->storage = DS_AUTO; out->flags = DF_NONE; out->quals = 0; + out->storage_explicit = 0; + out->pad = 0; out->align = 0; out->vla_byte_slot = FRAME_SLOT_NONE; out->attrs = NULL; @@ -536,12 +625,14 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) { } else if (is_kw(p, &t, KW_STATIC)) { if (storage_seen) perr(p, "multiple storage-class specifiers"); storage_seen = 1; + out->storage_explicit = 1; out->storage = DS_STATIC; advance(p); seen = 1; } else if (is_kw(p, &t, KW_EXTERN)) { if (storage_seen) perr(p, "multiple storage-class specifiers"); storage_seen = 1; + out->storage_explicit = 1; out->storage = DS_EXTERN; advance(p); seen = 1; @@ -568,6 +659,7 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) { advance(p); /* `(` */ inner = parse_type_name(p); expect_punct(p, ')', "')' after _Atomic type"); + validate_atomic_operand(p, inner); tagged_ty = type_qualified(p->pool, inner, Q_ATOMIC); acc.saw_explicit_type = 1; seen = 1; @@ -579,6 +671,7 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) { } else if (is_kw(p, &t, KW_TYPEDEF)) { if (storage_seen) perr(p, "multiple storage-class specifiers"); storage_seen = 1; + out->storage_explicit = 1; out->storage = DS_TYPEDEF; advance(p); seen = 1; @@ -594,6 +687,9 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) { if (v < 0) perr(p, "_Alignas requires a non-negative alignment"); a = (u32)v; } + if (a != 0 && !is_power_of_two_u32(a)) { + perr(p, "_Alignas requires a power-of-two alignment"); + } expect_punct(p, ')', "')' after _Alignas argument"); if (a > out->align) out->align = a; seen = 1; @@ -606,17 +702,20 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) { advance(p); seen = 1; } else if (is_kw(p, &t, KW_NORETURN)) { + out->flags |= DF_NORETURN; advance(p); seen = 1; } else if (is_kw(p, &t, KW_REGISTER)) { if (storage_seen) perr(p, "multiple storage-class specifiers"); storage_seen = 1; + out->storage_explicit = 1; out->storage = DS_REGISTER; advance(p); seen = 1; } else if (is_kw(p, &t, KW_AUTO)) { if (storage_seen) perr(p, "multiple storage-class specifiers"); storage_seen = 1; + out->storage_explicit = 1; out->storage = DS_AUTO; advance(p); seen = 1; @@ -698,7 +797,56 @@ int find_field(TargetABI* abi, Pool* pool, const Type* rec, Sym name, return 0; } +typedef struct MemberNameSeen { + Sym name; + struct MemberNameSeen* next; +} MemberNameSeen; + +static int member_name_seen(MemberNameSeen* names, Sym name) { + for (; names; names = names->next) { + if (names->name == name) return 1; + } + return 0; +} + +static void member_name_add(Parser* p, MemberNameSeen** names, Sym name) { + MemberNameSeen* n; + if (!name) return; + n = arena_new(p->pool->arena, MemberNameSeen); + if (!n) perr(p, "out of memory tracking struct members"); + n->name = name; + n->next = *names; + *names = n; +} + +static void validate_and_add_field(Parser* p, TypeRecordBuilder* b, + const DeclSpecs* specs, Field* f, + MemberNameSeen** names, u32* field_count, + int* saw_flexible) { + int is_flexible = f->type && f->type->kind == TY_ARRAY && f->type->arr.incomplete; + validate_decl_type_constraints(p, specs, f->type, /*is_function=*/0, + /*is_member=*/1); + if ((f->flags & FIELD_BITFIELD) && specs->align) { + perr(p, "_Alignas is invalid on bit-field"); + } + if (f->name && member_name_seen(*names, f->name)) { + perr(p, "duplicate member name"); + } + if (*saw_flexible) perr(p, "flexible array member must be last"); + if (is_flexible) { + if (*field_count == 0) perr(p, "flexible array member cannot be only member"); + f->flags |= FIELD_FLEXIBLE_ARRAY; + *saw_flexible = 1; + } + member_name_add(p, names, f->name); + type_record_field(b, *f); + ++*field_count; +} + static void parse_member_decls(Parser* p, TypeRecordBuilder* b) { + MemberNameSeen* names = NULL; + u32 field_count = 0; + int saw_flexible = 0; while (!is_punct(&p->cur, '}') && p->cur.kind != TOK_EOF) { DeclSpecs specs; if (!parse_decl_specs(p, &specs)) { @@ -712,7 +860,8 @@ static void parse_member_decls(Parser* p, TypeRecordBuilder* b) { f.name = 0; f.type = specs.type; f.flags = FIELD_ANON; - type_record_field(b, f); + validate_and_add_field(p, b, &specs, &f, &names, &field_count, + &saw_flexible); advance(p); continue; } @@ -739,7 +888,8 @@ static void parse_member_decls(Parser* p, TypeRecordBuilder* b) { if (w == 0) f.flags |= FIELD_ZERO_WIDTH; attrs_to_field(specs.attrs, &f); if (specs.align > f.align_override) f.align_override = (u16)specs.align; - type_record_field(b, f); + validate_and_add_field(p, b, &specs, &f, &names, &field_count, + &saw_flexible); if (!accept_punct(p, ',')) break; continue; } @@ -773,7 +923,8 @@ static void parse_member_decls(Parser* p, TypeRecordBuilder* b) { attrs_to_field(trailing, &f); } if (specs.align > f.align_override) f.align_override = (u16)specs.align; - type_record_field(b, f); + validate_and_add_field(p, b, &specs, &f, &names, &field_count, + &saw_flexible); if (!accept_punct(p, ',')) break; } expect_punct(p, ';', "';' after struct member declaration"); @@ -1069,6 +1220,9 @@ int parse_decl_suffix(Parser* p, DeclSuffix* out) { { Tok t = p->cur; int is_const_start = (t.kind == TOK_NUM || t.kind == TOK_CHR); + if (t.kind == TOK_FLT) { + perr(p, "array bound requires integer type"); + } if (!is_const_start && t.kind == TOK_IDENT) { SymEntry* e = scope_lookup(p, t.v.ident); if (e && e->kind == SEK_ENUM_CST) is_const_start = 1; @@ -1102,6 +1256,7 @@ int parse_decl_suffix(Parser* p, DeclSuffix* out) { cg_store(p->cg); cg_drop(p->cg); p->vla_pending = 1; + ++p->vla_mark; p->vla_pending_count_slot = out->vla_count_slot; } }