kit

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

commit efce37cf44757ee952a79b3d98f42096b6c1b65a
parent 5db3a3fbdfc23fa85e50ab5f38f7170f7f74c906
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 18 May 2026 14:36:21 -0700

Add C semantic checks and declaration state

Diffstat:
Mdoc/C11_CONFORMANCE_CHECKLIST.md | 19+++++++++++++++----
Mlang/c/decl/decl.h | 8++++++++
Mlang/c/parse/parse.c | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mlang/c/parse/parse_expr.c | 12+++++++++---
Mlang/c/parse/parse_init.c | 10++++++++++
Mlang/c/parse/parse_priv.h | 14+++++++++++---
Mlang/c/parse/parse_stmt.c | 12++++++++++++
Mlang/c/parse/parse_type.c | 6+++++-
Alang/c/sem/sem.c | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alang/c/sem/sem.h | 23+++++++++++++++++++++++
Mlang/c/type/type.c | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mlang/c/type/type.h | 1+
Atest/parse/cases_err/6_2_7_conflicting_object_types.c | 6++++++
Atest/parse/cases_err/6_2_7_conflicting_object_types.errpat | 1+
Atest/parse/cases_err/6_7_same_scope_redefinition.c | 5+++++
Atest/parse/cases_err/6_7_same_scope_redefinition.errpat | 1+
Atest/parse/cases_err/6_9_conflicting_function_types.c | 6++++++
Atest/parse/cases_err/6_9_conflicting_function_types.errpat | 1+
Atest/parse/cases_err/6_9_duplicate_parameter.c | 7+++++++
Atest/parse/cases_err/6_9_duplicate_parameter.errpat | 1+
20 files changed, 418 insertions(+), 25 deletions(-)

diff --git a/doc/C11_CONFORMANCE_CHECKLIST.md b/doc/C11_CONFORMANCE_CHECKLIST.md @@ -96,15 +96,20 @@ CFREE_TEST_FILTER=asm_02_file_scope make test-parse ## Type system and declarations -- [ ] Implement enough structural compatibility for redeclarations and +- [x] Implement enough structural compatibility for redeclarations and composite types beyond pointer identity. - Existing planned cases: `6_2_7_01_composite_array_size`, + Covered cases: `6_2_7_01_composite_array_size`, `6_2_2_01_extern_in_block_inherits_internal`. -- [ ] Track declaration state separately from scope lookup: + Code: `type_compatible`, `type_composite`, and + `c_sem_check_redeclaration`. +- [x] Track declaration state for ordinary identifiers: declaration, tentative definition, definition, function definition, linkage, storage duration, and type compatibility. -- [ ] Add same-scope ordinary identifier redefinition checks while preserving + Code: parser `SymEntry` state plus `DSTATE_*`. +- [x] Add same-scope ordinary identifier redefinition checks while preserving 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 completion, and wrong-kind redeclarations. - [ ] Validate function declarator constraints: @@ -120,6 +125,8 @@ CFREE_TEST_FILTER=asm_02_file_scope make test-parse - [ ] 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, array, function designator, and incomplete type. - [ ] Implement `sizeof` rules completely: @@ -200,8 +207,12 @@ 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. 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`/constant-expression/static-initializer semantics. 6. Unskip `long double` or explicitly narrow the supported C profile until diff --git a/lang/c/decl/decl.h b/lang/c/decl/decl.h @@ -44,6 +44,14 @@ typedef enum DeclLinkage { DL_EXTERNAL, } DeclLinkage; +typedef enum DeclState { + DSTATE_NONE, + DSTATE_DECLARED, + DSTATE_TENTATIVE, + DSTATE_DEFINED, + DSTATE_FUNC_DEFINED, +} DeclState; + typedef enum DeclFlag { DF_NONE = 0, DF_THREAD = 1u << 0, diff --git a/lang/c/parse/parse.c b/lang/c/parse/parse.c @@ -313,6 +313,15 @@ SymEntry* scope_define(Parser* p, Sym name, SymEntryKind kind, return e; } +SymEntry* scope_lookup_current(Parser* p, Sym name) { + SymEntry* e; + if (!p->scope) return NULL; + for (e = p->scope->entries; e; e = e->next) { + if (e->name == name) return e; + } + return NULL; +} + SymEntry* scope_lookup(Parser* p, Sym name) { Scope* s; for (s = p->scope; s; s = s->parent) { @@ -324,6 +333,33 @@ SymEntry* scope_lookup(Parser* p, Sym name) { return NULL; } +static void sym_set_decl(SymEntry* e, DeclId id, DeclStorage storage, + DeclLinkage linkage, u32 flags, DeclState state) { + e->decl_id = id; + e->storage = (u8)storage; + e->linkage = (u8)linkage; + e->decl_flags = flags; + e->decl_state = (u8)state; + e->defined = (state == DSTATE_DEFINED || state == DSTATE_FUNC_DEFINED); +} + +static int is_ordinary_decl_kind(SymEntryKind k) { + return k == SEK_LOCAL || k == SEK_GLOBAL || k == SEK_FUNC || + k == SEK_TYPEDEF || k == SEK_ENUM_CST; +} + +static void reject_same_scope_redefinition(Parser* p, Sym name, + SymEntryKind kind, + const Type* type) { + SymEntry* e = scope_lookup_current(p, name); + if (!e || !is_ordinary_decl_kind((SymEntryKind)e->kind)) return; + if (e->kind == SEK_TYPEDEF && kind == SEK_TYPEDEF && + type_compatible(e->type, type)) { + return; + } + perr(p, "redefinition of identifier"); +} + TagEntry* tag_define(Parser* p, Sym name, TagDeclKind kind, Type* type, int complete) { TagEntry* e = arena_new(p->pool->arena, TagEntry); @@ -384,8 +420,10 @@ FrameSlot make_local_aligned(Parser* p, Sym name, const Type* type, SrcLoc loc, fsd.kind = FS_LOCAL; fsd.flags = FSF_NONE; s = cg_local(p->cg, &fsd); + reject_same_scope_redefinition(p, name, SEK_LOCAL, type); e = scope_define(p, name, SEK_LOCAL, type); e->v.slot = s; + sym_set_decl(e, DECL_NONE, DS_AUTO, DL_NONE, DF_NONE, DSTATE_DEFINED); return s; } @@ -437,7 +475,10 @@ static void parse_init_declarator(Parser* p, const DeclSpecs* specs) { perr(p, "typedef declarator cannot have initializer"); } { + reject_same_scope_redefinition(p, name, SEK_TYPEDEF, var_ty); SymEntry* e = scope_define(p, name, SEK_TYPEDEF, var_ty); + sym_set_decl(e, DECL_NONE, DS_TYPEDEF, DL_NONE, specs->flags, + DSTATE_DECLARED); if (p->vla_pending && var_ty && var_ty->kind == TY_ARRAY) { FrameSlot count_slot = p->vla_pending_count_slot; const Type* elem_ty = var_ty->arr.elem; @@ -491,8 +532,10 @@ static void parse_init_declarator(Parser* p, const DeclSpecs* specs) { attr_list_to_decl(p->c, p->decls, specs->attrs, &decl_in); did = decl_declare(p->decls, &decl_in); sym = decl_obj_sym(p->decls, did); + reject_same_scope_redefinition(p, name, SEK_GLOBAL, var_ty); e = scope_define(p, name, SEK_GLOBAL, var_ty); e->v.sym = sym; + sym_set_decl(e, did, DS_STATIC, DL_NONE, decl_in.flags, DSTATE_DEFINED); has_init = accept_punct(p, '='); if (has_init && var_ty && var_ty->kind == TY_ARRAY && var_ty->arr.incomplete) { @@ -519,8 +562,19 @@ static void parse_init_declarator(Parser* p, const DeclSpecs* specs) { } prior = scope_lookup(p, name); if (prior && prior->kind == SEK_GLOBAL) { + SymEntry* cur = scope_lookup_current(p, name); + const Type* composite = NULL; + CSemCheck chk = + c_sem_check_redeclaration(p->pool, prior->type, var_ty, &composite); + if (!chk.ok) perr(p, "%s", chk.message); + if (cur && cur->kind != SEK_GLOBAL) { + perr(p, "redefinition of identifier"); + } + if (cur) return; e = scope_define(p, name, SEK_GLOBAL, var_ty); e->v.sym = prior->v.sym; + sym_set_decl(e, prior->decl_id, DS_EXTERN, (DeclLinkage)prior->linkage, + prior->decl_flags, (DeclState)prior->decl_state); return; } memset(&decl_in, 0, sizeof decl_in); @@ -534,8 +588,11 @@ static void parse_init_declarator(Parser* p, const DeclSpecs* specs) { attr_list_to_decl(p->c, p->decls, specs->attrs, &decl_in); did = decl_declare(p->decls, &decl_in); sym = decl_obj_sym(p->decls, did); + reject_same_scope_redefinition(p, name, SEK_GLOBAL, var_ty); e = scope_define(p, name, SEK_GLOBAL, var_ty); e->v.sym = sym; + sym_set_decl(e, did, DS_EXTERN, DL_EXTERNAL, decl_in.flags, + DSTATE_DECLARED); return; } @@ -617,6 +674,12 @@ static void parse_init_declarator(Parser* p, const DeclSpecs* specs) { cg_push_local_typed(p->cg, s, var_ty); parse_assign_expr(p); to_rvalue(p); + { + const Type* rhs = cg_top_type(p->cg); + CSemCheck chk = + c_sem_check_assignment(p->pool, var_ty, rhs, C_SEM_ASSIGN_INIT); + if (!chk.ok) perr(p, "%s", chk.message); + } coerce_top_to_lvalue(p); cg_store(p->cg); cg_drop(p->cg); @@ -683,6 +746,11 @@ 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"); } + if (pname) { + for (u32 pi = 0; pi < n; ++pi) { + if (infos[pi].name == pname) perr(p, "redefinition of parameter"); + } + } if (n == cap) { cap *= 2; ParamInfo* nbuf = (ParamInfo*)arena_array(p->pool->arena, ParamInfo, cap); @@ -706,8 +774,19 @@ static SymEntry* declare_function(Parser* p, Sym fname, const Type* fn_ty, if (out_section_id) *out_section_id = OBJ_SEC_NONE; if (out_decl_flags) *out_decl_flags = 0; if (out_alias_target) *out_alias_target = 0; - SymEntry* existing = scope_lookup(p, fname); + SymEntry* existing = scope_lookup_current(p, fname); + if (existing && existing->kind != SEK_FUNC) { + perr(p, "redefinition of identifier"); + } if (existing && existing->kind == SEK_FUNC) { + const Type* composite = NULL; + CSemCheck chk = + c_sem_check_redeclaration(p->pool, existing->type, fn_ty, &composite); + if (!chk.ok) perr(p, "%s", chk.message); + if (specs->storage == DS_STATIC && existing->linkage == DL_EXTERNAL) { + perr(p, "static declaration follows non-static declaration"); + } + existing->type = composite ? composite : existing->type; Decl tmp; memset(&tmp, 0, sizeof tmp); attr_list_to_decl(p->c, p->decls, specs->attrs, &tmp); @@ -735,6 +814,8 @@ static SymEntry* declare_function(Parser* p, Sym fname, const Type* fn_ty, fsym = decl_obj_sym(p->decls, did); e = scope_define(p, fname, SEK_FUNC, fn_ty); e->v.sym = fsym; + sym_set_decl(e, did, decl_in.storage, decl_in.linkage, decl_in.flags, + DSTATE_DECLARED); if (out_section_id) *out_section_id = decl_in.section_id; if (out_decl_flags) *out_decl_flags = decl_in.flags; if (out_alias_target) *out_alias_target = decl_in.alias_target; @@ -799,8 +880,11 @@ static void parse_function_body(Parser* p, ObjSymId fsym, const Type* fn_ty, s = pcg_param_slot(p, i, &fsd); pds[i].slot = s; if (infos[i].name) { + reject_same_scope_redefinition(p, infos[i].name, SEK_LOCAL, + infos[i].type); e = scope_define(p, infos[i].name, SEK_LOCAL, infos[i].type); e->v.slot = s; + sym_set_decl(e, DECL_NONE, DS_AUTO, DL_NONE, DF_NONE, DSTATE_DEFINED); } } @@ -846,7 +930,12 @@ static void parse_external_decl(Parser* p) { if (is_punct(&p->cur, '=')) { perr(p, "typedef declarator cannot have initializer"); } - scope_define(p, tname, SEK_TYPEDEF, tty); + reject_same_scope_redefinition(p, tname, SEK_TYPEDEF, tty); + { + SymEntry* te = scope_define(p, tname, SEK_TYPEDEF, tty); + sym_set_decl(te, DECL_NONE, DS_TYPEDEF, DL_NONE, specs.flags, + DSTATE_DECLARED); + } (void)tloc; if (!accept_punct(p, ',')) break; } @@ -905,11 +994,15 @@ static void parse_external_decl(Parser* p) { if (is_punct(&p->cur, '{')) { if (fent->defined) perr(p, "redefinition of function"); fent->defined = 1; + fent->decl_state = DSTATE_FUNC_DEFINED; Sym saved_func_name = p->cur_func_name; + const Type* saved_func_ret = p->cur_func_ret; p->cur_func_name = name; + p->cur_func_ret = fn_ty->fn.ret; parse_function_body(p, fent->v.sym, fn_ty, abi, infos, nparams, loc, fn_section_id, fn_decl_flags); p->cur_func_name = saved_func_name; + p->cur_func_ret = saved_func_ret; return; } if (accept_punct(p, ';')) { @@ -945,23 +1038,30 @@ static void parse_external_decl(Parser* p) { for (;;) { int has_init = is_punct(&p->cur, '='); int is_pure_extern = (specs.storage == DS_EXTERN) && !has_init; - SymEntry* existing = scope_lookup(p, name); + SymEntry* existing = scope_lookup_current(p, name); ObjSymId sym = OBJ_SYM_NONE; ObjSecId section_id = OBJ_SEC_NONE; SymEntry* e = NULL; + if (existing && existing->kind != SEK_GLOBAL) { + perr(p, "redefinition of identifier"); + } + if (existing && existing->kind == SEK_GLOBAL) { + const Type* composite = NULL; + CSemCheck chk = c_sem_check_redeclaration(p->pool, existing->type, + base_ty, &composite); + if (!chk.ok) perr(p, "%s", chk.message); + if (specs.storage == DS_STATIC && existing->linkage == DL_EXTERNAL) { + perr(p, "static declaration follows non-static declaration"); + } sym = existing->v.sym; e = existing; if (has_init && e->defined) { perr(p, "redefinition of object"); } - if (e->type && base_ty && e->type->kind == TY_ARRAY && - base_ty->kind == TY_ARRAY) { - if (e->type->arr.incomplete && !base_ty->arr.incomplete) { - e->type = base_ty; - } - } + if (composite) e->type = composite; + base_ty = e->type; } else { Decl decl_in; DeclId did; @@ -985,6 +1085,8 @@ static void parse_external_decl(Parser* p) { section_id = decl_in.section_id; e = scope_define(p, name, SEK_GLOBAL, base_ty); e->v.sym = sym; + sym_set_decl(e, did, decl_in.storage, decl_in.linkage, decl_in.flags, + DSTATE_DECLARED); } attr_list_append(&e->attrs, dattrs); @@ -997,6 +1099,7 @@ static void parse_external_decl(Parser* p) { if (has_init) { if (e) e->defined = 1; + if (e) e->decl_state = DSTATE_DEFINED; advance(p); /* '=' */ if (base_ty && base_ty->kind == TY_ARRAY && base_ty->arr.incomplete) { const Type* completed = complete_incomplete_array(p, base_ty); @@ -1008,6 +1111,9 @@ static void parse_external_decl(Parser* p) { define_static_object(p, sym, section_id, base_ty, specs.quals, /*has_init=*/1, loc, align_eff); } else if (!is_pure_extern) { + if (e && e->decl_state == DSTATE_DECLARED) { + e->decl_state = DSTATE_TENTATIVE; + } define_static_object(p, sym, section_id, base_ty, specs.quals, /*has_init=*/0, loc, align_eff); } diff --git a/lang/c/parse/parse_expr.c b/lang/c/parse/parse_expr.c @@ -1977,9 +1977,9 @@ void parse_assign_expr(Parser* p) { to_rvalue(p); { const Type* rhs = cg_top_type(p->cg); - if (type_is_ptr(rhs) && type_is_arith(lhs)) { - perr(p, "incompatible assignment from pointer to integer"); - } + CSemCheck chk = + c_sem_check_assignment(p->pool, lhs, rhs, C_SEM_ASSIGN_EXPR); + if (!chk.ok) perr(p, "%s", chk.message); } coerce_top_to_lvalue(p); cg_store(p->cg); @@ -1989,6 +1989,12 @@ void parse_assign_expr(Parser* p) { cg_load(p->cg); parse_assign_expr(p); to_rvalue(p); + { + const Type* rhs = cg_top_type(p->cg); + CSemCheck chk = + c_sem_check_assignment(p->pool, lhs, rhs, C_SEM_ASSIGN_EXPR); + if (!chk.ok) perr(p, "%s", chk.message); + } if (compound == BO_IADD || compound == BO_ISUB) { emit_add_or_sub(p, compound); } else { diff --git a/lang/c/parse/parse_init.c b/lang/c/parse/parse_init.c @@ -343,6 +343,11 @@ static u32 init_elided(Parser* p, FrameSlot slot, const Type* arr_ty, push_subobject_lv(p, slot, arr_ty, offset, ty); parse_assign_expr(p); to_rvalue(p); + { + const Type* rhs = cg_top_type(p->cg); + CSemCheck chk = c_sem_check_assignment(p->pool, ty, rhs, C_SEM_ASSIGN_INIT); + if (!chk.ok) perr(p, "%s", chk.message); + } coerce_top_to_lvalue(p); cg_store(p->cg); cg_drop(p->cg); @@ -454,6 +459,11 @@ void init_at(Parser* p, FrameSlot slot, const Type* arr_ty, u32 offset, push_subobject_lv(p, slot, arr_ty, offset, ty); parse_assign_expr(p); to_rvalue(p); + { + const Type* rhs = cg_top_type(p->cg); + CSemCheck chk = c_sem_check_assignment(p->pool, ty, rhs, C_SEM_ASSIGN_INIT); + if (!chk.ok) perr(p, "%s", chk.message); + } coerce_top_to_lvalue(p); cg_store(p->cg); cg_drop(p->cg); diff --git a/lang/c/parse/parse_priv.h b/lang/c/parse/parse_priv.h @@ -16,6 +16,7 @@ #include "parse/attr.h" #include "parse/parse.h" #include "pp/pp.h" +#include "sem/sem.h" #include "type/type.h" /* ============================================================ @@ -87,9 +88,14 @@ typedef enum SymEntryKind { typedef struct SymEntry SymEntry; struct SymEntry { Sym name; - u8 kind; /* SymEntryKind */ - u8 defined; - u8 pad[2]; + u8 kind; /* SymEntryKind */ + u8 defined; /* compatibility alias for decl_state >= DSTATE_DEFINED */ + u8 decl_state; + u8 storage; /* DeclStorage */ + u8 linkage; /* DeclLinkage */ + u8 pad[3]; + DeclId decl_id; + u32 decl_flags; const Type* type; union { FrameSlot slot; @@ -190,6 +196,7 @@ typedef struct Parser { Sym sym_pretty_func_gcc; /* __PRETTY_FUNCTION__ */ Sym cur_func_name; /* name of the function whose body we're in, * 0 at file scope */ + const Type* cur_func_ret; Sym sym_b_expect; Sym sym_b_offsetof; Sym sym_b_va_list; @@ -299,6 +306,7 @@ void scope_push(Parser* p); void scope_pop(Parser* p); SymEntry* scope_define(Parser* p, Sym name, SymEntryKind kind, const Type* type); +SymEntry* scope_lookup_current(Parser* p, Sym name); SymEntry* scope_lookup(Parser* p, Sym name); TagEntry* tag_define(Parser* p, Sym name, TagDeclKind kind, Type* type, int complete); diff --git a/lang/c/parse/parse_stmt.c b/lang/c/parse/parse_stmt.c @@ -122,11 +122,23 @@ static void parse_for_stmt(Parser* p) { static void parse_return_stmt(Parser* p) { if (accept_punct(p, ';')) { + if (p->cur_func_ret && p->cur_func_ret->kind != TY_VOID) { + perr(p, "return with no value in non-void function"); + } cg_ret(p->cg, 0); return; } + if (p->cur_func_ret && p->cur_func_ret->kind == TY_VOID) { + perr(p, "return with a value in void function"); + } parse_expr(p); to_rvalue(p); + { + const Type* rhs = cg_top_type(p->cg); + CSemCheck chk = c_sem_check_assignment(p->pool, p->cur_func_ret, rhs, + C_SEM_ASSIGN_RETURN); + if (!chk.ok) perr(p, "%s", chk.message); + } expect_punct(p, ';', "';' after return value"); cg_ret(p->cg, 1); } diff --git a/lang/c/parse/parse_type.c b/lang/c/parse/parse_type.c @@ -720,7 +720,8 @@ static void parse_member_decls(Parser* p, TypeRecordBuilder* b) { if (accept_punct(p, ':')) { i64 w = eval_const_int(p, mloc); if (w < 0) perr(p, "negative bit-field width"); - if (w == 0 && mname != 0) perr(p, "zero-width bit-field must be unnamed"); + if (w == 0 && mname != 0) + perr(p, "zero-width bit-field must be unnamed"); if (w > (i64)c_abi_sizeof(p->abi, mty) * 8) { perr(p, "bit-field width exceeds its type width"); } @@ -874,6 +875,9 @@ const Type* parse_enum(Parser* p, Attr** anon_attrs_out) { if (accept_punct(p, '=')) { val = eval_const_int(p, nloc); } + if (scope_lookup_current(p, name)) { + perr(p, "redefinition of enumerator"); + } e = scope_define(p, name, SEK_ENUM_CST, et); e->v.enum_value = val; next_val = val + 1; diff --git a/lang/c/sem/sem.c b/lang/c/sem/sem.c @@ -0,0 +1,76 @@ +#include "sem/sem.h" + +static int is_void_pointer(const Type* t) { + return t && t->kind == TY_PTR && t->ptr.pointee && + t->ptr.pointee->kind == TY_VOID; +} + +static int pointer_pointees_assignable(Pool* p, const Type* lhs, + const Type* rhs) { + const Type* lp; + const Type* rp; + if (!lhs || !rhs || lhs->kind != TY_PTR || rhs->kind != TY_PTR) return 0; + lp = lhs->ptr.pointee; + rp = rhs->ptr.pointee; + if (!lp || !rp) return 0; + if (is_void_pointer(lhs) || is_void_pointer(rhs)) return 1; + if ((rp->qual & (u16)~lp->qual) != 0) return 0; + return type_compatible(type_unqual(p, lp), type_unqual(p, rp)); +} + +static CSemCheck sem_ok(void) { + CSemCheck c; + c.ok = 1; + c.message = NULL; + return c; +} + +static CSemCheck sem_bad(const char* msg) { + CSemCheck c; + c.ok = 0; + c.message = msg; + return c; +} + +CSemCheck c_sem_check_assignment(Pool* p, const Type* lhs, const Type* rhs, + CSemAssignContext ctx) { + const Type* lu; + const Type* ru; + (void)ctx; + if (!lhs || !rhs) return sem_ok(); + lu = type_unqual(p, lhs); + ru = type_unqual(p, rhs); + if (lu->kind == TY_VOID) + return sem_bad("assignment to an object with void type"); + if (lu->kind == TY_ARRAY) return sem_bad("assignment to array type"); + if (type_is_arith(lu) && type_is_arith(ru)) return sem_ok(); + if ((lu->kind == TY_STRUCT || lu->kind == TY_UNION) && + type_compatible(lu, ru)) { + return sem_ok(); + } + if (type_is_ptr(lu)) { + if (type_is_ptr(ru)) { + if (pointer_pointees_assignable(p, lu, ru)) { + return sem_ok(); + } + return sem_bad("assignment between incompatible pointer types"); + } + if (type_is_int(ru)) return sem_ok(); + return sem_bad("incompatible assignment to pointer"); + } + if (type_is_ptr(ru) && type_is_arith(lu)) + return sem_bad("incompatible assignment from pointer to integer"); + return sem_bad("incompatible assignment"); +} + +CSemCheck c_sem_check_redeclaration(Pool* p, const Type* old_type, + const Type* new_type, + const Type** composite_out) { + const Type* composite; + if (composite_out) *composite_out = old_type; + if (!old_type || !new_type) return sem_ok(); + composite = type_composite(p, old_type, new_type); + if (!composite) return sem_bad("conflicting types for declaration"); + if (composite_out) *composite_out = composite; + return sem_ok(); +} diff --git a/lang/c/sem/sem.h b/lang/c/sem/sem.h @@ -0,0 +1,23 @@ +#ifndef CFREE_C_SEM_H +#define CFREE_C_SEM_H + +#include "type/type.h" + +typedef enum CSemAssignContext { + C_SEM_ASSIGN_EXPR, + C_SEM_ASSIGN_INIT, + C_SEM_ASSIGN_RETURN, +} CSemAssignContext; + +typedef struct CSemCheck { + u8 ok; + const char* message; +} CSemCheck; + +CSemCheck c_sem_check_assignment(Pool*, const Type* lhs, const Type* rhs, + CSemAssignContext); +CSemCheck c_sem_check_redeclaration(Pool*, const Type* old_type, + const Type* new_type, + const Type** composite_out); + +#endif diff --git a/lang/c/type/type.c b/lang/c/type/type.c @@ -284,8 +284,23 @@ const Type* type_enum(Pool* p, TagId tag_id, Sym tag, const Type* base) { /* ---- predicates / utilities ---- */ const Type* type_unqual(Pool* p, const Type* t) { + PoolTypeCache* c; + Type tmpl; + Type* nt; if (!t || t->qual == 0) return t; - return type_qualified(p, t, 0); + if ((unsigned)t->kind < NUM_PRIM_KINDS) + return type_prim(p, (TypeKind)t->kind); + c = cache_get(p); + if (!c) return NULL; + tmpl = *t; + tmpl.qual = 0; + for (TypeListNode* n = c->derived; n; n = n->next) { + if (memcmp(&n->ty, &tmpl, sizeof(Type)) == 0) return &n->ty; + } + nt = alloc_type_node(p, c); + if (!nt) return NULL; + *nt = tmpl; + return nt; } const Type* type_promoted(Pool* p, const Type* t) { @@ -303,13 +318,98 @@ const Type* type_promoted(Pool* p, const Type* t) { } } -int type_compatible(const Type* a, const Type* b) { +static int type_compatible_inner(const Type* a, const Type* b, unsigned depth) { + u16 i; + if (depth > 64) return 0; if (a == b) return 1; if (!a || !b) return 0; + if (a->qual != b->qual) return 0; if (a->kind != b->kind) return 0; - /* Strict structural compatibility past identity is parser territory; v1 - * relies on interning for the common cases. */ - return 0; + switch (a->kind) { + case TY_VOID: + case TY_BOOL: + case TY_CHAR: + case TY_SCHAR: + case TY_UCHAR: + case TY_SHORT: + case TY_USHORT: + case TY_INT: + case TY_UINT: + case TY_LONG: + case TY_ULONG: + case TY_LLONG: + case TY_ULLONG: + case TY_INT128: + case TY_UINT128: + case TY_FLOAT: + case TY_DOUBLE: + case TY_LDOUBLE: + return 1; + case TY_PTR: + return type_compatible_inner(a->ptr.pointee, b->ptr.pointee, depth + 1); + case TY_ARRAY: + if (!type_compatible_inner(a->arr.elem, b->arr.elem, depth + 1)) return 0; + if (a->arr.incomplete || b->arr.incomplete) return 1; + return a->arr.count == b->arr.count; + case TY_FUNC: + if (a->fn.variadic != b->fn.variadic) return 0; + if (a->fn.nparams != b->fn.nparams) return 0; + if (!type_compatible_inner(a->fn.ret, b->fn.ret, depth + 1)) return 0; + for (i = 0; i < a->fn.nparams; ++i) { + if (!type_compatible_inner(a->fn.params[i], b->fn.params[i], depth + 1)) + return 0; + } + return 1; + case TY_STRUCT: + case TY_UNION: + return a->rec.tag_id != TAG_NONE && a->rec.tag_id == b->rec.tag_id; + case TY_ENUM: + if (a->enm.tag_id != TAG_NONE || b->enm.tag_id != TAG_NONE) + return a->enm.tag_id == b->enm.tag_id; + return type_compatible_inner(a->enm.base, b->enm.base, depth + 1); + default: + return 0; + } +} + +int type_compatible(const Type* a, const Type* b) { + return type_compatible_inner(a, b, 0); +} + +const Type* type_composite(Pool* p, const Type* a, const Type* b) { + const Type* elem; + const Type** params; + u16 i; + if (!type_compatible(a, b)) return NULL; + if (a == b) return a; + if (!a || !b) return NULL; + switch (a->kind) { + case TY_ARRAY: + elem = type_composite(p, a->arr.elem, b->arr.elem); + if (!elem) elem = a->arr.elem; + if (a->arr.incomplete && !b->arr.incomplete) + return type_array(p, elem, b->arr.count, 0); + if (b->arr.incomplete && !a->arr.incomplete) + return type_array(p, elem, a->arr.count, 0); + return type_array(p, elem, a->arr.count, a->arr.incomplete); + case TY_PTR: { + const Type* pointee = type_composite(p, a->ptr.pointee, b->ptr.pointee); + if (!pointee) pointee = a->ptr.pointee; + return type_ptr(p, pointee); + } + case TY_FUNC: + if (a->fn.nparams == 0) return a; + params = arena_array(p->arena, const Type*, a->fn.nparams); + if (!params) return NULL; + for (i = 0; i < a->fn.nparams; ++i) { + params[i] = type_composite(p, a->fn.params[i], b->fn.params[i]); + if (!params[i]) params[i] = a->fn.params[i]; + } + return type_func(p, type_composite(p, a->fn.ret, b->fn.ret), params, + a->fn.nparams, a->fn.variadic); + default: + return a; + } } int type_is_int(const Type* t) { diff --git a/lang/c/type/type.h b/lang/c/type/type.h @@ -165,6 +165,7 @@ const Type* type_enum(Pool*, TagId, Sym tag, const Type* base); const Type* type_unqual(Pool*, const Type*); const Type* type_promoted(Pool*, const Type*); int type_compatible(const Type*, const Type*); +const Type* type_composite(Pool*, const Type*, const Type*); int type_is_arith(const Type*); int type_is_int(const Type*); int type_is_ptr(const Type*); diff --git a/test/parse/cases_err/6_2_7_conflicting_object_types.c b/test/parse/cases_err/6_2_7_conflicting_object_types.c @@ -0,0 +1,6 @@ +extern int a[]; +extern long a[]; + +int test_main(void) { + return 0; +} diff --git a/test/parse/cases_err/6_2_7_conflicting_object_types.errpat b/test/parse/cases_err/6_2_7_conflicting_object_types.errpat @@ -0,0 +1 @@ +conflicting types for declaration diff --git a/test/parse/cases_err/6_7_same_scope_redefinition.c b/test/parse/cases_err/6_7_same_scope_redefinition.c @@ -0,0 +1,5 @@ +int test_main(void) { + int x; + int x; + return x; +} diff --git a/test/parse/cases_err/6_7_same_scope_redefinition.errpat b/test/parse/cases_err/6_7_same_scope_redefinition.errpat @@ -0,0 +1 @@ +redefinition of identifier diff --git a/test/parse/cases_err/6_9_conflicting_function_types.c b/test/parse/cases_err/6_9_conflicting_function_types.c @@ -0,0 +1,6 @@ +int f(int); +int f(long); + +int test_main(void) { + return 0; +} diff --git a/test/parse/cases_err/6_9_conflicting_function_types.errpat b/test/parse/cases_err/6_9_conflicting_function_types.errpat @@ -0,0 +1 @@ +conflicting types for declaration diff --git a/test/parse/cases_err/6_9_duplicate_parameter.c b/test/parse/cases_err/6_9_duplicate_parameter.c @@ -0,0 +1,7 @@ +int f(int x, int x) { + return x; +} + +int test_main(void) { + return f(1, 2); +} diff --git a/test/parse/cases_err/6_9_duplicate_parameter.errpat b/test/parse/cases_err/6_9_duplicate_parameter.errpat @@ -0,0 +1 @@ +redefinition of parameter