#include #include #include #include #include #include #include "ast.h" #include "io.h" #include "limits.h" #include "module.h" #include "resolver.h" #include "riscv.h" #include "strings.h" #include "symtab.h" #include "util.h" #define max(a, b) (a > b ? a : b) #define DEFAULT_SIZE 4 #define DEFAULT_ALIGN 4 static type_t *alloc_type( resolve_t *t, typeclass_t kind, const char *name, usize namel, i32 size, i32 align ); static type_t *alloc_array_type(resolve_t *t, type_t *elem, usize length); static type_t *alloc_slice_type( resolve_t *t, type_t *elem, type_t *base, bool mut ); static type_t *alloc_union_type(resolve_t *t, union_decl_t *uni); static type_t *alloc_result_type(resolve_t *t, type_t *payload, type_t *err); static type_t *alloc_record_type(resolve_t *t, record_decl_t *rec); static type_t *alloc_anonymous_record_type(resolve_t *t); static type_t *alloc_ptr_type(resolve_t *t, type_t *base, bool mut); static type_t *alloc_opt_type(resolve_t *t, type_t *elem); static type_t *resolve_node(resolve_t *t, node_t *n, type_t *expected_type); static type_t *resolve_var(resolve_t *t, node_t *n); static type_t *resolve_const(resolve_t *t, node_t *n); static type_t *resolve_static(resolve_t *t, node_t *n); static type_t *resolve_use(resolve_t *t, node_t *n); static type_t *resolve_mod_decl(resolve_t *t, node_t *n); static bool resolve_mod_def(resolve_t *t, module_t *module); static type_t *resolve_scope(resolve_t *t, node_t *n); static type_t *resolve_block(resolve_t *t, node_t *n); static type_t *resolve_fn_def(resolve_t *t, node_t *n); static type_t *resolve_fn_decl(resolve_t *t, node_t *n); static type_t *resolve_number(resolve_t *t, node_t *n, type_t *expected); static type_t *resolve_builtin(resolve_t *t, node_t *n, type_t *expected); static bool resolve_decls(resolve_t *t, module_t *module); static type_t *resolve_throw(resolve_t *t, node_t *n); static type_t *resolve_try_expr(resolve_t *t, node_t *n, type_t *expected); static bool declare_record(resolve_t *t, node_t *n); static bool declare_enum(resolve_t *t, node_t *n); static type_t *resolve_tuple_record_constructor( resolve_t *t, node_t *call, type_t *record_type ); static type_t *type_unify( resolve_t *t, type_t *a, type_t *b, node_t *n, bool co, const char *ctx ); static type_t *resolve_type(resolve_t *t, node_t *n); static symbol_t *resolve_name(resolve_t *t, node_t *n, symkind_t kind); static bool resolve_const_usize(resolve_t *t, node_t *expr, usize *value); static bool symbol_add(resolve_t *t, node_t *ident, node_t *n); static void finalize_type_layout(resolve_t *t); static void module_scope_path(node_t *node, char *path_str); static bool node_is_super(const node_t *n); static module_t *module_super_ancestor(module_t *mod, usize depth); static bool node_diverges(node_t *n); /* Initialize type checker. */ void resolve_init(resolve_t *t, module_manager_t *mm) { t->fn = NULL; t->global = symtab_scope(NULL, NULL); t->scope = t->global; t->mm = mm; t->module = NULL; t->recordid = 0; t->ctx = TC_CTX_NORMAL; t->types.nsympool = 0; t->types.ntypepool = 0; t->types.type_bool = alloc_type(t, TYPE_BOOL, "bool", 4, 1, 1); t->types.type_char = alloc_type(t, TYPE_I8, "i8", 2, sizeof(i8), sizeof(i8)); t->types.type_i8 = alloc_type(t, TYPE_I8, "i8", 2, sizeof(i8), sizeof(i8)); t->types.type_i16 = alloc_type(t, TYPE_I16, "i16", 3, sizeof(i16), sizeof(i16)); t->types.type_i32 = alloc_type(t, TYPE_I32, "i32", 3, sizeof(i32), sizeof(i32)); t->types.type_u8 = alloc_type(t, TYPE_U8, "u8", 2, sizeof(u8), sizeof(u8)); t->types.type_u16 = alloc_type(t, TYPE_U16, "u16", 3, sizeof(u16), sizeof(u16)); t->types.type_u32 = alloc_type(t, TYPE_U32, "u32", 3, sizeof(u32), sizeof(u32)); t->types.type_str = alloc_slice_type(t, t->types.type_u8, NULL, false); t->types.type_void = alloc_type(t, TYPE_VOID, "void", 4, 0, 0); t->types.type_opaque = alloc_type(t, TYPE_OPAQUE, "opaque", 6, 0, 0); t->types.type_never = alloc_type(t, TYPE_NEVER, "never", 5, 0, 0); /* Add root module to global scope * so it can be accessed with `::module` */ if (mm->root && mm->root->ast && mm->root->ast->sym) { /* Root module declarations are checked later, so just add the symbol */ symtab_add_symbol(t->global, mm->root->ast->sym); } } symbol_t **types_alloc_sympool(types_t *t, u8 n) { assert(t->nsympool + n <= MAX_SYMPTR_POOL); symbol_t **ptr = &t->sympool[t->nsympool]; t->nsympool += n; return ptr; } type_t **types_alloc_typepool(types_t *t, u8 n) { assert(t->ntypepool + n <= MAX_TYPEPTR_POOL); type_t **ptr = &t->typepool[t->ntypepool]; t->ntypepool += n; return ptr; } type_t *deref_type(type_t *ref) { type_t *target = ref->info.ptr.target; return target; } bool ident_eq(node_t *ident, const char *str, usize len) { const char *ident_str = ident->val.ident.name; usize ident_len = ident->val.ident.length; return ident_len == len && (memcmp(ident_str, str, len) == 0); } static bool node_is_super(const node_t *n) { return n && n->cls == NODE_SUPER; } static module_t *module_super_ancestor(module_t *mod, usize depth) { module_t *current = mod; for (usize i = 0; i < depth; i++) { if (!current || !current->parent) return NULL; current = current->parent; } return current; } inline bool type_is_packed(type_t *t) { switch (t->cls) { case TYPE_RECORD: if ((i32)t->info.srt.packedsize != t->size) { return false; } break; case TYPE_ARRAY: case TYPE_SLICE: default: break; } return true; } inline bool type_is_numeric(typeclass_t t) { return t >= TYPE_I8 && t <= TYPE_U32; } inline bool type_is_address(typeclass_t t) { return t == TYPE_PTR || t == TYPE_SLICE || t == TYPE_FN; } inline bool type_is_compound(type_t *t) { typeclass_t cls = t->cls; return cls == TYPE_ARRAY || cls == TYPE_RECORD || cls == TYPE_SLICE || cls == TYPE_PTR || cls == TYPE_OPT || cls == TYPE_RESULT || cls == TYPE_FN || type_is_union_with_payload(t); } inline bool type_is_passed_by_ref(type_t *t) { typeclass_t cls = t->cls; return cls == TYPE_ARRAY || cls == TYPE_RECORD || cls == TYPE_SLICE || cls == TYPE_OPT || cls == TYPE_RESULT || type_is_union_with_payload(t); } inline bool type_is_union_with_payload(type_t *ty) { return ty->cls == TYPE_UNION && ty->info.uni.has_payload; } inline bool type_is_tagged_value(type_t *ty) { return ty->cls == TYPE_OPT || ty->cls == TYPE_RESULT || type_is_union_with_payload(ty); } inline bool type_is_primitive(type_t *t) { return !type_is_compound(t); } inline bool type_is_int(typeclass_t t) { return t >= TYPE_I8 && t <= TYPE_U32; } inline bool type_is_unsigned(typeclass_t t) { return t == TYPE_U8 || t == TYPE_U16 || t == TYPE_U32; } bool type_coercible(type_t *a, type_t *b) { if (a == b) return true; /* Handle slice coercion: *mut [T] can coerce to *[T] */ if (a->cls == TYPE_SLICE && b->cls == TYPE_SLICE) { if (a->info.slc.elem != b->info.slc.elem) return false; /* Mutable can coerce to immutable, but not vice versa */ if (!a->info.slc.mut && b->info.slc.mut) return false; return true; } /* Handle pointer coercion: *mut T can coerce to *T */ if (a->cls == TYPE_PTR && b->cls == TYPE_PTR) { if (a->info.ptr.target != b->info.ptr.target) return false; if (!a->info.ptr.mut && b->info.ptr.mut) return false; return true; } return false; } /* Unify two types, attempting to find the most general unifier. * Returns the unified type on success, or `NULL` if types cannot be unified. * If `n` and `context` are provided, reports an error on failure. */ static type_t *type_unify( resolve_t *t, type_t *a, type_t *b, node_t *n, /* Node to report error on, or NULL for silent */ bool coerce, /* Allow safe type coercion */ const char *context /* Context string for error message */ ) { /* If the pointers are equal, they're already unified */ if (a == b) return a; /* If they are both `NULL`, there's nothing we can do */ if (!a && !b) return NULL; /* Treat `never` as compatible with any type. */ if (a && a->cls == TYPE_NEVER) return b ? b : a; if (b && b->cls == TYPE_NEVER) return a ? a : b; /* If one type is NULL, create optional of the other type */ if (!a && b && (b->cls == TYPE_OPT)) return alloc_opt_type(t, b); if (!b && a && (a->cls == TYPE_OPT)) return alloc_opt_type(t, a); /* If either type is `NULL` and the other is not an optional, bail, * because we have an error. */ if (!a || !b) { return NULL; } /* Handle coercion of T to ?T */ if (coerce) { if (b->cls == TYPE_OPT && a->cls != TYPE_OPT) { if (type_unify(t, a, b->info.opt.elem, n, coerce, context)) { return b; /* a unifies with ?T's element, result is ?T */ } /* Try to unify a with the optional's element type */ type_t *unified = type_unify(t, a, b->info.opt.elem, NULL, coerce, NULL); if (unified) { return alloc_opt_type(t, unified); } } } /* Handle pointer types */ if (a->cls == TYPE_PTR && b->cls == TYPE_PTR) { /* Allow coercion from *T to *opaque and *mut T to *mut opaque */ if (coerce && (a->info.ptr.target->cls == TYPE_OPAQUE || b->info.ptr.target->cls == TYPE_OPAQUE)) { return a->info.ptr.target->cls == TYPE_OPAQUE ? a : b; } type_t *unified = type_unify( t, a->info.ptr.target, b->info.ptr.target, NULL, coerce, NULL ); if (unified) { /* When coercing *mut T to *T, prefer immutable target */ if (coerce && a->info.ptr.mut && !b->info.ptr.mut) { return b; } if (unified == a->info.ptr.target) { return a; } else if (unified == b->info.ptr.target) { return b; } else { return alloc_ptr_type(t, unified, a->info.ptr.mut); } } goto error; } /* Handle numeric type unification - promote to wider type */ if (type_is_numeric(a->cls) && type_is_numeric(b->cls)) { /* Return the "wider" type based on size and signedness */ if (a->size > b->size) { return a; } else if (b->size > a->size) { return b; } else { /* Same size - prefer unsigned over signed */ if ((a->cls >= TYPE_U8 && a->cls <= TYPE_U32) && (b->cls >= TYPE_I8 && b->cls <= TYPE_I32)) { return a; /* a is unsigned, b is signed */ } else if ((b->cls >= TYPE_U8 && b->cls <= TYPE_U32) && (a->cls >= TYPE_I8 && a->cls <= TYPE_I32)) { return b; /* b is unsigned, a is signed */ } else { return a; /* Default to first type if same category */ } } } /* Handle array types */ if (a->cls == TYPE_ARRAY && b->cls == TYPE_ARRAY) { /* Arrays must have same length to unify */ if (a->info.ary.length != b->info.ary.length) { goto error; } /* Unify element types */ type_t *unified = type_unify( t, a->info.ary.elem, b->info.ary.elem, NULL, false, NULL ); if (unified) { /* If element types are already the same, return existing array */ if (unified == a->info.ary.elem) { return a; } else if (unified == b->info.ary.elem) { return b; } else { return alloc_array_type(t, unified, a->info.ary.length); } } goto error; } /* Handle slice types */ if (a->cls == TYPE_SLICE && b->cls == TYPE_SLICE) { /* Allow coercion from *[T] to *[opaque] */ if (coerce && (a->info.slc.elem->cls == TYPE_OPAQUE || b->info.slc.elem->cls == TYPE_OPAQUE)) { return a->info.slc.elem->cls == TYPE_OPAQUE ? a : b; } type_t *unified = type_unify( t, a->info.slc.elem, b->info.slc.elem, NULL, false, NULL ); if (unified) { /* When coercing *mut [T] to *[T], prefer immutable target */ if (coerce && a->info.slc.mut && !b->info.slc.mut) { return b; } if (unified == a->info.slc.elem) { return a; } else if (unified == b->info.slc.elem) { return b; } else { return alloc_slice_type(t, unified, NULL, a->info.slc.mut); } } goto error; } /* Handle optional types */ if (a->cls == TYPE_OPT && b->cls == TYPE_OPT) { type_t *unified = type_unify( t, a->info.opt.elem, b->info.opt.elem, NULL, coerce, NULL ); if (unified) { if (unified == a->info.opt.elem) { return a; } else if (unified == b->info.opt.elem) { return b; } else { return alloc_opt_type(t, unified); } } goto error; } if (a->cls == TYPE_RESULT && b->cls == TYPE_RESULT) { if (a->info.res.err != b->info.res.err) goto error; type_t *payload = type_unify( t, a->info.res.payload, b->info.res.payload, NULL, coerce, NULL ); if (payload) { if (payload == a->info.res.payload) { return a; } else if (payload == b->info.res.payload) { return b; } } goto error; } /* Handle array to slice conversion */ if (a->cls == TYPE_ARRAY && b->cls == TYPE_SLICE) { if (b->info.slc.mut) { goto error; } type_t *unified = type_unify( t, a->info.ary.elem, b->info.slc.elem, NULL, coerce, NULL ); if (unified && unified == a->info.ary.elem) { return a->slice; /* Convert array to its slice type */ } goto error; } if (b->cls == TYPE_ARRAY && a->cls == TYPE_SLICE) { if (a->info.slc.mut) { goto error; } type_t *unified = type_unify( t, a->info.slc.elem, b->info.ary.elem, NULL, coerce, NULL ); if (unified && unified == b->info.ary.elem) { return b->slice; /* Convert array to its slice type */ } goto error; } if (a->cls == TYPE_FN && b->cls == TYPE_FN) { usize nparams = a->info.fun.nparams; if (b->info.fun.nparams != nparams) { goto error; } for (usize i = 0; i < nparams; i++) { type_t *pa = a->info.fun.params[i]; type_t *pb = b->info.fun.params[i]; if (pa != pb) goto error; } if (a->info.fun.ret != b->info.fun.ret) goto error; return a; } error: return NULL; } static type_t *resolve_throw(resolve_t *t, node_t *n) { type_t *fn_ret = t->fn->node->type->info.fun.ret; type_t *err_type = fn_ret->info.res.err; if (!resolve_node(t, n->val.throw_stmt.expr, err_type)) return NULL; return (n->type = fn_ret); } static type_t *resolve_try_expr(resolve_t *t, node_t *n, type_t *expected) { bool optional = n->val.try_expr.optional; node_t *expr = n->val.try_expr.expr; node_t *catch_expr = n->val.try_expr.catch_expr; resolve_ctx_t pctx = t->ctx; t->ctx = TC_CTX_TRY; type_t *expr_type = resolve_node(t, expr, NULL); t->ctx = pctx; if (!expr_type) return NULL; type_t *payload = expr_type->info.res.payload; /* `try?` converts errors to nil and returns an optional type. */ if (optional) { if (payload->cls != TYPE_OPT) { payload = alloc_opt_type(t, payload); } return (n->type = payload); } if (catch_expr) { node_t *catch_binding = catch_expr->val.catch_clause.binding; node_t *catch_body = catch_expr->val.catch_clause.body; type_t *err_type = expr_type->info.res.err; /* If there's a binding, create a scope and add the error variable. */ if (catch_binding) { catch_expr->val.catch_clause.scope = symtab_scope(t->scope, NULL); t->scope = catch_expr->val.catch_clause.scope; catch_binding->type = err_type; if (!symbol_add(t, catch_binding, catch_binding)) return NULL; catch_binding->sym->e.var.typ = err_type; catch_binding->sym->e.var.align = err_type->align; catch_binding->sym->scope = t->scope; } type_t *catch_type = resolve_node(t, catch_body, NULL); if (catch_binding) { t->scope = t->scope->parent; } if (!catch_type) return NULL; if (catch_type->cls != TYPE_NEVER) return (n->type = t->types.type_void); /* Divergent catch block: fall through and keep payload type. */ } if (expected) { type_t *target = expected; if (expected->cls == TYPE_RESULT) target = expected->info.res.payload; type_t *unified = type_unify(t, payload, target, n, true, NULL); if (unified) payload = unified; } return (n->type = payload); } /* Process a submodule declaration */ static type_t *resolve_mod_decl(resolve_t *t, node_t *n) { node_t *name = n->val.mod_decl.ident; char rel[MAX_PATH_LEN] = { 0 }; strncpy(rel, name->val.ident.name, name->val.ident.length); /* Convert to path relative to current module and find it */ module_t *submod = module_manager_find_relative(t->mm, t->module->path, rel); if (!submod) return NULL; symbol_t *sym = symtab_scope_lookup( t->scope, name->val.ident.name, name->val.ident.length, SYM_MODULE ); if (sym) { n->sym = sym; } else { if (!symbol_add(t, name, n)) { /* Add module to current scope */ return NULL; } } if (!resolve_decls(t, submod)) { return NULL; } /* For mod declarations, also do full type checking */ if (!resolve_mod_def(t, submod)) { return NULL; } n->sym->e.mod = submod; n->sym->e.mod->attribs = n->val.mod_decl.attribs ? n->val.mod_decl.attribs->val.attrib : ATTRIB_NONE; module_path(submod->qualified, t->module->qualified); module_qualify(submod->qualified, name); return (n->type = t->types.type_void); } /* Helper function to look up a symbol in a module's scope */ static type_t *module_lookup( resolve_t *t, node_t *n, node_t *child, module_t *module ) { /* If the module hasn't been checked yet, check it on-demand. * This allows parent modules to reference submodule types. */ if (!module->scope && !module->declared && module->state != MODULE_STATE_VISITING) { if (!resolve_decls(t, module)) { return NULL; } } if (!module->scope) return NULL; symbol_t *sym = symtab_scope_lookup( module->scope, child->val.ident.name, child->val.ident.length, SYM_ANY ); if (!sym) return NULL; n->sym = sym; n->type = sym->node->type; return n->type; } static symbol_t *union_variant_lookup(type_t *typ, node_t *n) { for (usize i = 0; i < typ->info.uni.nvariants; i++) { symbol_t *v = typ->info.uni.variants[i]; if (ident_eq(n, v->name, v->length)) { return v; } } return NULL; } /* Look up a record field by name. */ static symbol_t *record_field_lookup(type_t *typ, node_t *n) { for (usize i = 0; i < typ->info.srt.nfields; i++) { symbol_t *f = typ->info.srt.fields[i]; if (ident_eq(n, f->name, f->length)) { return f; } } return NULL; } /* Add a field to a record type. */ static bool record_field_add( resolve_t *t, type_t *rec_typ, node_t *field, node_t *field_ident, type_t *field_typ ) { (void)t; const char *field_name; usize field_len; char tuple_name[16]; if (field_ident) { field_name = field_ident->val.ident.name; field_len = field_ident->val.ident.length; } else { /* Tuple field: generate synthetic name based on index */ snprintf( tuple_name, sizeof(tuple_name), "%u", (unsigned)rec_typ->info.srt.nfields ); field_name = strings_alloc(tuple_name); field_len = strlen(field_name); } field->type = field_typ; /* Nb. Since we're modifying the record size as we add fields, we always * add new fields at the end of the record. */ i32 field_align = field_typ->align; i32 aligned_offset = align(rec_typ->size, field_align); /* Keep track of packed size */ rec_typ->info.srt.packedsize += field_typ->size; field->sym = alloc_symbol((symbol_t){ .name = field_name, .length = field_len, .node = field, .kind = SYM_FIELD, .e.field = { .typ = field_typ, .offset = (i32)aligned_offset, }, }); /* Update record size to include this new field */ rec_typ->size = aligned_offset + field_typ->size; /* Update record alignment to be the maximum of its current alignment * and the new field's alignment */ rec_typ->align = (rec_typ->align > field_typ->align) ? rec_typ->align : field_typ->align; /* Add field to record type. */ rec_typ->info.srt.fields[rec_typ->info.srt.nfields++] = field->sym; return true; } static bool update_i32(i32 *dst, i32 value) { if (*dst == value) return false; *dst = value; return true; } static bool update_bool(bool *dst, bool value) { if (*dst == value) return false; *dst = value; return true; } static bool update_record_layout(type_t *strct_typ) { i32 size = 0; i32 record_align = 1; u32 packedsize = 0; bool changed = false; for (usize i = 0; i < strct_typ->info.srt.nfields; i++) { symbol_t *field_sym = strct_typ->info.srt.fields[i]; type_t *field_type = field_sym->e.field.typ; i32 field_align = field_type->align ? field_type->align : DEFAULT_ALIGN; i32 field_size = field_type->size; i32 offset = align(size, field_align); if (field_sym->e.field.offset != offset) { field_sym->e.field.offset = offset; changed = true; } size = offset + field_size; if (field_align > record_align) record_align = field_align; packedsize += (u32)field_size; } /* Round overall size up to record alignment to match C layout. */ size = align(size, record_align); changed |= update_i32(&strct_typ->size, size); changed |= update_i32(&strct_typ->align, record_align); if (strct_typ->info.srt.packedsize != packedsize) { strct_typ->info.srt.packedsize = packedsize; changed = true; } return changed; } static bool update_array_layout(type_t *typ) { type_t *elem = typ->info.ary.elem; if (!elem) return false; i32 elem_align = elem->align ? elem->align : DEFAULT_ALIGN; i32 size = elem->size * (i32)typ->info.ary.length; bool changed = false; changed |= update_i32(&typ->size, size); changed |= update_i32(&typ->align, elem_align); return changed; } static bool update_opt_layout(type_t *typ) { type_t *elem = typ->info.opt.elem; if (!elem) return false; i32 elem_align = elem->align ? elem->align : DEFAULT_ALIGN; i32 alignment = max(elem_align, TAG_SIZE); i32 val_offset = align(TAG_SIZE, elem_align); i32 size = align(val_offset + elem->size, alignment); bool changed = false; changed |= update_i32(&typ->size, size); changed |= update_i32(&typ->align, alignment); return changed; } static bool update_result_layout(resolve_t *t, type_t *typ) { type_t *payload = typ->info.res.payload; type_t *err = typ->info.res.err; i32 payload_align = payload == t->types.type_void ? TAG_SIZE : payload->align; i32 err_align = err == t->types.type_void ? TAG_SIZE : err->align; i32 alignment = max(max(payload_align, err_align), TAG_SIZE); i32 payload_size = payload == t->types.type_void ? 0 : payload->size; i32 err_size = err == t->types.type_void ? 0 : err->size; i32 val_offset = align(TAG_SIZE, alignment); i32 value_size = align(max(payload_size, err_size), alignment); i32 size = val_offset + value_size; bool changed = false; changed |= update_i32(&typ->size, size); changed |= update_i32(&typ->align, alignment); return changed; } static bool update_enum_layout(type_t *typ) { i32 new_align = typ->info.uni.base ? typ->info.uni.base->align : 0; bool has_payload = false; i32 variantsize = 0; bool changed = false; if (new_align <= 0) new_align = TAG_SIZE; for (usize i = 0; i < typ->info.uni.nvariants; i++) { symbol_t *variant_sym = typ->info.uni.variants[i]; if (!variant_sym || !variant_sym->node) continue; node_t *variant_node = variant_sym->node; type_t *payload = variant_node->type; if (!payload || payload->cls == TYPE_VOID) continue; has_payload = true; if (payload->size > variantsize) variantsize = payload->size; if (payload->align > new_align) new_align = payload->align; } if (new_align <= 0) new_align = TAG_SIZE; i32 size = typ->info.uni.base ? typ->info.uni.base->size : TAG_SIZE; if (has_payload) { i32 val_offset = align(TAG_SIZE, new_align); i32 aligned_payload = variantsize > 0 ? align(variantsize, new_align) : 0; size = val_offset + aligned_payload; } changed |= update_bool(&typ->info.uni.has_payload, has_payload); changed |= update_i32(&typ->info.uni.variantsize, variantsize); changed |= update_i32(&typ->align, new_align); changed |= update_i32(&typ->size, size); return changed; } static bool update_type_layout(resolve_t *t, type_t *typ) { switch (typ->cls) { case TYPE_ARRAY: return update_array_layout(typ); case TYPE_UNION: return update_enum_layout(typ); case TYPE_RECORD: return update_record_layout(typ); case TYPE_OPT: return update_opt_layout(typ); case TYPE_RESULT: return update_result_layout(t, typ); default: return false; } } static void finalize_type_layout(resolve_t *t) { usize max_passes = t->types.nobjects ? t->types.nobjects : 1; for (usize pass = 0; pass < max_passes; pass++) { bool changed = false; for (usize i = 0; i < t->types.nobjects; i++) { type_t *typ = &t->types.objects[i]; if (update_type_layout(t, typ)) changed = true; } if (!changed) return; } bail("type layout failed to stabilize"); } static bool declare_enum(resolve_t *t, node_t *n) { union_decl_t *decl = &n->val.union_decl; if (!n->sym) { if (!symbol_add(t, decl->name, n)) return false; if (!n->sym) return false; } if (!n->type) { type_t *typ = alloc_union_type(t, decl); n->sym->e.typ.info = n->type = typ; } else if (!n->sym->e.typ.info) { n->sym->e.typ.info = n->type; } return true; } static bool declare_record(resolve_t *t, node_t *n) { record_decl_t *decl = &n->val.record_decl; if (!n->sym) { if (!symbol_add(t, decl->name, n)) return false; if (!n->sym) return false; } if (!n->type) { type_t *strct_typ = alloc_record_type(t, decl); n->sym->e.typ.info = n->type = strct_typ; } else if (!n->sym->e.typ.info) { n->sym->e.typ.info = n->type; } return true; } static bool resolve_const_usize(resolve_t *t, node_t *expr, usize *value) { if (expr->cls == NODE_NUMBER) { *value = expr->val.number.value.u; return true; } symbol_t *sym = expr->sym; if (!sym && (expr->cls == NODE_IDENT || expr->cls == NODE_SCOPE)) { sym = resolve_name(t, expr, SYM_CONSTANT); if (!sym) return false; } if (!sym || sym->kind != SYM_CONSTANT || !sym->node || sym->node->cls != NODE_CONST) return false; node_t *value_node = sym->node->val.constant.value; if (!value_node || value_node->cls != NODE_NUMBER) return false; *value = value_node->val.number.value.u; return true; } static bool resolve_record_literal_fields( resolve_t *t, node_t *lit, type_t *record_type ) { node_t **lit_fields = nodespan_ptrs(&t->module->parser, lit->val.record_lit.fields); for (usize i = 0; i < lit->val.record_lit.fields.len; i++) { node_t *field_init = lit_fields[i]; record_lit_field_t *init = &field_init->val.record_lit_field; symbol_t *field_sym = record_field_lookup(record_type, init->name); if (!field_sym) return false; type_t *field_typ = field_sym->e.field.typ; if (!resolve_node(t, init->value, field_typ)) return false; field_init->sym = field_sym; } return true; } static bool resolve_record_literal_types( resolve_t *t, node_t *type_node, type_t *expected, type_t **out_record, type_t **out_result, symbol_t **out_variant ) { type_t *record_type = NULL; type_t *result_type = NULL; symbol_t *variant_sym = NULL; /* Explicit type annotation: either * `Type { ... }` or * `module::Type { ... }`, or * `Enum::Variant { ... }` */ if (type_node) { switch (type_node->cls) { case NODE_SCOPE: case NODE_IDENT: { symbol_t *sym = resolve_name(t, type_node, SYM_ANY); if (!sym) return false; type_t *resolved = type_node->type; if (!resolved && sym->node) resolved = sym->node->type; if (type_node->cls == NODE_SCOPE && sym->kind == SYM_VARIANT && sym->node->cls == NODE_UNION_VARIANT) { if (!resolved || resolved->cls != TYPE_UNION) return false; type_t *variant_type = sym->node->type; if (!variant_type || variant_type->cls != TYPE_RECORD) return false; record_type = variant_type; result_type = resolved; variant_sym = sym; break; } if (!resolved) { resolved = resolve_type(t, type_node); if (!resolved) return false; } if (resolved->cls != TYPE_RECORD) return false; record_type = resolved; result_type = record_type; break; } case NODE_RECORD_TYPE: { type_t *resolved = resolve_type(t, type_node); if (!resolved) return false; if (resolved->cls != TYPE_RECORD) return false; record_type = resolved; result_type = record_type; break; } default: return false; } } else { /* No explicit type: fall back to the expected type from context */ if (!expected) return false; if (expected->cls == TYPE_OPT) expected = expected->info.opt.elem; if (expected->cls != TYPE_RECORD) return false; record_type = expected; result_type = record_type; } *out_record = record_type; *out_result = result_type; if (out_variant) *out_variant = variant_sym; return true; } static bool anonymous_record_equals( resolve_t *t, type_t *typ, record_type_t *stype ) { if (typ->info.srt.nfields != stype->fields.len) return false; node_t **fields = nodespan_ptrs(&t->module->parser, stype->fields); for (usize i = 0; i < stype->fields.len; i++) { node_t *field_node = fields[i]; symbol_t *field_sym = typ->info.srt.fields[i]; if (field_node->type != field_sym->e.field.typ) return false; if (!ident_eq( field_node->val.var.ident, field_sym->name, field_sym->length )) return false; } return true; } static type_t *anonymous_record_lookup(resolve_t *t, record_type_t *stype) { for (usize i = 0; i < t->types.nobjects; i++) { type_t *typ = &t->types.objects[i]; if (typ->cls != TYPE_RECORD || !typ->info.srt.anonymous) continue; if (anonymous_record_equals(t, typ, stype)) return typ; } return NULL; } static bool union_variant_add( resolve_t *t, type_t *typ, node_t *v, usize idx, i32 *iota ) { (void)idx; union_variant_t *variant = &v->val.union_variant; const char *name = variant->name->val.ident.name; const usize length = variant->name->val.ident.length; symbol_t *sym = alloc_symbol((symbol_t){ .name = name, .length = length, .node = v, .kind = SYM_VARIANT, }); if (variant->type) { type_t *payload = resolve_type(t, variant->type); if (!payload) return false; v->type = payload; variant->value = *iota; *iota = variant->value + 1; } else { v->type = t->types.type_void; if (variant->value_expr) { if (!resolve_number(t, variant->value_expr, t->types.type_i32)) return false; variant->value = variant->value_expr->val.number.value.i; *iota = variant->value + 1; } else { variant->value = *iota; *iota = variant->value + 1; } } assert(typ->info.uni.nvariants < MAX_UNION_VARIANTS); typ->info.uni.variants[typ->info.uni.nvariants++] = sym; update_enum_layout(typ); return true; } /* Allocate a type. */ static type_t *alloc_type( resolve_t *t, typeclass_t kind, const char *name, usize namelen, i32 size, i32 align ) { if (t->types.nobjects >= MAX_TYPES) { bail("type overflow: too many types"); return NULL; } type_t *slot = &t->types.objects[t->types.nobjects++]; slot->name = name; slot->namelen = namelen; slot->cls = kind; slot->size = size; slot->align = align; slot->ptr = NULL; slot->ptr_mut = NULL; slot->slice = NULL; slot->slice_mut = NULL; /* For non-pointer types, allocate a pointer type and * link it to the target type. */ if (kind != TYPE_PTR) { slot->ptr = alloc_ptr_type(t, slot, false); } return slot; } /* Allocate a slice type. * `base` can be `NULL` for things like `*[u8]` from string literals. */ static type_t *alloc_slice_type( resolve_t *t, type_t *elem, type_t *base, bool mut ) { if (base) { if (!mut && base->slice) { return base->slice; } if (mut && base->slice_mut) { return base->slice_mut; } } else { if (!mut && elem->slice) { return elem->slice; } if (mut && elem->slice_mut) { return elem->slice_mut; } } char buf[MAX_STRING_LEN] = { 0 }; if (mut) { snprintf( buf, MAX_STRING_LEN, "*mut [%.*s]", (int)elem->namelen, elem->name ); } else { snprintf( buf, MAX_STRING_LEN, "*[%.*s]", (int)elem->namelen, elem->name ); } const char *name = strings_alloc(buf); type_t *typ = alloc_type(t, TYPE_SLICE, name, strlen(name), WORD_SIZE * 2, WORD_SIZE); typ->info.slc.elem = elem; typ->info.slc.base = base; typ->info.slc.mut = mut; if (base) { if (!mut) { base->slice = typ; } else { base->slice_mut = typ; } } else { if (!mut) { elem->slice = typ; } else { elem->slice_mut = typ; } } return typ; } /* Allocate a pointer type. */ static type_t *alloc_ptr_type(resolve_t *t, type_t *base, bool mut) { if (!mut && base->ptr) { return base->ptr; } if (mut && base->ptr_mut) { return base->ptr_mut; } char buf[MAX_STRING_LEN] = { 0 }; if (mut) { snprintf( buf, MAX_STRING_LEN, "*mut %.*s", (int)base->namelen, base->name ); } else { snprintf(buf, MAX_STRING_LEN, "*%.*s", (int)base->namelen, base->name); } const char *name = strings_alloc(buf); type_t *typ = alloc_type(t, TYPE_PTR, name, strlen(name), WORD_SIZE, WORD_SIZE); typ->info.ptr.target = base; typ->info.ptr.mut = mut; if (!mut) { base->ptr = typ; } else { base->ptr_mut = typ; } return typ; } /* Allocate an array type. */ static type_t *alloc_array_type(resolve_t *t, type_t *elem, usize length) { /* First check if we already have this array type */ for (usize i = 0; i < t->types.nobjects; i++) { type_t *typ = &t->types.objects[i]; if (typ->cls == TYPE_ARRAY && typ->info.ary.elem == elem && typ->info.ary.length == length) { return typ; } } char buf[MAX_STRING_LEN] = { 0 }; snprintf( buf, MAX_STRING_LEN, "[%.*s; %ld]", (int)elem->namelen, elem->name, length ); const char *name = strings_alloc(buf); type_t *array_type = alloc_type(t, TYPE_ARRAY, name, strlen(name), 0, 0); array_type->info.ary.elem = elem; array_type->info.ary.length = length; update_array_layout(array_type); array_type->slice = alloc_slice_type(t, elem, array_type, false); array_type->ptr = alloc_ptr_type(t, array_type, false); return array_type; } static type_t *alloc_union_type(resolve_t *t, union_decl_t *uni) { type_t *typ = alloc_type( t, TYPE_UNION, uni->name->val.ident.name, uni->name->val.ident.length, WORD_SIZE, WORD_SIZE ); /* TODO: use correct type based on union variants. * For now, default all enums to an `i32` base type. */ typ->info.uni.decl = uni; typ->info.uni.base = t->types.type_i32; typ->info.uni.variants = types_alloc_sympool(&t->types, MAX_UNION_VARIANTS); typ->info.uni.nvariants = 0; typ->info.uni.variantsize = 0; typ->info.uni.has_payload = false; return typ; } static type_t *alloc_fn_type( resolve_t *t, node_t *n, type_t *ret, usize nparams ) { type_t *type = alloc_type( t, TYPE_FN, n->sym ? n->sym->name : "#fn", n->sym ? n->sym->length : 3, DEFAULT_SIZE, DEFAULT_ALIGN ); type->info.fun.ret = ret ? ret : t->types.type_void; type->info.fun.params = types_alloc_typepool(&t->types, MAX_FN_PARAMS); type->info.fun.throws = types_alloc_typepool(&t->types, MAX_FN_THROWS); type->info.fun.nparams = nparams; type->info.fun.nthrows = 0; return (n->type = type); } static type_t *alloc_record_type(resolve_t *t, record_decl_t *srt) { type_t *typ = alloc_type( t, TYPE_RECORD, srt->name->val.ident.name, srt->name->val.ident.length, 0, /* Size will be updated when we add fields */ DEFAULT_ALIGN ); typ->info.srt.fields = types_alloc_sympool(&t->types, MAX_RECORD_FIELDS); typ->info.srt.nfields = 0; typ->info.srt.packedsize = 0; typ->info.srt.anonymous = false; typ->info.srt.tuple = srt->tuple; return typ; } static type_t *alloc_anonymous_record_type(resolve_t *t) { char buf[32]; snprintf(buf, sizeof(buf), "record#%u", (unsigned)t->recordid++); const char *name = strings_alloc(buf); type_t *typ = alloc_type( t, TYPE_RECORD, name, strlen(name), 0, /* Size will be updated when we add fields */ DEFAULT_ALIGN ); typ->info.srt.fields = types_alloc_sympool(&t->types, MAX_RECORD_FIELDS); typ->info.srt.nfields = 0; typ->info.srt.packedsize = 0; typ->info.srt.anonymous = true; return typ; } /* Allocate an optional type. */ static type_t *alloc_opt_type(resolve_t *t, type_t *elem) { /* First check if we already have this optional type */ for (usize i = 0; i < t->types.nobjects; i++) { type_t *typ = &t->types.objects[i]; if (typ->cls == TYPE_OPT && typ->info.opt.elem == elem) { return typ; } } char buf[MAX_STRING_LEN] = { 0 }; snprintf(buf, MAX_STRING_LEN, "?%.*s", (int)elem->namelen, elem->name); const char *name = strings_alloc(buf); type_t *opt_type = alloc_type(t, TYPE_OPT, name, strlen(name), 0, 0); opt_type->info.opt.elem = elem; update_opt_layout(opt_type); return opt_type; } static type_t *alloc_result_type(resolve_t *t, type_t *payload, type_t *err) { /* Find existing result type that matches this one. */ for (usize i = 0; i < t->types.nobjects; i++) { type_t *typ = &t->types.objects[i]; if (typ->cls == TYPE_RESULT && typ->info.res.payload == payload && typ->info.res.err == err) { return typ; } } char buf[MAX_STRING_LEN] = { 0 }; snprintf( buf, MAX_STRING_LEN, "result<%.*s, %.*s>", (int)err->namelen, err->name, (int)payload->namelen, payload->name ); const char *name = strings_alloc(buf); type_t *result_typ = alloc_type(t, TYPE_RESULT, name, strlen(name), 0, 0); result_typ->info.res.err = err; result_typ->info.res.payload = payload; update_result_layout(t, result_typ); return result_typ; } static bool resolve_fn_throws( resolve_t *t, type_t *fn_type, nodespan_t throws, type_t *ret_payload ) { usize nthrows = throws.len; if (nthrows == 0) { fn_type->info.fun.ret = ret_payload; return true; } if (nthrows > MAX_FN_THROWS) bail("too many throw types"); node_t **throw_nodes = nodespan_ptrs(&t->module->parser, throws); for (usize i = 0; i < nthrows; i++) { node_t *thrown = throw_nodes[i]; type_t *thrown_typ = resolve_type(t, thrown); if (!thrown_typ) return false; fn_type->info.fun.throws[i] = thrown_typ; fn_type->info.fun.nthrows++; } type_t *thrown_typ = fn_type->info.fun.throws[0]; type_t *result_typ = alloc_result_type(t, ret_payload, thrown_typ); fn_type->info.fun.ret = result_typ; return true; } static bool union_variant_validate_args( resolve_t *t, node_t *call, symbol_t *variant_sym, node_t **out_arg_expr ) { (void)t; type_t *variant_type = variant_sym->node->type; usize nargs = call->val.call.args.len; if (variant_type->cls == TYPE_VOID) { if (out_arg_expr) *out_arg_expr = NULL; return nargs == 0; } if (nargs != 1) return false; if (out_arg_expr) *out_arg_expr = nodespan_ptrs(&t->module->parser, call->val.call.args)[0] ->val.call_arg.expr; return true; } /* Check a union constructor call like `Expr::number(42)`. */ static type_t *resolve_enum_constructor( resolve_t *t, node_t *call, type_t *union_type, symbol_t *variant_sym ) { type_t *variant_type = variant_sym->node->type; node_t *arg_expr = NULL; if (!union_variant_validate_args(t, call, variant_sym, &arg_expr)) return NULL; if (arg_expr) { if (!resolve_node(t, arg_expr, variant_type)) return NULL; } call->sym = variant_sym; call->type = union_type; return union_type; } /* Check tuple record constructor call */ static type_t *resolve_tuple_record_constructor( resolve_t *t, node_t *call, type_t *record_type ) { usize nfields = record_type->info.srt.nfields; usize nargs = call->val.call.args.len; if (nargs != nfields) return NULL; /* Type check each argument against the corresponding field type. */ for (usize i = 0; i < nargs; i++) { node_t *arg = nodespan_ptrs(&t->module->parser, call->val.call.args)[i]; symbol_t *field_sym = record_type->info.srt.fields[i]; type_t *field_typ = field_sym->e.field.typ; if (!resolve_node(t, arg, field_typ)) return NULL; } call->sym = NULL; return (call->type = record_type); } static bool symbol_add(resolve_t *t, node_t *ident, node_t *n) { if (ident->cls == NODE_PLACEHOLDER) return true; return symtab_add_ident(t->scope, ident, n); } static symbol_t *resolve_name(resolve_t *t, node_t *n, symkind_t kind) { n->sym = NULL; if (n->cls == NODE_SCOPE) { if (!resolve_scope(t, n) || !n->sym) return NULL; if (kind != SYM_ANY && n->sym->kind != kind) return NULL; return n->sym; } symbol_t *sym = symtab_lookup(t->scope, n->val.ident.name, n->val.ident.length, kind); if (!sym && kind == SYM_ANY) { sym = symtab_lookup( t->scope, n->val.ident.name, n->val.ident.length, SYM_ANY ); } if (sym) { n->sym = sym; if (sym->node && sym->node->type && !n->type) n->type = sym->node->type; return sym; } return NULL; } /* Resolve a type by looking up its definition if necessary, eg. for custom * types defined in the source code. */ static type_t *resolve_type(resolve_t *t, node_t *n) { if (n->type) return n->type; switch (n->cls) { case NODE_TYPE: switch (n->val.type.tclass) { case TYPE_U8: return (n->type = t->types.type_u8); case TYPE_U16: return (n->type = t->types.type_u16); case TYPE_U32: return (n->type = t->types.type_u32); case TYPE_I8: return (n->type = t->types.type_i8); case TYPE_I16: return (n->type = t->types.type_i16); case TYPE_I32: return (n->type = t->types.type_i32); case TYPE_BOOL: return (n->type = t->types.type_bool); case TYPE_VOID: return (n->type = t->types.type_void); case TYPE_OPAQUE: return (n->type = t->types.type_opaque); case TYPE_FN: { /* Resolve return type */ type_t *ret_type = n->val.type.info.fn.ret ? resolve_type(t, n->val.type.info.fn.ret) : t->types.type_void; if (!ret_type) return NULL; n->type = alloc_fn_type(t, n, ret_type, n->val.type.info.fn.params.len); /* Resolve parameter types */ for (usize i = 0; i < n->val.type.info.fn.params.len; i++) { type_t *param_typ = resolve_type( t, nodespan_ptrs( &t->module->parser, n->val.type.info.fn.params )[i] ); if (!param_typ) return NULL; n->type->info.fun.params[i] = param_typ; } if (!resolve_fn_throws( t, n->type, n->val.type.info.fn.throws, ret_type )) return NULL; return n->type; } case TYPE_ARRAY: { type_t *elem_typ = resolve_type(t, n->val.type.elem_type); if (!elem_typ) return NULL; node_t *len_node = n->val.type.info.array.length; if (!resolve_node(t, len_node, t->types.type_u32)) return NULL; usize len = 0; if (!resolve_const_usize(t, len_node, &len)) return NULL; return (n->type = alloc_array_type(t, elem_typ, len)); } case TYPE_SLICE: { type_t *elem_typ = resolve_type(t, n->val.type.elem_type); if (!elem_typ) return NULL; bool mut = n->val.type.info.slice.mut; return (n->type = alloc_slice_type(t, elem_typ, NULL, mut)); } case TYPE_UNION: case TYPE_RESULT: case TYPE_RECORD: abort(); case TYPE_PTR: { type_t *elem_typ = resolve_type(t, n->val.type.elem_type); if (!elem_typ) return NULL; bool mut = n->val.type.info.ptr.mut; return (n->type = alloc_ptr_type(t, elem_typ, mut)); } case TYPE_OPT: { type_t *elem_typ = resolve_type(t, n->val.type.elem_type); if (!elem_typ) return NULL; return (n->type = alloc_opt_type(t, elem_typ)); } default: break; } break; case NODE_RECORD_TYPE: { record_type_t *stype = &n->val.record_type; node_t **fields = nodespan_ptrs(&t->module->parser, stype->fields); for (usize i = 0; i < stype->fields.len; i++) { node_t *field = fields[i]; type_t *typ = resolve_type(t, field->val.var.type); if (!typ) return NULL; field->type = typ; } type_t *existing = anonymous_record_lookup(t, stype); if (existing) return (n->type = existing); type_t *typ = alloc_anonymous_record_type(t); for (usize i = 0; i < stype->fields.len; i++) { node_t *field = fields[i]; if (!record_field_add( t, typ, field, field->val.var.ident, field->type )) return NULL; } return (n->type = typ); } case NODE_SCOPE: case NODE_IDENT: { symbol_t *sym = resolve_name(t, n, SYM_TYPE); if (!sym) return NULL; if (!sym->node || !sym->node->type) bail("type symbol missing type information"); return (n->type = sym->node->type); } default: bail("node is not a kind of type, class is %d", n->cls); } return NULL; } static type_t *resolve_number(resolve_t *t, node_t *n, type_t *expected) { if (!expected) expected = t->types.type_i32; if (expected->cls == TYPE_OPT) expected = expected->info.opt.elem; if (!expected || !type_is_numeric(expected->cls)) return NULL; type_t *result_type = expected; typeclass_t tclass = expected->cls; imm_t value = { 0 }; /* Create a null-terminated copy of the text for strto* functions. */ static char text[16] = { 0 }; memcpy(text, n->val.number.text, n->val.number.text_len); text[n->val.number.text_len] = '\0'; /* Manual binary literal parsing since `strtol` doesn't support 0b in this * environment. */ bool is_binary = (text[0] == '0' && (text[1] == 'b' || text[1] == 'B')); u32 binval = 0; if (is_binary) { for (usize i = 2; text[i]; i++) { binval = (binval << 1) + (text[i] - '0'); } } /* Parse the number based on the type */ switch (tclass) { case TYPE_I8: case TYPE_I16: case TYPE_I32: { i32 val; if (is_binary) { val = (i32)binval; } else { val = strtol(text, NULL, 0); } value.i = val; break; } case TYPE_U8: case TYPE_U16: case TYPE_U32: { u32 val; if (is_binary) { val = binval; } else { val = strtoul(text, NULL, 0); } value.u = val; break; } default: break; } n->val.number.value = value; return (n->type = result_type); } static type_t *resolve_builtin(resolve_t *t, node_t *n, type_t *expected) { (void)expected; builtin_kind_t kind = n->val.builtin.kind; node_t **args = nodespan_ptrs(&t->module->parser, n->val.builtin.args); type_t *typ; /* @sliceOf is handled separately since it takes two runtime arguments */ if (kind == BUILTIN_SLICE_OF) { /* Check first argument (pointer) */ type_t *ptr_type = resolve_node(t, args[0], NULL); if (!ptr_type) return NULL; /* Check second argument (length) */ type_t *len_type = resolve_node(t, args[1], t->types.type_u32); if (!len_type) return NULL; /* Result is a slice of the pointer's element type */ type_t *elem_type = ptr_type->info.ptr.target; bool mut = ptr_type->info.ptr.mut; return (n->type = alloc_slice_type(t, elem_type, NULL, mut)); } node_t *expr = args[0]; switch (expr->cls) { case NODE_TYPE: case NODE_RECORD_TYPE: typ = resolve_type(t, expr); break; default: typ = resolve_node(t, expr, NULL); break; } if (!typ) return NULL; u32 value = 0; switch (kind) { case BUILTIN_SIZE_OF: value = (u32)typ->size; break; case BUILTIN_ALIGN_OF: value = (u32)typ->align; if (expr->sym && expr->sym->kind == SYM_VARIABLE && expr->sym->e.var.align > 0) { value = (u32)expr->sym->e.var.align; } break; case BUILTIN_SLICE_OF: /* Already handled above */ break; } n->cls = NODE_NUMBER; n->val.number.text = NULL; n->val.number.text_len = 0; n->val.number.value.u = value; return (n->type = t->types.type_u32); } /* Bind a pattern variable to a field type. Handles identifiers and * placeholders. If is_ref_match is true, the binding type is wrapped * in a pointer type. */ static bool bind_pattern_var( resolve_t *t, node_t *binding, type_t *field_typ, bool is_ref_match, bool ref_mut ) { type_t *binding_type = is_ref_match ? alloc_ptr_type(t, field_typ, ref_mut) : field_typ; if (binding->cls == NODE_IDENT) { binding->type = binding_type; if (!symbol_add(t, binding, binding)) return false; binding->sym->e.var.typ = binding_type; binding->sym->e.var.align = binding_type->align; binding->sym->scope = t->scope; } return true; } /* Bind pattern variables to record fields. Works for both tuple-style S(x, y) * and labeled T { x, y } patterns. Returns false on error. * If is_ref_match is true, bindings are pointer types. */ static bool resolve_record_pattern_bindings( resolve_t *t, node_t *pattern, type_t *rec_type, bool is_ref_match, bool ref_mut ) { if (pattern->cls == NODE_CALL) { usize nargs = pattern->val.call.args.len; for (usize i = 0; i < nargs; i++) { node_t *arg_node = nodespan_ptrs(&t->module->parser, pattern->val.call.args)[i]; node_t *arg = (arg_node->cls == NODE_CALL_ARG) ? arg_node->val.call_arg.expr : arg_node; symbol_t *field_sym = rec_type->info.srt.fields[i]; if (!bind_pattern_var( t, arg, field_sym->e.field.typ, is_ref_match, ref_mut )) return false; arg_node->sym = field_sym; } } else if (pattern->cls == NODE_RECORD_LIT) { node_t **fields = nodespan_ptrs(&t->module->parser, pattern->val.record_lit.fields); for (usize f = 0; f < pattern->val.record_lit.fields.len; f++) { node_t *field_node = fields[f]; node_t *binding = field_node->val.record_lit_field.value; node_t *name_node = field_node->val.record_lit_field.name; node_t *lookup_node = name_node ? name_node : binding; symbol_t *field_sym = record_field_lookup(rec_type, lookup_node); if (!field_sym) return false; field_node->sym = field_sym; if (!bind_pattern_var( t, binding, field_sym->e.field.typ, is_ref_match, ref_mut )) return false; } } return true; } /* Check a match statement case. */ static type_t *resolve_match_case(resolve_t *t, node_t *n, type_t *match_typ) { /* If this is the default (else) case, there are no patterns to check. */ if (!n->val.match_case.patterns.len) { if (!resolve_node(t, n->val.match_case.body, NULL)) return NULL; return (n->type = t->types.type_void); } scope_t *prev = t->scope; /* Check if matching on a pointer to a union - bindings will be pointers */ bool is_ref_match = false; bool ref_mut = false; type_t *union_typ = match_typ; if (match_typ->cls == TYPE_PTR && type_is_union_with_payload(match_typ->info.ptr.target)) { is_ref_match = true; ref_mut = match_typ->info.ptr.mut; union_typ = match_typ->info.ptr.target; } if (type_is_union_with_payload((union_typ))) { /* Create a shared scope for all patterns in this case */ scope_t *case_scope = symtab_scope(t->scope, NULL); t->scope = case_scope; n->val.match_case.variable = NULL; /* Check each pattern in this case */ node_t **patterns = nodespan_ptrs(&t->module->parser, n->val.match_case.patterns); for (usize p = 0; p < n->val.match_case.patterns.len; p++) { node_t *pattern = patterns[p]; node_t *callee = NULL; bool is_call = (pattern->cls == NODE_CALL); bool is_reclit = (pattern->cls == NODE_RECORD_LIT); if (is_call) { callee = pattern->val.call.callee; } else if (is_reclit) { callee = pattern->val.record_lit.type; if (!callee) return NULL; } else if (pattern->cls == NODE_SCOPE) { callee = pattern; } else { return NULL; } type_t *parent = resolve_scope(t, callee); node_t *variant = callee->val.access.rval; if (!parent) return NULL; symbol_t *variant_sym = union_variant_lookup(union_typ, variant); if (!variant_sym) return NULL; variant->sym = variant_sym; type_t *variant_type = variant_sym->node->type; if (variant_type->cls == TYPE_VOID) { if (is_call) { if (!union_variant_validate_args( t, pattern, variant_sym, NULL )) return NULL; } } else if (is_reclit) { if (variant_type->cls != TYPE_RECORD) return NULL; if (!resolve_record_pattern_bindings( t, pattern, variant_type, is_ref_match, ref_mut )) return NULL; } else { node_t *arg_expr = NULL; if (!union_variant_validate_args( t, pattern, variant_sym, &arg_expr )) return NULL; node_t *variable = arg_expr; n->val.match_case.variable = variable; /* Create scope for the bound variable. * If matching on a pointer to union, binding is a pointer. */ type_t *binding_type = is_ref_match ? alloc_ptr_type(t, variant_type, ref_mut) : variant_type; variable->type = binding_type; if (variable->cls == NODE_IDENT) { /* Add the bound variable to the scope */ if (!symbol_add(t, variable, variable)) return NULL; variable->sym->e.var.typ = binding_type; variable->sym->e.var.align = binding_type->align; variable->sym->scope = t->scope; } } /* Set the pattern type to the union type. */ pattern->type = match_typ; } } else if (match_typ->cls == TYPE_RECORD) { /* Record pattern matching: `match rec { case T(x) => ... }` */ scope_t *case_scope = symtab_scope(t->scope, NULL); t->scope = case_scope; n->val.match_case.variable = NULL; node_t **patterns = nodespan_ptrs(&t->module->parser, n->val.match_case.patterns); for (usize p = 0; p < n->val.match_case.patterns.len; p++) { node_t *pattern = patterns[p]; if (!resolve_record_pattern_bindings( t, pattern, match_typ, false, false )) return NULL; pattern->type = match_typ; } } else { bool pctx = t->ctx; t->ctx = TC_CTX_PATTERN; /* Check each pattern in this case */ node_t **patterns2 = nodespan_ptrs(&t->module->parser, n->val.match_case.patterns); for (usize p = 0; p < n->val.match_case.patterns.len; p++) { node_t *pattern = patterns2[p]; if (!resolve_node(t, pattern, match_typ)) return NULL; } t->ctx = pctx; } if (n->val.match_case.guard) { if (!resolve_node(t, n->val.match_case.guard, t->types.type_bool)) return NULL; } /* Check case body */ if (!resolve_node(t, n->val.match_case.body, NULL)) { t->scope = prev; return NULL; } t->scope = prev; return (n->type = t->types.type_void); } static type_t *resolve_call_fn_ptr(resolve_t *t, symbol_t *sym, node_t *call) { node_t *fn = sym->node; if (fn->type->cls != TYPE_FN) return NULL; /* Check each argument type. */ for (usize i = 0; i < call->val.call.args.len; i++) { node_t *arg_node = nodespan_ptrs(&t->module->parser, call->val.call.args)[i]; type_t *param_typ = fn->type->info.fun.params[i]; if (!resolve_node(t, arg_node->val.call_arg.expr, param_typ)) return NULL; } call->sym = sym; return (call->type = fn->type->info.fun.ret); } static type_t *resolve_call_fn(resolve_t *t, symbol_t *sym, node_t *call) { node_t *fn = sym->node; if (fn->type->cls != TYPE_FN) return NULL; sym->e.fn.used = true; /* Check each argument type. */ for (usize i = 0; i < call->val.call.args.len; i++) { node_t *arg_node = nodespan_ptrs(&t->module->parser, call->val.call.args)[i]; node_t *param_node = nodespan_ptrs(&sym->scope->mod->parser, fn->val.fn_decl.params)[i]; type_t *param_type = resolve_type(t, param_node->val.param.type); if (!resolve_node(t, arg_node->val.call_arg.expr, param_type)) return NULL; } call->sym = sym; return (call->type = fn->type->info.fun.ret); } /* Helper function to build a module path from NODE_ACCESS nodes */ static void module_scope_path(node_t *node, char *path_str) { if (node->cls == NODE_IDENT) { strncat(path_str, node->val.ident.name, node->val.ident.length); } else if (node->cls == NODE_SUPER) { strlcat(path_str, "super", MAX_PATH_LEN); } else if (node->cls == NODE_SCOPE) { module_scope_path(node->val.access.lval, path_str); strlcat(path_str, "::", MAX_PATH_LEN); module_scope_path(node->val.access.rval, path_str); } else { } } static type_t *resolve_use(resolve_t *t, node_t *n) { /* Extract the import path from the `use` node */ node_t *path_node = n->val.use_decl.path; bool wildcard = n->val.use_decl.wildcard; /* Get the last component (symbol name) and parent scope */ node_t *last = path_node; node_t *parent = NULL; while (last && (last->cls == NODE_SCOPE)) { parent = last->val.access.lval; last = last->val.access.rval; } /* Try to find as a module first */ char filepath[MAX_PATH_LEN] = { 0 }; module_scope_path(path_node, filepath); module_t *imported = module_manager_find_relative(t->mm, t->module->path, filepath); if (imported) { /* Module import: check both declarations and definitions */ if (!resolve_decls(t, imported)) { return NULL; } bool in_decl_phase = t->module && !t->module->declared; if (!in_decl_phase) { if (!resolve_mod_def(t, imported)) { return NULL; } } if (wildcard) { /* Re-export all public symbols from the imported module */ for (usize i = 0; i < imported->scope->nsymbols; i++) { symbol_t *sym = imported->scope->symbols[i]; if (!sym || !sym->node) continue; /* Only re-export public symbols */ attrib_t sym_attribs = 0; switch (sym->kind) { case SYM_FUNCTION: sym_attribs = sym->e.fn.attribs; break; case SYM_TYPE: /* Check if it's a record or union declaration */ if (sym->node->cls == NODE_RECORD) { node_t *attribs_node = sym->node->val.record_decl.attribs; if (attribs_node && attribs_node->cls == NODE_ATTRIBUTE) { sym_attribs = attribs_node->val.attrib; } } else if (sym->node->cls == NODE_UNION) { node_t *attribs_node = sym->node->val.union_decl.attribs; if (attribs_node && attribs_node->cls == NODE_ATTRIBUTE) { sym_attribs = attribs_node->val.attrib; } } break; case SYM_MODULE: /* Check module declaration attributes */ if (sym->node->cls == NODE_MOD) { node_t *attribs_node = sym->node->val.mod_decl.attribs; if (attribs_node && attribs_node->cls == NODE_ATTRIBUTE) { sym_attribs = attribs_node->val.attrib; } } break; default: /* Skip other symbol types for now */ continue; } if (sym_attribs & ATTRIB_PUB) { if (!symtab_add_alias(t->scope, sym->node, sym)) { /* Symbol already exists, skip it */ } } } return (n->type = t->types.type_void); } else { /* Regular module import */ if (!symbol_add(t, last, n)) { return NULL; } n->sym->e.mod = imported; n->sym->scope = imported->scope; return (n->type = t->types.type_void); } } /* Try function/symbol import if there's a parent scope */ if (parent) { char modulepath[MAX_PATH_LEN] = { 0 }; module_scope_path(parent, modulepath); module_t *parent_mod = module_manager_find_relative(t->mm, t->module->path, modulepath); if (parent_mod && resolve_mod_def(t, parent_mod)) { symbol_t *sym = symtab_scope_lookup( parent_mod->scope, last->val.ident.name, last->val.ident.length, SYM_ANY ); if (sym) { /* Add alias with qualified name */ symtab_add_alias(t->scope, last, sym); n->sym = sym; n->sym->scope = parent_mod->scope; return (n->type = t->types.type_void); } } } return NULL; } /* Scope access, eg. `foo::bar` */ static type_t *resolve_scope(resolve_t *t, node_t *n) { node_t *parent = n->val.access.lval; node_t *child = n->val.access.rval; /* Handle absolute path from global root: ::module::symbol */ if (parent == NULL) { /* Look up module in global scope */ symbol_t *sym = symtab_lookup( t->global, child->val.ident.name, child->val.ident.length, SYM_MODULE ); if (sym) { n->sym = sym; return (n->type = t->types.type_void); } return NULL; } /* Handle `super::` references */ if (node_is_super(parent)) { if (!t->module) return NULL; module_t *target = module_super_ancestor(t->module, 1); if (!target) return NULL; if (!target->declared && target->state != MODULE_STATE_VISITING) { if (!resolve_decls(t, target)) { return NULL; } } if (target->ast && target->ast->sym) { parent->sym = target->ast->sym; parent->type = t->types.type_void; } return module_lookup(t, n, child, target); } /* Handle direct module access: module::symbol */ if (parent->cls == NODE_IDENT) { symbol_t *sym = resolve_name(t, parent, SYM_MODULE); if (sym) { return module_lookup(t, n, child, sym->e.mod); } } else if (parent->cls == NODE_SCOPE) { /* Handle recursive scope access: foo::bar::baz */ type_t *parent_type = resolve_scope(t, parent); if (!parent_type) return NULL; /* If parent is a module, look up symbol in module scope */ if (parent->sym && parent->sym->kind == SYM_MODULE) { return module_lookup(t, n, child, parent->sym->e.mod); } /* If parent is a union, handle union scope */ if (parent_type->cls == TYPE_UNION) { symbol_t *sym = union_variant_lookup(parent_type, child); if (sym) { n->sym = sym; return (n->type = parent_type); } } return NULL; } /* If not a module, treat it as a normal type */ type_t *parent_type = resolve_node(t, parent, NULL); if (!parent_type) return NULL; /* Handle union variant access */ if (parent_type->cls == TYPE_UNION) { if ((n->sym = union_variant_lookup(parent_type, child))) { /* For unions we store the type of the enum, not the variant */ return (n->type = parent_type); } } return NULL; } static type_t *resolve_array_repeat(resolve_t *t, node_t *n, type_t *expected) { if (expected->cls == TYPE_OPT) { expected = expected->info.opt.elem; } node_t *value = n->val.array_repeat_lit.value; node_t *count = n->val.array_repeat_lit.count; /* Type check the value expression */ type_t *expected_typ = expected->info.ary.elem; type_t *value_typ = resolve_node(t, value, expected_typ); if (!value_typ) return NULL; /* Type check the count expression */ if (!resolve_node(t, count, t->types.type_u32)) return NULL; /* Ensure the count is a compile-time constant */ usize length = 0; if (!resolve_const_usize(t, count, &length)) return NULL; /* For array contexts, use expected type */ if (expected->cls == TYPE_ARRAY) { n->type = expected; } else { /* For slice contexts, create a new array type */ n->type = alloc_array_type(t, expected_typ, length); } return n->type; } /* Check expression types. */ static type_t *resolve_node(resolve_t *t, node_t *n, type_t *expected) { /* Short-circuit if we've already traversed this node. */ if (n->type && n->cls != NODE_RECORD && n->cls != NODE_UNION) return n->type; switch (n->cls) { case NODE_ARRAY_LIT: { if (expected->cls == TYPE_OPT) { expected = expected->info.opt.elem; } usize length = n->val.array_lit.elems.len; if (length == 0) { /* Create an empty array type with the expected element type. */ type_t *elem_type = expected->info.slc.elem; n->type = alloc_array_type(t, elem_type, 0); return n->type; } /* Get the expected element type */ type_t *expected_typ = expected->cls == TYPE_SLICE ? expected->info.slc.elem : expected->info.ary.elem; /* Check all elements */ node_t **elems = nodespan_ptrs(&t->module->parser, n->val.array_lit.elems); for (usize i = 0; i < length; i++) { if (!resolve_node(t, elems[i], expected_typ)) return NULL; } if (expected->cls == TYPE_ARRAY) { n->type = expected; } else { /* For slice contexts, create a new array type */ n->type = alloc_array_type(t, expected_typ, length); } return n->type; } case NODE_ARRAY_REPEAT_LIT: return resolve_array_repeat(t, n, expected); case NODE_ARRAY_INDEX: { type_t *array_typ = resolve_node(t, n->val.access.lval, NULL); if (!array_typ) return NULL; if (array_typ->cls == TYPE_PTR) array_typ = deref_type(array_typ); node_t *idx_node = n->val.access.rval; if (idx_node->cls == NODE_RANGE) { if (array_typ->cls == TYPE_SLICE) { n->type = array_typ; if (array_typ->info.slc.base) array_typ = array_typ->info.slc.base; } if (idx_node->val.range.start) { if (!resolve_node( t, idx_node->val.range.start, t->types.type_u32 )) return NULL; } if (idx_node->val.range.end) { if (!resolve_node( t, idx_node->val.range.end, t->types.type_u32 )) return NULL; } return (n->type = n->type ? n->type : array_typ->slice); } else { type_t *elem_typ = array_typ->cls == TYPE_SLICE ? array_typ->info.slc.elem : array_typ->info.ary.elem; if (!resolve_node(t, idx_node, t->types.type_u32)) return NULL; return (n->type = elem_typ); } } case NODE_UNION: { union_decl_t *decl = &n->val.union_decl; node_t **variants = nodespan_ptrs(&t->module->parser, decl->variants); if (!declare_enum(t, n)) { return NULL; } type_t *typ = n->type; /* Add each variant to the union's symbol table. */ i32 iota = 0; for (usize i = 0; i < decl->variants.len; i++) { node_t *v = variants[i]; if (!union_variant_add(t, typ, v, i, &iota)) return NULL; } update_enum_layout(typ); n->sym->e.typ.info = typ; return (n->type = typ); } case NODE_RECORD: { record_decl_t *decl = &n->val.record_decl; node_t **fields = nodespan_ptrs(&t->module->parser, decl->fields); if (!declare_record(t, n)) { return NULL; } type_t *strct_typ = n->type; /* Add each field to the record. */ for (usize i = 0; i < decl->fields.len; i++) { node_t *f = fields[i]; var_decl_t *field = &f->val.var; type_t *field_type = resolve_type(t, field->type); if (!field_type) return NULL; if (!record_field_add(t, strct_typ, f, field->ident, field_type)) { return NULL; } } n->sym->e.typ.info = strct_typ; return strct_typ; } case NODE_RECORD_TYPE: return resolve_type(t, n); case NODE_RECORD_FIELD: /* Record fields are handled when processing the parent record. */ return n->type; case NODE_RECORD_LIT_FIELD: return n->type; case NODE_RECORD_LIT: { type_t *record_type = NULL; type_t *result_type = NULL; symbol_t *variant_sym = NULL; if (!resolve_record_literal_types( t, n->val.record_lit.type, expected, &record_type, &result_type, &variant_sym )) return NULL; if (!resolve_record_literal_fields(t, n, record_type)) return NULL; if (variant_sym) n->sym = variant_sym; return (n->type = result_type); } case NODE_NUMBER: return resolve_number(t, n, expected); case NODE_CHAR: return (n->type = t->types.type_u8); case NODE_STRING: return (n->type = t->types.type_str); case NODE_BOOL: return (n->type = t->types.type_bool); case NODE_UNDEF: return (n->type = expected); case NODE_NIL: if (expected) { if (expected->cls == TYPE_OPT) return (n->type = expected); return (n->type = alloc_opt_type(t, expected)); } return NULL; case NODE_SUPER: return NULL; case NODE_IDENT: case NODE_SCOPE: { bool pattern_ctx = (t->ctx == TC_CTX_PATTERN && n->cls == NODE_IDENT); symbol_t *sym = resolve_name(t, n, SYM_ANY); if (!sym) { if (pattern_ctx) { type_t *bind_type = expected ? expected : t->types.type_void; n->type = bind_type; n->sym = NULL; return bind_type; } return NULL; } type_t *scoped_type = n->type; switch (sym->kind) { case SYM_VARIABLE: case SYM_VARIANT: case SYM_CONSTANT: { if (!sym->node) return NULL; if (sym->node->cls == NODE_UNION_VARIANT) { if (!scoped_type || scoped_type->cls != TYPE_UNION) return NULL; type_t *variant_type = sym->node->type; if (variant_type->cls == TYPE_VOID) return (n->type = scoped_type); return NULL; } return (n->type = sym->node->type); } case SYM_FUNCTION: if (!sym->node) return NULL; sym->e.fn.used = true; n->type = sym->node->type; return n->type; case SYM_TYPE: if (!sym->node) return NULL; n->type = sym->node->type; return n->type; default: return NULL; } } case NODE_REF: { node_t *target = n->val.ref.target; type_t *target_typ = resolve_node(t, target, expected); if (!target_typ) return NULL; bool mut_ref = n->val.ref.mut; type_t *exp = expected; while (exp && exp->cls == TYPE_OPT) { exp = exp->info.opt.elem; } switch (target->cls) { case NODE_IDENT: { return (n->type = alloc_ptr_type(t, target_typ, mut_ref)); } case NODE_ARRAY_INDEX: { node_t *idx = target->val.access.rval; if (idx->cls == NODE_RANGE) { /* Array slice reference (e.g., &ary[0..3]) */ if (target_typ->info.slc.mut == mut_ref) { return (n->type = target_typ); } type_t *slice_type = alloc_slice_type( t, target_typ->info.slc.elem, target_typ->info.slc.base, mut_ref ); return (n->type = slice_type); } else { /* Array element reference (e.g., &ary[3]) */ return (n->type = alloc_ptr_type(t, target_typ, mut_ref)); } } case NODE_ARRAY_LIT: case NODE_ARRAY_REPEAT_LIT: /* Slice literal. */ if (target_typ->cls == TYPE_ARRAY) { type_t *slice_type = alloc_slice_type( t, target_typ->info.ary.elem, target_typ, mut_ref ); return (n->type = slice_type); } else if (target_typ->cls == TYPE_SLICE) { type_t *slice_type = alloc_slice_type( t, target_typ->info.slc.elem, target_typ->info.slc.base, mut_ref ); return (n->type = slice_type); } else { bail("unexpected slice literal type"); } case NODE_ACCESS: /* Field access. */ return (n->type = alloc_ptr_type(t, target_typ, mut_ref)); default: bail("can't take reference of %s", node_names[target->cls]); } } case NODE_UNOP: { switch (n->val.unop.op) { case OP_NOT: { type_t *typ = resolve_node(t, n->val.unop.expr, expected); if (!typ) return NULL; return (n->type = typ); } case OP_NEG: { type_t *typ = resolve_node(t, n->val.unop.expr, expected); if (!typ) return NULL; return (n->type = typ); } case OP_DEREF: { type_t *target_typ = resolve_node(t, n->val.unop.expr, NULL); if (!target_typ) return NULL; return (n->type = deref_type(target_typ)); } case OP_BNOT: { type_t *typ = resolve_node(t, n->val.unop.expr, expected); if (!typ) return NULL; return (n->type = typ); } default: abort(); } } case NODE_BINOP: { node_t *lhs = n->val.binop.left; node_t *rhs = n->val.binop.right; bool left_is_nil = lhs && lhs->cls == NODE_NIL; /* Check operands without forcing specific expected types */ type_t *left = NULL; type_t *right = NULL; if (left_is_nil && rhs && rhs->cls != NODE_NIL) { right = resolve_node(t, rhs, NULL); left = resolve_node(t, lhs, right); } else { left = resolve_node(t, lhs, NULL); right = resolve_node(t, rhs, left); } type_t *unified = NULL; if (!left && !right) return NULL; /* Check for pointer arithmetic before type unification */ if (n->val.binop.op == OP_ADD || n->val.binop.op == OP_SUB) { if (left && right) { /* Allow pointer + integer or integer + pointer */ if (left->cls == TYPE_PTR && type_is_int(right->cls)) { return (n->type = left); } if (n->val.binop.op == OP_ADD && right->cls == TYPE_PTR && type_is_int(left->cls)) { return (n->type = right); } } } bool coerce = n->val.binop.op == OP_EQ || n->val.binop.op == OP_NE; if (coerce && left && right) { if (left->cls == TYPE_OPT && right->cls != TYPE_OPT) { /* Flip arguments because coercion only applies to the rval */ unified = type_unify(t, right, left, n, coerce, "binary operation"); } else { unified = type_unify(t, left, right, n, coerce, "binary operation"); } } else { unified = type_unify(t, left, right, n, coerce, "binary operation"); } if (!unified) return NULL; /* Set operand types to unified type if they were previously NULL */ if (!left) n->val.binop.left->type = unified; if (!right) n->val.binop.right->type = unified; /* Check numeric operations. */ if (n->val.binop.op <= OP_MOD) { if (expected) { /* If we have an expected numeric type different from unified, * coerce to it. This will affect the instructions used by the * code generator. Note that we don't try to unify the two types * as this will promote the smaller type to the larger one. */ if (expected != unified) return (n->type = expected); } return (n->type = unified); } /* Check comparison operations. */ switch (n->val.binop.op) { case OP_EQ: case OP_NE: case OP_GT: case OP_LT: case OP_LE: case OP_GE: /* Update operand types to unified type for comparison */ n->val.binop.left->type = unified; n->val.binop.right->type = unified; return (n->type = t->types.type_bool); case OP_AND: case OP_OR: return (n->type = unified); case OP_BAND: case OP_BOR: case OP_XOR: case OP_SHL: case OP_SHR: return (n->type = unified); case OP_ADD: case OP_SUB: case OP_MUL: case OP_DIV: case OP_MOD: /* These are handled above in the numeric operations section */ abort(); } return NULL; } case NODE_ACCESS: { node_t *expr = n->val.access.lval; node_t *field = n->val.access.rval; type_t *decl_type = resolve_node(t, expr, NULL); if (!decl_type) return NULL; while (decl_type->cls == TYPE_PTR) decl_type = deref_type(decl_type); if (decl_type->cls == TYPE_RECORD) { symbol_t *field_sym = record_field_lookup(decl_type, field); if (!field_sym) return NULL; n->sym = field_sym; return (n->type = field_sym->e.field.typ); } else if (decl_type->cls == TYPE_ARRAY) { if (ident_eq(field, LEN_FIELD, LEN_FIELD_LEN)) { n->cls = NODE_NUMBER; n->type = t->types.type_u32; n->val.number.value.u = decl_type->info.ary.length; n->val.number.text = NULL; n->val.number.text_len = 0; return n->type; } return NULL; } else if (decl_type->cls == TYPE_SLICE) { if (ident_eq(field, LEN_FIELD, LEN_FIELD_LEN)) return (n->type = t->types.type_u32); if (ident_eq(field, PTR_FIELD, PTR_FIELD_LEN)) return (n->type = decl_type->info.slc.elem->ptr); return NULL; } return NULL; } case NODE_USE: return resolve_use(t, n); case NODE_CALL: { node_t *callee = n->val.call.callee; symbol_t *sym = resolve_name(t, callee, SYM_ANY); if (!sym) return NULL; /* Function call */ if (sym->kind == SYM_FUNCTION) { n->sym = sym; return resolve_call_fn(t, sym, n); } /* Tuple record constructor call */ if (sym->kind == SYM_TYPE) { type_t *typ = sym->e.typ.info; if (typ && typ->cls == TYPE_RECORD && typ->info.srt.tuple) { return resolve_tuple_record_constructor(t, n, typ); } } /* Function pointer call */ if (sym->kind == SYM_VARIABLE) { if (callee->cls == NODE_IDENT) { if (sym->node->type && sym->node->type->cls == TYPE_FN) { n->sym = sym; return resolve_call_fn_ptr(t, sym, n); } return NULL; } } else if (sym->kind == SYM_VARIANT) { if (callee->cls == NODE_SCOPE) { type_t *scope = resolve_scope(t, callee); if (scope && type_is_union_with_payload(scope)) return resolve_enum_constructor(t, n, scope, sym); } } return NULL; } case NODE_BUILTIN: return resolve_builtin(t, n, expected); case NODE_CALL_ARG: return (n->type = resolve_node(t, n->val.call_arg.expr, expected)); case NODE_THROW: return resolve_throw(t, n); case NODE_TRY: return resolve_try_expr(t, n, expected); case NODE_CATCH: bail("cannot type check %s", node_names[n->cls]); case NODE_RETURN: { type_t *fn_ret = t->fn->node->type->info.fun.ret; type_t *expected = fn_ret; if (fn_ret->cls == TYPE_RESULT) expected = fn_ret->info.res.payload; if (expected == t->types.type_void) { if (n->val.return_stmt.value) return NULL; return (n->type = fn_ret); } if (n->val.return_stmt.value) { if (!resolve_node(t, n->val.return_stmt.value, expected)) return NULL; } return (n->type = fn_ret); } case NODE_IF: if (!resolve_node(t, n->val.if_stmt.cond, t->types.type_bool)) return NULL; type_t *result_typ = expected ? expected : t->types.type_void; type_t *lbranch_typ = resolve_node(t, n->val.if_stmt.lbranch, expected); if (!lbranch_typ) return NULL; if (n->val.if_stmt.rbranch) { type_t *rbranch_typ = resolve_node(t, n->val.if_stmt.rbranch, expected); if (!rbranch_typ) return NULL; if (!expected) { type_t *unified = type_unify(t, lbranch_typ, rbranch_typ, n, false, NULL); if (unified) result_typ = unified; } } return (n->type = result_typ); case NODE_IF_LET: { type_t *expr_type = resolve_node(t, n->val.if_let_stmt.expr, NULL); if (!expr_type) return NULL; /* Create scope for the bound variable */ n->val.if_let_stmt.scope = symtab_scope(t->scope, NULL); n->val.if_let_stmt.var->type = expr_type->info.opt.elem; t->scope = n->val.if_let_stmt.scope; /* Add the bound variable to the scope */ if (!symbol_add(t, n->val.if_let_stmt.var, n->val.if_let_stmt.var)) return NULL; /* Only set symbol data if not a placeholder */ if (n->val.if_let_stmt.var->cls != NODE_PLACEHOLDER) { n->val.if_let_stmt.var->sym->e.var.typ = expr_type->info.opt.elem; n->val.if_let_stmt.var->sym->e.var.align = expr_type->info.opt.elem->align; n->val.if_let_stmt.var->sym->scope = t->scope; } if (n->val.if_let_stmt.guard) { if (!resolve_node(t, n->val.if_let_stmt.guard, t->types.type_bool)) return NULL; } if (!resolve_block(t, n->val.if_let_stmt.lbranch)) return NULL; t->scope = t->scope->parent; if (n->val.if_let_stmt.rbranch) { if (!resolve_block(t, n->val.if_let_stmt.rbranch)) return NULL; } return (n->type = t->types.type_void); } case NODE_MATCH: { /* Check the match operand */ type_t *match_typ = resolve_node(t, n->val.match_stmt.expr, NULL); if (!match_typ) return NULL; /* Check each case to ensure patterns match the * match operand type. */ node_t **cases = nodespan_ptrs(&t->module->parser, n->val.match_stmt.cases); bool all_diverge = n->val.match_stmt.cases.len > 0; for (usize i = 0; i < n->val.match_stmt.cases.len; i++) { node_t *c = cases[i]; type_t *case_typ = resolve_match_case(t, c, match_typ); if (!case_typ) return NULL; /* Check if this case diverges. */ if (!node_diverges(c->val.match_case.body)) all_diverge = false; } /* Match diverges if all cases diverge. */ if (all_diverge) return (n->type = t->types.type_never); return (n->type = t->types.type_void); } case NODE_MATCH_CASE: /* Handled in `NODE_MATCH` */ case NODE_BLOCK: return (n->type = resolve_block(t, n)); case NODE_FN: /* Handled at the module level */ case NODE_LOOP: return (n->type = resolve_block(t, n->val.loop_stmt.body)); case NODE_BREAK: return (n->type = t->types.type_never); case NODE_VAR: return resolve_var(t, n); case NODE_CONST: return resolve_const(t, n); case NODE_STATIC: return resolve_static(t, n); case NODE_PARAM: abort(); case NODE_ASSIGN: { type_t *ltype = resolve_node(t, n->val.assign.lval, NULL); if (!ltype) return NULL; if (!resolve_node(t, n->val.assign.rval, ltype)) return NULL; return (n->type = ltype); } case NODE_ATTRIBUTE: return (n->type = t->types.type_void); case NODE_EXPR_STMT: { /* Check the expression but don't use its result value. */ type_t *typ = resolve_node(t, n->val.expr_stmt, NULL); if (!typ) return NULL; /* Expression statements don't produce values. */ return (n->type = t->types.type_void); } case NODE_MOD: return resolve_mod_decl(t, n); case NODE_RANGE: { /* Check range start expression if provided */ if (n->val.range.start) { if (!resolve_node(t, n->val.range.start, t->types.type_u32)) return NULL; } /* Check range end expression if provided */ if (n->val.range.end) { if (!resolve_node(t, n->val.range.end, t->types.type_u32)) return NULL; } /* Range nodes don't have a specific type, they're contextual */ return (n->type = t->types.type_void); } case NODE_AS: { if (!resolve_node(t, n->val.as_expr.expr, NULL)) return NULL; type_t *target_type = resolve_type(t, n->val.as_expr.type); if (!target_type) return NULL; return (n->type = target_type); } case NODE_PANIC: return (n->type = t->types.type_never); case NODE_WHILE: case NODE_WHILE_LET: case NODE_IF_CASE: case NODE_GUARD_CASE: case NODE_GUARD_LET: case NODE_FOR: case NODE_PLACEHOLDER: /* Placeholders don't produce a value, so return NULL type */ return (n->type = NULL); case NODE_TYPE: case NODE_UNION_VARIANT: case NODE_PTR: case NODE_MOD_BODY: case NODE_ALIGN: bail("unsupported node type %s", node_names[n->cls]); } return NULL; } static node_t *binding_ident(node_t *n) { switch (n->cls) { case NODE_VAR: return n->val.var.ident; case NODE_CONST: return n->val.constant.ident; case NODE_STATIC: return n->val.static_decl.ident; default: bail("unexpected binding node %s", node_names[n->cls]); } } static type_t *resolve_binding( resolve_t *t, node_t *n, node_t *val, node_t *typ ) { type_t *declared = NULL; if (typ) { /* Resolve the declared type before checking the value */ if (!(declared = resolve_type(t, typ))) return NULL; } /* Check the value with the declared type as expected type */ type_t *inferred = resolve_node(t, val, declared); if (!inferred) return NULL; type_t *final_type = inferred; if (declared) { final_type = type_unify(t, inferred, declared, val, true, "variable binding"); if (!final_type) return NULL; } node_t *ident = binding_ident(n); /* symbol_add handles placeholders internally */ if (!symbol_add(t, ident, n)) return NULL; /* Only set symbol data if not a placeholder */ if (ident->cls != NODE_PLACEHOLDER) { n->sym->scope = t->scope; n->sym->e.var.typ = final_type; n->sym->e.var.align = final_type->align; } return (n->type = final_type); } /* Check if a `const` declaration is valid. */ static type_t *resolve_const(resolve_t *t, node_t *n) { return resolve_binding(t, n, n->val.constant.value, n->val.constant.type); } static type_t *resolve_static(resolve_t *t, node_t *n) { return resolve_binding( t, n, n->val.static_decl.value, n->val.static_decl.type ); } /* Check if a `let` or `mut` declaration is valid. */ static type_t *resolve_var(resolve_t *t, node_t *n) { node_t *type = n->val.var.type; node_t *value = n->val.var.value; if (!value) return NULL; type_t *var_type = resolve_binding(t, n, value, type); if (!var_type) return NULL; if (n->val.var.align) { node_t *align = n->val.var.align->val.align; if (!resolve_node(t, align, t->types.type_u32)) return NULL; usize c = 0; if (!resolve_const_usize(t, align, &c)) return NULL; n->sym->e.var.align = (i32)c; } return var_type; } static bool node_diverges(node_t *n) { if (!n) return false; switch (n->cls) { case NODE_RETURN: case NODE_THROW: case NODE_PANIC: return true; case NODE_BLOCK: return n->type && n->type->cls == TYPE_NEVER; case NODE_IF: { node_t *then_branch = n->val.if_stmt.lbranch; node_t *else_branch = n->val.if_stmt.rbranch; if (!then_branch || !else_branch) return false; return node_diverges(then_branch) && node_diverges(else_branch); } case NODE_IF_LET: case NODE_IF_CASE: { node_t *then_branch = NULL; node_t *else_branch = NULL; if (n->cls == NODE_IF_LET) { then_branch = n->val.if_let_stmt.lbranch; else_branch = n->val.if_let_stmt.rbranch; } else { then_branch = n->val.if_case_stmt.lbranch; else_branch = n->val.if_case_stmt.rbranch; } if (!then_branch || !else_branch) return false; return node_diverges(then_branch) && node_diverges(else_branch); } case NODE_EXPR_STMT: { node_t *expr = n->val.expr_stmt; if (!expr) return false; if (expr->type && expr->type->cls == TYPE_NEVER) return true; if (expr->cls == NODE_CALL && expr->sym && expr->sym->kind == SYM_FUNCTION) { const char *qualified = expr->sym->qualified; if (qualified && strcmp(qualified, "core::intrinsics::ebreak") == 0) { return true; } } return false; } case NODE_MATCH: /* Match diverges if its type is TYPE_NEVER (all cases diverge). */ return n->type && n->type->cls == TYPE_NEVER; default: break; } return false; } /* Check a code block. */ static type_t *resolve_block(resolve_t *t, node_t *n) { /* Create a new scope for this block. */ scope_t *parent = t->scope; n->val.block.scope = symtab_scope(parent, NULL); t->scope = n->val.block.scope; /* Check each statement in the block. */ node_t **stmts = nodespan_ptrs(&t->module->parser, n->val.block.stmts); for (usize i = 0; i < n->val.block.stmts.len; i++) { if (!resolve_node(t, stmts[i], NULL)) return NULL; } /* Return to parent scope. */ t->scope = parent; type_t *block_type = t->types.type_void; if (n->val.block.stmts.len > 0) { node_t *last = stmts[n->val.block.stmts.len - 1]; if (node_diverges(last)) block_type = t->types.type_never; } return (n->type = block_type); } /* Type check a complete AST, starting from the root module. */ bool resolve_run(resolve_t *t, module_t *root) { if (!resolve_mod_def(t, root)) return false; for (usize i = 0; i < t->mm->nmodules; i++) { module_t *mod = &t->mm->modules[i]; if (!mod->checked) { if (!resolve_mod_def(t, mod)) { return false; } } } return true; } /* Type check a module. */ static bool resolve_mod_def(resolve_t *t, module_t *module) { /* First check all function signatures and type declarations */ if (!resolve_decls(t, module)) { return false; } if (module->state == MODULE_STATE_VISITING) return false; if (module->state == MODULE_STATE_VISITED && module->checked) { return true; } module_t *pmodule = t->module; scope_t *pscope = t->scope; module->state = MODULE_STATE_VISITING; t->module = module; t->scope = module->scope; /* Type check function bodies */ node_t **mod_stmts = nodespan_ptrs(&module->parser, module->ast->val.block.stmts); for (usize i = 0; i < module->ast->val.block.stmts.len; i++) { node_t *stmt = mod_stmts[i]; if (stmt->cls == NODE_FN) { if (!resolve_fn_def(t, stmt)) { return false; } if (stmt->val.fn_decl.attribs && stmt->val.fn_decl.attribs->val.attrib & ATTRIB_DEFAULT) { if (module->default_fn == NULL) { module->default_fn = stmt->sym; } } } } if (!module->default_fn) { for (usize i = 0; i < module->ast->val.block.stmts.len; i++) { node_t *stmt = mod_stmts[i]; if (stmt->cls == NODE_FN && stmt->val.fn_decl.attribs && stmt->val.fn_decl.attribs->val.attrib & ATTRIB_DEFAULT && stmt->sym) { module->default_fn = stmt->sym; break; } } } module->checked = true; module->state = MODULE_STATE_VISITED; module->ast->type = t->types.type_void; t->module = pmodule; t->scope = pscope; return true; } /* Check function and type declarations */ static bool resolve_decls(resolve_t *t, module_t *module) { if (module->state == MODULE_STATE_VISITING) return false; if (module->state == MODULE_STATE_VISITED && module->declared) { return true; } module_t *parent = t->module; module->state = MODULE_STATE_VISITING; module->scope = symtab_scope(t->scope, module); t->module = module; t->scope = module->scope; node_t *module_stmts[MAX_BLOCK_STATEMENTS] = { 0 }; module_t *module_refs[MAX_BLOCK_STATEMENTS] = { 0 }; usize nmodules = 0; /* Predeclare child modules so their symbols are available early. */ node_t **decl_stmts = nodespan_ptrs(&module->parser, module->ast->val.block.stmts); for (usize i = 0; i < module->ast->val.block.stmts.len; i++) { node_t *stmt = decl_stmts[i]; if (stmt->cls != NODE_MOD) continue; node_t *name = stmt->val.mod_decl.ident; char rel[MAX_PATH_LEN] = { 0 }; strncpy(rel, name->val.ident.name, name->val.ident.length); module_t *submod = module_manager_find_relative(t->mm, module->path, rel); if (!submod) { if (stmt->val.mod_decl.attribs && (stmt->val.mod_decl.attribs->val.attrib & ATTRIB_TEST)) continue; return false; } symbol_t *sym = symtab_scope_lookup( module->scope, name->val.ident.name, name->val.ident.length, SYM_MODULE ); if (!sym) { if (!symbol_add(t, name, stmt)) { return false; } sym = stmt->sym; } else { stmt->sym = sym; } sym->e.mod = submod; sym->scope = submod->scope; submod->attribs = stmt->val.mod_decl.attribs ? stmt->val.mod_decl.attribs->val.attrib : ATTRIB_NONE; module_path(submod->qualified, module->qualified); module_qualify(submod->qualified, name); module_stmts[nmodules] = stmt; module_refs[nmodules++] = submod; } /* Predeclare named types so mutually recursive definitions can resolve. */ for (usize i = 0; i < module->ast->val.block.stmts.len; i++) { node_t *stmt = decl_stmts[i]; if (stmt->cls == NODE_RECORD) { if (!declare_record(t, stmt)) { return false; } } else if (stmt->cls == NODE_UNION) { if (!declare_enum(t, stmt)) { return false; } } } for (usize i = 0; i < module->ast->val.block.stmts.len; i++) { node_t *stmt = decl_stmts[i]; switch (stmt->cls) { case NODE_USE: if (!resolve_use(t, stmt)) { return false; } break; case NODE_FN: if (!resolve_fn_decl(t, stmt)) { return false; } break; case NODE_RECORD: case NODE_UNION: if (!resolve_node(t, stmt, NULL)) { return false; } break; case NODE_MOD: stmt->type = t->types.type_void; break; case NODE_CONST: if (!resolve_const(t, stmt)) { return false; } break; case NODE_STATIC: if (!resolve_static(t, stmt)) { return false; } break; default: break; } } /* Check submodule declarations after parent types are sized, * so that `super::` references can resolve to fully-typed symbols. * Skip submodules that are already being visited to avoid false * circular dependency errors when `use X::Y` directly imports a * submodule and that submodule uses `super::`. */ for (usize i = 0; i < nmodules; i++) { module_t *submod = module_refs[i]; if (!submod->declared && submod->state != MODULE_STATE_VISITING) { if (!resolve_decls(t, submod)) { return false; } } if (module_stmts[i] && module_stmts[i]->sym) { module_stmts[i]->sym->scope = submod->scope; } } for (usize i = 0; i < nmodules; i++) { module_t *submod = module_refs[i]; if (!submod) return false; /* Skip submodules that are already being visited to avoid false * circular dependency errors. They will be checked by their * original caller. */ if (submod->state == MODULE_STATE_VISITING) { continue; } if (!resolve_mod_def(t, submod)) { return false; } } finalize_type_layout(t); module->declared = true; module->state = MODULE_STATE_VISITED; module->ast->type = t->types.type_void; t->scope = t->scope->parent; t->module = parent; return true; } /* Register a function signature without checking its body */ static type_t *resolve_fn_decl(resolve_t *t, node_t *n) { fn_decl_t *fn = &n->val.fn_decl; /* Check attributes. */ if (fn->attribs && !resolve_node(t, fn->attribs, NULL)) return NULL; attrib_t attrs = fn->attribs ? fn->attribs->val.attrib : ATTRIB_NONE; /* Add function to symbol table */ if (!symbol_add(t, fn->ident, n)) { return NULL; } n->sym->e.fn.attribs = attrs; /* Set up the qualified name for the function */ module_path(n->sym->qualified, t->module->qualified); module_qualify(n->sym->qualified, fn->ident); /* Initialize usage tracking - mark as used if it's a default function */ n->sym->e.fn.used = (attrs & ATTRIB_DEFAULT) || (attrs & ATTRIB_TEST); /* Initialize function type and scope */ type_t *ret_typ = n->val.fn_decl.return_type ? resolve_type(t, n->val.fn_decl.return_type) : t->types.type_void; n->sym->e.fn.scope = symtab_scope(t->scope, NULL); n->type = alloc_fn_type(t, n, ret_typ, n->val.fn_decl.params.len); /* Enter function scope temporarily to register parameters */ scope_t *parent = t->scope; t->scope = n->sym->e.fn.scope; /* Add parameters to function scope */ for (usize i = 0; i < n->val.fn_decl.params.len; i++) { node_t *param = nodespan_ptrs(&t->module->parser, n->val.fn_decl.params)[i]; /* Assign declared type to identifier node. */ node_t *type = param->val.param.type; type_t *declared = resolve_type(t, type); if (!declared) { return NULL; } param->type = declared; /* Store parameter type in function type for function pointer * compatibility */ n->type->info.fun.params[i] = declared; if (!symbol_add(t, param->val.param.ident, param)) { return NULL; } param->sym->e.var.typ = declared; param->sym->e.var.align = declared->align; } t->scope = parent; if (!resolve_fn_throws(t, n->type, fn->throws, ret_typ)) return NULL; return n->type; } /* Type check function body (assumes signature is already registered) */ static type_t *resolve_fn_def(resolve_t *t, node_t *n) { /* Set current function and enter function scope */ t->fn = n->sym; t->scope = n->sym->e.fn.scope; /* For extern functions, body will be NULL */ if (n->val.fn_decl.body && !resolve_block(t, n->val.fn_decl.body)) { t->fn = NULL; t->scope = t->scope->parent; return NULL; } t->fn = NULL; t->scope = t->scope->parent; return n->type; }