commit b2419bcaa1e961e0c1e16f3e5656343c55270d90 parent 491137be4f696a8ca5f6cb75baaf0970c5248ced Author: Ryan Sepassi <rsepassi@gmail.com> Date: Mon, 18 May 2026 19:34:20 -0700 Complete C11 declaration and expression semantics cleanup Diffstat:
41 files changed, 385 insertions(+), 44 deletions(-)
diff --git a/doc/C11_CONFORMANCE_CHECKLIST.md b/doc/C11_CONFORMANCE_CHECKLIST.md @@ -123,15 +123,18 @@ CFREE_TEST_FILTER=asm_02_file_scope make test-parse legal shadowing in nested block scopes. Tests: `6_7_same_scope_redefinition`, `6_9_duplicate_parameter`. -- [ ] Complete tag state handling for forward declarations, same-scope +- [x] Complete tag state handling for forward declarations, same-scope completion, and wrong-kind redeclarations. - New negative coverage: `6_7_2_tag_wrong_kind` passes. -- [ ] Validate function declarator constraints: + Negative coverage: `6_7_2_tag_wrong_kind`, + `6_7_2_enum_forward`, `6_7_2_enum_wrong_kind`, and + `6_7_2_enum_redefinition`. +- [x] Validate function declarator constraints: `void` parameter rules, variadic placement, function returning function, function returning array, array/function parameter adjustment. - New coverage: invalid variadic placement, function returning function, - and function returning array all pass as diagnostics; positive function - parameter adjustment is covered by `6_7_6_14_func_param_adjust`. + Negative coverage: invalid variadic placement, ellipsis without a + preceding parameter, function returning function, function returning + array, and array of function. Positive function parameter adjustment is + covered by `6_7_6_14_func_param_adjust`. - [x] Decide and document implementation-defined bit-field behavior: plain `int` signedness, allowed extended bit-field types, allocation order, straddling, and alignment. @@ -147,32 +150,38 @@ CFREE_TEST_FILTER=asm_02_file_scope make test-parse ## Expressions and conversions -- [ ] Make implicit conversions constraint-aware. Do not rely on CG conversion +- [x] Make implicit conversions constraint-aware. Do not rely on CG conversion success as the semantic check. - First slice complete for assignment, initialization, return, and - redeclaration diagnostics via `lang/c/sem`. -- [ ] Preserve lvalue properties: modifiable, const-qualified, bit-field, + Covered for assignment, compound assignment, initialization, return, + calls, and redeclaration diagnostics via `lang/c/sem`. +- [x] Preserve lvalue properties: modifiable, const-qualified, bit-field, array, function designator, and incomplete type. -- [ ] Implement `sizeof` rules completely: + The parser value stack tracks lvalue, modifiable-lvalue, bit-field, and + null-pointer-constant state across loads, conversions, member access, + dereference, and materialization. +- [x] Implement `sizeof` rules completely: no incomplete object type, no function type, no bit-field, VLA operand evaluated, non-VLA operand not evaluated. - New coverage: `sizeof(function)` is rejected and VLA/deref pointer - positive cases pass; `sizeof(bit-field)` is rejected. -- [ ] Complete conditional operator usual-conversion behavior for arithmetic + Coverage: `sizeof(function)` and `sizeof(bit-field)` are rejected, + VLA/deref pointer positive cases pass, and `6_5_59_sizeof_no_eval` + verifies non-VLA operands are not evaluated. +- [x] Complete conditional operator usual-conversion behavior for arithmetic and pointer/null arms. Positive arithmetic and pointer/null cases pass; incompatible pointer arms are rejected. -- [ ] Complete pointer compound assignment (`p += n`, `p -= n`). - Positive `p += n` coverage passes. -- [ ] Expand `_Generic` tests for default selection, compatible types, and +- [x] Complete pointer compound assignment (`p += n`, `p -= n`). + Positive `p += n` and `p -= n` coverage passes; pointer RHS is rejected. +- [x] Expand `_Generic` tests for default selection, compatible types, and unevaluated controlling expression. - Default selection coverage passes. -- [ ] Add negative tests for invalid pointer arithmetic, invalid relational + Default selection, compatible typedef matching, and duplicate-compatible + association diagnostics pass. +- [x] Add negative tests for invalid pointer arithmetic, invalid relational comparisons, invalid casts, modifying non-lvalues, and scalar-required operators. - New negative tests are checked in. Pointer-plus-pointer, incompatible - pointer relational compare, struct-to-int cast, struct condition, and - array assignment are rejected. + Pointer-plus-pointer, incompatible pointer relational compare, + struct-to-int cast, struct condition, array assignment, bad call + argument conversion, invalid pointer compound assignment, and floating + bitwise operands are rejected. ## Constant expressions and initializers @@ -203,16 +212,16 @@ CFREE_TEST_FILTER=asm_02_file_scope make test-parse - [x] Implement static-storage union initialization or document a temporary nonconformance gate. Positive non-first union designated initializer passes. -- [ ] Complete designated initializers: +- [x] Complete designated initializers: nested designators, enum-valued array designators, duplicate designator overwrite rules, non-first union member. Positive nested, enum-valued, duplicate overwrite, and non-first union coverage passes. -- [ ] Add diagnostics for initializer overflow, excess scalar initializers, +- [x] Add diagnostics for initializer overflow, excess scalar initializers, non-constant static initializers, and invalid designators. New diagnostics for excess scalar initializers, non-constant static - initializers, and invalid array/struct/union designators pass. Overflow - diagnostics still need a precise C11 test case. + initializers, invalid array/struct/union designators, and signed static + integer initializer overflow pass. ## Preprocessor and translation phases @@ -264,15 +273,15 @@ conformance needs a mode story. the first targeted cases. 2. Add a compact "semantic type checks" helper layer so assignment, return, initialization, conditional expressions, and calls share rules. - Initial helper layer exists in `lang/c/sem`; conditional/call sharing is - still pending. + Helper coverage now includes assignment, compound assignment, + redeclaration, calls, and initializer/return use sites. 3. Fix declaration-state tracking: redeclarations, tentative definitions, function definitions, tag completion, and composite types. - Ordinary identifier redeclarations, tentative/defined state, and composite - object/function types are covered; tag completion remains pending. -4. Finish bit-fields: diagnostics first, then layout/codegen. -5. Finish `sizeof` edge cases and unify static-initializer/address-constant - semantics with the typed constant-expression evaluator. + Ordinary identifier redeclarations, tentative/defined state, composite + object/function types, and tag completion are covered. +4. Keep bit-field layout/codegen covered while broadening target ABI tests. +5. Keep the shared static-initializer/address-constant classifier as the only + static initializer constant path when adding new initializer forms. 6. Unskip `long double` or explicitly narrow the supported C profile until runtime/CG support exists. 7. Bring `rt` and freestanding header tests into the default conformance gate. diff --git a/lang/c/parse/cg_adapter.c b/lang/c/parse/cg_adapter.c @@ -12,7 +12,19 @@ static u32 pcg_alignof(Parser* p, const Type* ty) { return (u32)cfree_cg_type_align(p->c, pcg_tid(p->c, ty)); } -#define PCG_VALUE_BITFIELD 1u +#define PCG_VALUE_LVALUE 1u +#define PCG_VALUE_MODIFIABLE 2u +#define PCG_VALUE_BITFIELD 4u +#define PCG_VALUE_NULL_PTR_CONST 8u + +static u8 pcg_lvalue_flags_for_type(const Type* ty) { + u8 flags = PCG_VALUE_LVALUE; + if (ty && !(ty->qual & Q_CONST) && ty->kind != TY_ARRAY && + ty->kind != TY_FUNC && ty->kind != TY_VOID) { + flags |= PCG_VALUE_MODIFIABLE; + } + return flags; +} CfreeCgMemAccess pcg_mem(Parser* p, const Type* ty) { CfreeCgMemAccess m; @@ -114,6 +126,30 @@ 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_lvalue(Parser* p) { + return p->cg_type_sp && + (p->cg_value_flags[p->cg_type_sp - 1u] & PCG_VALUE_LVALUE) != 0; +} + +int pcg_top_is_modifiable_lvalue(Parser* p) { + return p->cg_type_sp && + (p->cg_value_flags[p->cg_type_sp - 1u] & + (PCG_VALUE_LVALUE | PCG_VALUE_MODIFIABLE)) == + (PCG_VALUE_LVALUE | PCG_VALUE_MODIFIABLE); +} + +int pcg_top_is_null_ptr_const(Parser* p) { + return p->cg_type_sp && + (p->cg_value_flags[p->cg_type_sp - 1u] & + PCG_VALUE_NULL_PTR_CONST) != 0; +} + +void pcg_set_top_lvalue(Parser* p) { + const Type* ty = pcg_top_type(p); + if (p->cg_type_sp) p->cg_value_flags[p->cg_type_sp - 1u] = + pcg_lvalue_flags_for_type(ty); +} + int pcg_emit_enabled(Parser* p) { return p && p->suppress_codegen == 0; } void pcg_codegen_suppress_push(Parser* p) { @@ -297,6 +333,9 @@ void pcg_push_int(Parser* p, i64 v, const Type* ty) { cfree_cg_push_int(p->cg, (uint64_t)v, pcg_tid(p->c, ty)); } pcg_push_type(p, ty); + if (v == 0 && p->cg_type_sp) { + p->cg_value_flags[p->cg_type_sp - 1u] |= PCG_VALUE_NULL_PTR_CONST; + } } void pcg_push_float(Parser* p, double v, const Type* ty) { @@ -307,11 +346,15 @@ void pcg_push_float(Parser* p, double v, const Type* ty) { void pcg_push_local_typed(Parser* p, FrameSlot s, const Type* ty) { if (pcg_emit_enabled(p)) cfree_cg_push_local(p->cg, s); pcg_push_type(p, ty); + if (p->cg_type_sp) p->cg_value_flags[p->cg_type_sp - 1u] = + pcg_lvalue_flags_for_type(ty); } void pcg_push_global(Parser* p, ObjSymId sym, const Type* ty) { if (pcg_emit_enabled(p)) cfree_cg_push_symbol_lvalue(p->cg, sym, 0); pcg_push_type(p, ty); + if (p->cg_type_sp) p->cg_value_flags[p->cg_type_sp - 1u] = + pcg_lvalue_flags_for_type(ty); } void pcg_load(Parser* p) { @@ -348,6 +391,8 @@ void pcg_store(Parser* p) { void pcg_deref(Parser* p, const Type* pointee) { if (pcg_emit_enabled(p)) cfree_cg_indirect(p->cg); pcg_retag_top(p, pointee); + if (p->cg_type_sp) p->cg_value_flags[p->cg_type_sp - 1u] = + pcg_lvalue_flags_for_type(pointee); } void pcg_binop(Parser* p, BinOp op) { diff --git a/lang/c/parse/cg_public_compat.h b/lang/c/parse/cg_public_compat.h @@ -178,6 +178,10 @@ 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_lvalue(Parser*); +int pcg_top_is_modifiable_lvalue(Parser*); +int pcg_top_is_null_ptr_const(Parser*); +void pcg_set_top_lvalue(Parser*); void pcg_rot3_type(Parser*); int pcg_emit_enabled(Parser*); void pcg_codegen_suppress_push(Parser*); diff --git a/lang/c/parse/parse.c b/lang/c/parse/parse.c @@ -728,6 +728,7 @@ void parse_param_list(Parser* p, ParamInfo** infos_out, u16* nparams_out, SrcLoc ploc = {0, 0, 0}; const Type* pty; if (accept_punct(p, P_ELLIPSIS)) { + if (n == 0) perr(p, "ellipsis requires a preceding parameter"); *variadic_out = 1; break; } diff --git a/lang/c/parse/parse_expr.c b/lang/c/parse/parse_expr.c @@ -54,7 +54,9 @@ static int pointer_pointees_compatible(Parser* p, const Type* lhs, static int null_pointer_constant(Parser* p, const Type* ty) { i64 v = 1; - return type_is_int(ty) && cfree_cg_top_const_int(p->cg, &v) && v == 0; + return type_is_int(ty) && + (pcg_top_is_null_ptr_const(p) || + (cfree_cg_top_const_int(p->cg, &v) && v == 0)); } static void require_scalar(Parser* p, const Type* ty, const char* what) { @@ -1820,10 +1822,12 @@ static int find_record_member_path(Parser* p, const Type* rec_ty, Sym mname, static void cg_record_member_path(Parser* p, const Type* member_ty, const u32* path, u32 depth, const Field* field) { + int was_lvalue = pcg_top_is_lvalue(p); for (u32 i = 0; i < depth; ++i) { if (pcg_emit_enabled(p)) cfree_cg_field(p->cg, path[i]); } pcg_retag_top(p, member_ty); + if (was_lvalue) pcg_set_top_lvalue(p); if (field && (field->flags & FIELD_BITFIELD)) pcg_set_top_bitfield(p); } @@ -1833,11 +1837,23 @@ static void parse_postfix(Parser* p) { Tok t = p->cur; if (is_punct(&t, P_INC)) { advance(p); + if (!pcg_top_is_modifiable_lvalue(p)) { + perr(p, "increment/decrement requires modifiable lvalue"); + } + if (!c_type_is_scalar(cg_top_type(p->cg))) { + perr(p, "increment/decrement requires scalar operand"); + } cg_inc_dec(p->cg, BO_IADD, /*post=*/1); continue; } if (is_punct(&t, P_DEC)) { advance(p); + if (!pcg_top_is_modifiable_lvalue(p)) { + perr(p, "increment/decrement requires modifiable lvalue"); + } + if (!c_type_is_scalar(cg_top_type(p->cg))) { + perr(p, "increment/decrement requires scalar operand"); + } cg_inc_dec(p->cg, BO_ISUB, /*post=*/1); continue; } @@ -1857,8 +1873,16 @@ static void parse_postfix(Parser* p) { u32 nargs = 0; if (!is_punct(&p->cur, ')')) { for (;;) { + const Type* param_ty = + (nargs < fn_type->fn.nparams) ? fn_type->fn.params[nargs] : NULL; parse_assign_expr(p); to_rvalue(p); + if (param_ty) { + CSemCheck chk = c_sem_check_assignment( + p->pool, param_ty, cg_top_type(p->cg), C_SEM_ASSIGN_EXPR); + if (!chk.ok) perr(p, "%s", chk.message); + coerce_top_to_type(p, param_ty); + } ++nargs; if (!accept_punct(p, ',')) break; } @@ -2061,6 +2085,10 @@ void parse_unary(Parser* p) { if (is_punct(&t, '&')) { advance(p); parse_unary(p); + if (!pcg_top_is_lvalue(p) && !(cg_top_type(p->cg) && + cg_top_type(p->cg)->kind == TY_FUNC)) { + perr(p, "address-of requires lvalue operand"); + } if (pcg_top_is_bitfield(p)) perr(p, "cannot take address of bit-field"); cg_addr(p->cg); return; @@ -2086,6 +2114,12 @@ void parse_unary(Parser* p) { BinOp bop = is_punct(&t, P_INC) ? BO_IADD : BO_ISUB; advance(p); parse_unary(p); + if (!pcg_top_is_modifiable_lvalue(p)) { + perr(p, "increment/decrement requires modifiable lvalue"); + } + if (!c_type_is_scalar(cg_top_type(p->cg))) { + perr(p, "increment/decrement requires scalar operand"); + } cg_inc_dec(p->cg, bop, /*post=*/0); return; } @@ -2101,19 +2135,23 @@ void parse_unary(Parser* p) { expect_punct(p, ')', "')'"); } else { p->last_pushed_vla_slot = FRAME_SLOT_NONE; + pcg_codegen_suppress_push(p); parse_unary(p); ty = cg_top_type(p->cg); vla_slot = p->last_pushed_vla_slot; if (pcg_top_is_bitfield(p)) perr(p, "sizeof bit-field"); cg_drop(p->cg); + pcg_codegen_suppress_pop(p); } } else { p->last_pushed_vla_slot = FRAME_SLOT_NONE; + pcg_codegen_suppress_push(p); parse_unary(p); ty = cg_top_type(p->cg); vla_slot = p->last_pushed_vla_slot; if (pcg_top_is_bitfield(p)) perr(p, "sizeof bit-field"); cg_drop(p->cg); + pcg_codegen_suppress_pop(p); } if (vla_slot != FRAME_SLOT_NONE) { cg_push_local_typed(p->cg, vla_slot, ty_size_t(p)); @@ -2127,27 +2165,53 @@ void parse_unary(Parser* p) { if (is_kw(p, &t, KW_GENERIC)) { advance(p); expect_punct(p, '(', "'('"); + pcg_codegen_suppress_push(p); parse_assign_expr(p); to_rvalue(p); const Type* ctl_ty = cg_top_type(p->cg); cg_drop(p->cg); + pcg_codegen_suppress_pop(p); expect_punct(p, ',', "','"); int emitted = 0; Tok* default_buf = NULL; u32 default_len = 0; + const Type** assoc_types = NULL; + u32 assoc_n = 0; + u32 assoc_cap = 0; + int saw_default = 0; for (;;) { const Type* assoc_ty = NULL; int is_default = 0; if (is_kw(p, &p->cur, KW_DEFAULT)) { advance(p); is_default = 1; + if (saw_default) perr(p, "_Generic has duplicate default association"); + saw_default = 1; } else { assoc_ty = parse_type_name(p); + { + const Type* au = type_unqual(p->pool, assoc_ty); + for (u32 ai = 0; ai < assoc_n; ++ai) { + if (type_compatible(assoc_types[ai], au)) { + perr(p, "_Generic association type is duplicated"); + } + } + if (assoc_n == assoc_cap) { + u32 nc = assoc_cap ? assoc_cap * 2u : 8u; + const Type** nb = arena_array(p->pool->arena, const Type*, nc); + if (!nb) perr(p, "out of memory recording _Generic associations"); + if (assoc_n) memcpy(nb, assoc_types, assoc_n * sizeof(*nb)); + assoc_types = nb; + assoc_cap = nc; + } + assoc_types[assoc_n++] = au; + } } expect_punct(p, ':', "':' in _Generic association"); int take = 0; if (!emitted && !is_default && ctl_ty && assoc_ty && - ctl_ty->kind == assoc_ty->kind) { + type_compatible(type_unqual(p->pool, ctl_ty), + type_unqual(p->pool, assoc_ty))) { take = 1; } if (take) { @@ -2561,6 +2625,9 @@ static void parse_band(Parser* p) { to_rvalue(p); parse_eq(p); to_rvalue(p); + if (!type_is_int(cg_top2_type(p->cg)) || !type_is_int(cg_top_type(p->cg))) { + perr(p, "bitwise operator requires integer operands"); + } cg_binop(p->cg, BO_AND); } } @@ -2572,6 +2639,9 @@ static void parse_bxor(Parser* p) { to_rvalue(p); parse_band(p); to_rvalue(p); + if (!type_is_int(cg_top2_type(p->cg)) || !type_is_int(cg_top_type(p->cg))) { + perr(p, "bitwise operator requires integer operands"); + } cg_binop(p->cg, BO_XOR); } } @@ -2583,6 +2653,9 @@ static void parse_bor(Parser* p) { to_rvalue(p); parse_bxor(p); to_rvalue(p); + if (!type_is_int(cg_top2_type(p->cg)) || !type_is_int(cg_top_type(p->cg))) { + perr(p, "bitwise operator requires integer operands"); + } cg_binop(p->cg, BO_OR); } } @@ -2824,6 +2897,9 @@ void parse_assign_expr(Parser* p) { } else { return; } + if (!pcg_top_is_modifiable_lvalue(p)) { + perr(p, "assignment requires modifiable lvalue"); + } advance(p); const Type* lhs = cg_top_type(p->cg); { @@ -2850,8 +2926,22 @@ void parse_assign_expr(Parser* p) { to_rvalue(p); { const Type* rhs = cg_top_type(p->cg); + int op = '+'; + switch (compound) { + case BO_IADD: op = '+'; break; + case BO_ISUB: op = '-'; break; + case BO_IMUL: op = '*'; break; + case BO_SDIV: op = '/'; break; + case BO_SREM: op = '%'; break; + case BO_AND: op = '&'; break; + case BO_OR: op = '|'; break; + case BO_XOR: op = '^'; break; + case BO_SHL: op = '<'; break; + case BO_SHR_S: op = '>'; break; + default: op = 0; break; + } CSemCheck chk = - c_sem_check_assignment(p->pool, lhs, rhs, C_SEM_ASSIGN_EXPR); + c_sem_check_compound_assignment(p->pool, lhs, rhs, op); if (!chk.ok) perr(p, "%s", chk.message); } if (compound == BO_IADD || compound == BO_ISUB) { diff --git a/lang/c/parse/parse_init.c b/lang/c/parse/parse_init.c @@ -618,6 +618,30 @@ static u64 int_bits_for_type(Parser* p, CConstInt v, const Type* ty) { return bits; } +static void check_static_integer_initializer_range(Parser* p, const Type* ty, + CConstInt v) { + const Type* dst = type_unqual(p->pool, ty); + u32 bits; + if (!dst || !type_is_int(dst) || dst->kind == TY_BOOL) return; + if (!c_abi_type_info(p->abi, dst).signed_) return; + bits = c_abi_sizeof(p->abi, dst) * 8u; + if (bits < 64u) { + i64 minv = -(1ll << (bits - 1u)); + i64 maxv = (1ll << (bits - 1u)) - 1ll; + if (c_abi_type_info(p->abi, v.type).signed_) { + i64 sv = const_int_as_i64(p, v); + if (sv < minv || sv > maxv) { + perr(p, "initializer value overflows destination type"); + } + } else { + u64 maxu = (u64)maxv; + if (v.bits > maxu) { + perr(p, "initializer value overflows destination type"); + } + } + } +} + static CConstInt parse_null_pointer_constant(Parser* p, SrcLoc loc) { if (is_punct(&p->cur, '(')) { Tok n = peek1(p); @@ -741,6 +765,7 @@ static CStaticConst parse_static_const(Parser* p, const Type* ty, SrcLoc loc) { return r; } r.int_value = eval_const_int_typed(p, loc); + check_static_integer_initializer_range(p, ty, r.int_value); return r; } diff --git a/lang/c/parse/parse_type.c b/lang/c/parse/parse_type.c @@ -851,14 +851,10 @@ const Type* parse_enum(Parser* p, Attr** anon_attrs_out) { attr_list_append(&e->attrs, rec_attrs); return e->type; } - TagId tid = type_tag_new(p->pool, TAG_ENUM, tag_name, tag_loc); - const Type* et = type_enum(p->pool, tid, tag_name, ty_int(p)); - { - TagEntry* te = tag_define(p, tag_name, TAG_ENUM, (Type*)et, - /*complete=*/0); - attr_list_append(&te->attrs, rec_attrs); + if (e) { + perr(p, "tag redeclared with wrong kind"); } - return et; + perr(p, "enum tag declared without definition"); } TagId tid = type_tag_new(p->pool, TAG_ENUM, tag_name, tag_loc); const Type* et = type_enum(p->pool, tid, tag_name, ty_int(p)); @@ -894,6 +890,9 @@ const Type* parse_enum(Parser* p, Attr** anon_attrs_out) { if (existing->kind != TAG_ENUM) { perr(p, "tag redeclared with wrong kind"); } + if (existing->complete) { + perr(p, "redefinition of enum tag"); + } existing->complete = 1; attr_list_append(&existing->attrs, rec_attrs); } else { @@ -1096,10 +1095,22 @@ int parse_decl_suffix(Parser* p, DeclSuffix* out) { const Type* apply_decl_suffix(Parser* p, const Type* base, const DeclSuffix* s) { if (s->kind == DS_ARRAY) { + if (base && base->kind == TY_FUNC) { + perr(p, "array of function type is invalid"); + } + if (base && base->kind == TY_VOID) { + perr(p, "array of void type is invalid"); + } return type_array(p->pool, base, s->count, s->incomplete || s->vla); } { const Type** ptypes = NULL; + if (base && base->kind == TY_ARRAY) { + perr(p, "function returning array is invalid"); + } + if (base && base->kind == TY_FUNC) { + perr(p, "function returning function is invalid"); + } if (s->nparams) { ptypes = (const Type**)arena_array(p->pool->arena, const Type*, s->nparams); diff --git a/lang/c/sem/sem.c b/lang/c/sem/sem.c @@ -1,3 +1,5 @@ +#include <string.h> + #include "sem/sem.h" static int is_void_pointer(const Type* t) { @@ -5,6 +7,16 @@ static int is_void_pointer(const Type* t) { t->ptr.pointee->kind == TY_VOID; } +static int is_builtin_va_list_record(Pool* p, const Type* t) { + size_t n = 0; + const char* s; + if (!t || t->kind != TY_STRUCT || !t->rec.tag) return 0; + s = pool_str(p, t->rec.tag, &n); + if (!s) return 0; + return (n == 9 && memcmp(s, "__va_list", 9) == 0) || + (n == 13 && memcmp(s, "__va_list_tag", 13) == 0); +} + static int pointer_pointees_assignable(Pool* p, const Type* lhs, const Type* rhs) { const Type* lp; @@ -14,6 +26,8 @@ static int pointer_pointees_assignable(Pool* p, const Type* lhs, rp = rhs->ptr.pointee; if (!lp || !rp) return 0; if (is_void_pointer(lhs) || is_void_pointer(rhs)) return 1; + if (is_builtin_va_list_record(p, lp) && is_builtin_va_list_record(p, rp)) + return 1; if ((rp->qual & (u16)~lp->qual) != 0) return 0; return type_compatible(type_unqual(p, lp), type_unqual(p, rp)); } @@ -63,6 +77,39 @@ CSemCheck c_sem_check_assignment(Pool* p, const Type* lhs, const Type* rhs, return sem_bad("incompatible assignment"); } +CSemCheck c_sem_check_compound_assignment(Pool* p, const Type* lhs, + const Type* rhs, int op) { + const Type* lu; + const Type* ru; + if (!lhs || !rhs) return sem_ok(); + lu = type_unqual(p, lhs); + ru = type_unqual(p, rhs); + switch (op) { + case '+': + case '-': + if (type_is_ptr(lu)) { + if (type_is_int(ru)) return sem_ok(); + return sem_bad("pointer compound assignment requires integer rhs"); + } + if (type_is_arith(lu) && type_is_arith(ru)) return sem_ok(); + return sem_bad("compound assignment requires arithmetic operands"); + case '*': + case '/': + if (type_is_arith(lu) && type_is_arith(ru)) return sem_ok(); + return sem_bad("compound assignment requires arithmetic operands"); + case '%': + case '&': + case '|': + case '^': + case '<': + case '>': + if (type_is_int(lu) && type_is_int(ru)) return sem_ok(); + return sem_bad("compound assignment requires integer operands"); + default: + return sem_bad("unsupported compound assignment"); + } +} + CSemCheck c_sem_check_redeclaration(Pool* p, const Type* old_type, const Type* new_type, const Type** composite_out) { diff --git a/lang/c/sem/sem.h b/lang/c/sem/sem.h @@ -16,6 +16,8 @@ typedef struct CSemCheck { CSemCheck c_sem_check_assignment(Pool*, const Type* lhs, const Type* rhs, CSemAssignContext); +CSemCheck c_sem_check_compound_assignment(Pool*, const Type* lhs, + const Type* rhs, int op); CSemCheck c_sem_check_redeclaration(Pool*, const Type* old_type, const Type* new_type, const Type** composite_out); diff --git a/test/parse/cases/6_5_59_sizeof_no_eval.c b/test/parse/cases/6_5_59_sizeof_no_eval.c @@ -0,0 +1,5 @@ +int test_main(void) { + int x = 0; + (void)sizeof(x++); + return x == 0 ? 42 : 0; +} diff --git a/test/parse/cases/6_5_59_sizeof_no_eval.expected b/test/parse/cases/6_5_59_sizeof_no_eval.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/6_5_60_generic_compatible_typedef.c b/test/parse/cases/6_5_60_generic_compatible_typedef.c @@ -0,0 +1,6 @@ +typedef int I; + +int test_main(void) { + I x = 0; + return _Generic(x, int: 42, default: 0); +} diff --git a/test/parse/cases/6_5_60_generic_compatible_typedef.expected b/test/parse/cases/6_5_60_generic_compatible_typedef.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/6_5_61_cond_unsigned_conversion.c b/test/parse/cases/6_5_61_cond_unsigned_conversion.c @@ -0,0 +1,4 @@ +int test_main(void) { + unsigned int x = 1u; + return (0 ? 2 : x) == 1u ? 42 : 0; +} diff --git a/test/parse/cases/6_5_61_cond_unsigned_conversion.expected b/test/parse/cases/6_5_61_cond_unsigned_conversion.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/6_5_62_compound_assign_ptr_sub.c b/test/parse/cases/6_5_62_compound_assign_ptr_sub.c @@ -0,0 +1,6 @@ +int test_main(void) { + int a[5] = {42, 0, 0, 0, 0}; + int *p = a + 4; + p -= 4; + return *p; +} diff --git a/test/parse/cases/6_5_62_compound_assign_ptr_sub.expected b/test/parse/cases/6_5_62_compound_assign_ptr_sub.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/6_5_63_call_arg_conversion.c b/test/parse/cases/6_5_63_call_arg_conversion.c @@ -0,0 +1,7 @@ +static int f(double x) { + return (int)x; +} + +int test_main(void) { + return f(42); +} diff --git a/test/parse/cases/6_5_63_call_arg_conversion.expected b/test/parse/cases/6_5_63_call_arg_conversion.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/6_5_64_generic_control_no_eval.c b/test/parse/cases/6_5_64_generic_control_no_eval.c @@ -0,0 +1,5 @@ +int test_main(void) { + int x = 0; + (void)_Generic(x++, int: 1, default: 0); + return x == 0 ? 42 : 0; +} diff --git a/test/parse/cases/6_5_64_generic_control_no_eval.expected b/test/parse/cases/6_5_64_generic_control_no_eval.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases_err/6_5_bitwise_float.c b/test/parse/cases_err/6_5_bitwise_float.c @@ -0,0 +1,4 @@ +int test_main(void) { + double x = 1.0; + return x & 1; +} diff --git a/test/parse/cases_err/6_5_bitwise_float.errpat b/test/parse/cases_err/6_5_bitwise_float.errpat @@ -0,0 +1 @@ +bitwise operator requires integer operands diff --git a/test/parse/cases_err/6_5_call_arg_incompatible.c b/test/parse/cases_err/6_5_call_arg_incompatible.c @@ -0,0 +1,10 @@ +struct S { int x; }; + +static int f(int x) { + return x; +} + +int test_main(void) { + struct S s = {42}; + return f(s); +} diff --git a/test/parse/cases_err/6_5_call_arg_incompatible.errpat b/test/parse/cases_err/6_5_call_arg_incompatible.errpat @@ -0,0 +1 @@ +incompatible assignment diff --git a/test/parse/cases_err/6_5_generic_duplicate_compatible.c b/test/parse/cases_err/6_5_generic_duplicate_compatible.c @@ -0,0 +1,3 @@ +int test_main(void) { + return _Generic(0, int: 1, signed int: 2); +} diff --git a/test/parse/cases_err/6_5_generic_duplicate_compatible.errpat b/test/parse/cases_err/6_5_generic_duplicate_compatible.errpat @@ -0,0 +1 @@ +_Generic association type is duplicated diff --git a/test/parse/cases_err/6_5_pointer_compound_bad.c b/test/parse/cases_err/6_5_pointer_compound_bad.c @@ -0,0 +1,8 @@ +int test_main(void) { + int a = 0; + int b = 0; + int *p = &a; + int *q = &b; + p += q; + return 0; +} diff --git a/test/parse/cases_err/6_5_pointer_compound_bad.errpat b/test/parse/cases_err/6_5_pointer_compound_bad.errpat @@ -0,0 +1 @@ +pointer compound assignment requires integer rhs diff --git a/test/parse/cases_err/6_7_2_enum_forward.c b/test/parse/cases_err/6_7_2_enum_forward.c @@ -0,0 +1,5 @@ +enum E; + +int test_main(void) { + return 0; +} diff --git a/test/parse/cases_err/6_7_2_enum_forward.errpat b/test/parse/cases_err/6_7_2_enum_forward.errpat @@ -0,0 +1 @@ +enum tag declared without definition diff --git a/test/parse/cases_err/6_7_2_enum_redefinition.c b/test/parse/cases_err/6_7_2_enum_redefinition.c @@ -0,0 +1,6 @@ +enum E { A }; +enum E { B }; + +int test_main(void) { + return 0; +} diff --git a/test/parse/cases_err/6_7_2_enum_redefinition.errpat b/test/parse/cases_err/6_7_2_enum_redefinition.errpat @@ -0,0 +1 @@ +redefinition of enum tag diff --git a/test/parse/cases_err/6_7_2_enum_wrong_kind.c b/test/parse/cases_err/6_7_2_enum_wrong_kind.c @@ -0,0 +1,6 @@ +struct T; +enum T { A }; + +int test_main(void) { + return 0; +} diff --git a/test/parse/cases_err/6_7_2_enum_wrong_kind.errpat b/test/parse/cases_err/6_7_2_enum_wrong_kind.errpat @@ -0,0 +1 @@ +tag redeclared with wrong kind diff --git a/test/parse/cases_err/6_7_6_array_of_function.c b/test/parse/cases_err/6_7_6_array_of_function.c @@ -0,0 +1,6 @@ +typedef int F(void); +F a[3]; + +int test_main(void) { + return 0; +} diff --git a/test/parse/cases_err/6_7_6_array_of_function.errpat b/test/parse/cases_err/6_7_6_array_of_function.errpat @@ -0,0 +1 @@ +array of function type is invalid diff --git a/test/parse/cases_err/6_7_6_variadic_no_param.c b/test/parse/cases_err/6_7_6_variadic_no_param.c @@ -0,0 +1,5 @@ +int f(...); + +int test_main(void) { + return 0; +} diff --git a/test/parse/cases_err/6_7_6_variadic_no_param.errpat b/test/parse/cases_err/6_7_6_variadic_no_param.errpat @@ -0,0 +1 @@ +ellipsis requires a preceding parameter diff --git a/test/parse/cases_err/6_7_9_static_signed_char_overflow.c b/test/parse/cases_err/6_7_9_static_signed_char_overflow.c @@ -0,0 +1,5 @@ +static signed char c = 128; + +int test_main(void) { + return c; +} diff --git a/test/parse/cases_err/6_7_9_static_signed_char_overflow.errpat b/test/parse/cases_err/6_7_9_static_signed_char_overflow.errpat @@ -0,0 +1 @@ +initializer value overflows destination type