/* * check.c — name resolution + type checking pass. * * Two-stage: * 1) collect: walk top-level decls and install Syms with stub types. * 2) resolve: expand types, check fn bodies and def initialisers. * * Errors do not stop the walk — we keep going so the user gets many * diagnostics from one run. Nodes get their resolved Type attached. */ #include "ww.h" #include static void cstmt(Checker*, Node*); static Type *cexpr(Checker*, Node*); static Type *resolve_type(Checker*, Node*); static Type * err(Checker *c, Pos p, const char *fmt, ...) { (void)c; va_list ap; fprintf(errout ? errout : stderr, "%s:%d:%d: error: ", p.file ? p.file : "?", p.line, p.col); va_start(ap, fmt); vfprintf(errout ? errout : stderr, fmt, ap); va_end(ap); fputc('\n', errout ? errout : stderr); c->errs++; return ty_err; } static Type * lookup_builtin(const char *name) { if (strcmp(name, "void") == 0) return ty_void; if (strcmp(name, "bool") == 0) return ty_bool; if (strcmp(name, "rune") == 0) return ty_rune; if (strcmp(name, "i8") == 0) return ty_i8; if (strcmp(name, "i16") == 0) return ty_i16; if (strcmp(name, "i32") == 0) return ty_i32; if (strcmp(name, "i64") == 0) return ty_i64; if (strcmp(name, "u8") == 0) return ty_u8; if (strcmp(name, "u16") == 0) return ty_u16; if (strcmp(name, "u32") == 0) return ty_u32; if (strcmp(name, "u64") == 0) return ty_u64; if (strcmp(name, "int") == 0) return ty_int; if (strcmp(name, "uint") == 0) return ty_uint; if (strcmp(name, "uintptr") == 0) return ty_uintptr; if (strcmp(name, "f32") == 0) return ty_f32; if (strcmp(name, "f64") == 0) return ty_f64; if (strcmp(name, "str") == 0) return ty_str; return NULL; } static Type * resolve_typename(Checker *c, Node *n) { const char *nm = n->str; Type *bi = lookup_builtin(nm); if (bi) return bi; Sym *s = scope_lookup(c->cur, nm); if (s == NULL && nm) { /* module-qualified: io.stream → strip the last dot prefix * and look up the leaf if `io` is a `use`-imported name. */ const char *dot = strrchr(nm, '.'); if (dot) { char head[128] = {0}; size_t hl = (size_t)(dot - nm); if (hl < sizeof head) memcpy(head, nm, hl); Sym *m = scope_lookup(c->cur, head); if (m && m->kind == SK_USE) s = scope_lookup(c->cur, dot + 1); } } if (s == NULL || s->kind != SK_TYPE) return err(c, n->pos, "unknown type '%s'", nm); return s->type; } static Type * resolve_type(Checker *c, Node *n) { if (n == NULL) return ty_void; switch (n->kind) { case N_TNAME: return resolve_typename(c, n); case N_TPTR: return type_ptr(c->a, resolve_type(c, n->lhs)); case N_TSLICE: return type_slice(c->a, resolve_type(c, n->lhs)); case N_TARRAY: { u64 len = 0; if (n->rhs && n->rhs->kind == N_INTLIT) len = n->rhs->uval; else err(c, n->pos, "array length must be an integer literal"); return type_array(c->a, resolve_type(c, n->lhs), len); } case N_TCHAN: return type_chan(c->a, resolve_type(c, n->lhs)); case N_TTUPLE: { Type *t = newtype(c->a, TY_TUPLE); Tparam *head = NULL, *tail = NULL; u64 sz = 0, al = 1; for (Node *e = n->list; e; e = e->next) { Tparam *tp = amalloc(c->a, sizeof *tp); tp->type = resolve_type(c, e); if (tp->type && tp->type->align > al) al = tp->type->align; if (tp->type) sz += tp->type->size; if (head == NULL) head = tp; else tail->next = tp; tail = tp; } t->params = head; t->size = sz; t->align = al; return t; } case N_TTAGGED: { /* (T1 | T2 | ...) — tag (8B) followed by the largest variant. */ Type *t = newtype(c->a, TY_TAGGED); Tparam *head = NULL, *tail = NULL; u64 maxsz = 0, al = 8; for (Node *e = n->list; e; e = e->next) { Tparam *tp = amalloc(c->a, sizeof *tp); tp->type = resolve_type(c, e); if (tp->type && tp->type->size > maxsz) maxsz = tp->type->size; if (tp->type && tp->type->align > al) al = tp->type->align; if (head == NULL) head = tp; else tail->next = tp; tail = tp; } t->params = head; t->size = 8 + maxsz; t->align = al; return t; } case N_TFN: { Type *t = newtype(c->a, TY_FN); t->ret = resolve_type(c, n->lhs); t->size = 8; t->align = 8; Tparam *head = NULL, *tail = NULL; for (Node *p = n->list; p; p = p->next) { if (strcmp(p->str ? p->str : "", "...") == 0) { t->variadic = 1; continue; } Tparam *tp = amalloc(c->a, sizeof *tp); tp->name = p->str; tp->type = resolve_type(c, p->lhs); if (head == NULL) head = tp; else tail->next = tp; tail = tp; } t->params = head; return t; } case N_TSTRUCT: { Type *t = newtype(c->a, TY_STRUCT); Tfield *head = NULL, *tail = NULL; u64 off = 0, maxalign = 1; for (Node *f = n->list; f; f = f->next) { Tfield *tf = amalloc(c->a, sizeof *tf); tf->name = f->str; tf->type = resolve_type(c, f->lhs); if (tf->type->align > maxalign) maxalign = tf->type->align; off = (off + tf->type->align - 1) & ~(tf->type->align - 1); tf->offset = off; off += tf->type->size; if (head == NULL) head = tf; else tail->next = tf; tail = tf; } t->fields = head; t->align = maxalign; t->size = (off + maxalign - 1) & ~(maxalign - 1); return t; } default: return err(c, n->pos, "expected type expression"); } } /* ---- expressions -------------------------------------------------- */ static Type * unify_arith(Checker *c, Pos p, Type *a, Type *b) { if (a == ty_err || b == ty_err) return ty_err; /* untyped + untyped → untyped (prefer float over int) */ if (type_isuntyped(a) && type_isuntyped(b)) { if (a->kind == TY_UNTYPED_FLOAT || b->kind == TY_UNTYPED_FLOAT) return ty_untyped_float; return ty_untyped_int; } /* untyped + typed → typed (if assignable) */ if (type_isuntyped(a) && type_assignable(b, a)) return b; if (type_isuntyped(b) && type_assignable(a, b)) return a; if (type_eq(a, b)) return a; return err(c, p, "operands have differing types %s and %s", type_name(c->a, a), type_name(c->a, b)); } static Type * cbinop(Checker *c, Node *n) { Type *l = cexpr(c, n->lhs); Type *r = cexpr(c, n->rhs); switch (n->op) { case TK_PLUS: case TK_MINUS: case TK_STAR: case TK_SLASH: case TK_PERCENT: /* pointer arithmetic: ptr ± int → ptr; ptr - ptr → int */ if ((n->op == TK_PLUS || n->op == TK_MINUS) && l && l->kind == TY_PTR && type_isint(r)) return l; if (n->op == TK_PLUS && type_isint(l) && r && r->kind == TY_PTR) return r; if (n->op == TK_MINUS && l && r && l->kind == TY_PTR && r->kind == TY_PTR) return ty_i64; if (!type_isnum(l) || !type_isnum(r)) return err(c, n->pos, "arithmetic on non-numeric type"); return unify_arith(c, n->pos, l, r); case TK_AMP: case TK_PIPE: case TK_CARET: case TK_LSHIFT: case TK_RSHIFT: if (!type_isint(l) || !type_isint(r)) return err(c, n->pos, "bitwise on non-integer type"); return unify_arith(c, n->pos, l, r); case TK_EQ: case TK_NEQ: (void)unify_arith(c, n->pos, l, r); return ty_bool; case TK_LT: case TK_LE: case TK_GT: case TK_GE: if (!type_isnum(l) || !type_isnum(r)) err(c, n->pos, "ordered comparison on non-numeric"); (void)unify_arith(c, n->pos, l, r); return ty_bool; case TK_AND: case TK_OR: if (!(l == ty_bool || l == ty_untyped_bool || l == ty_err)) err(c, n->pos, "left of %s is not bool", tokname(n->op)); if (!(r == ty_bool || r == ty_untyped_bool || r == ty_err)) err(c, n->pos, "right of %s is not bool", tokname(n->op)); return ty_bool; default: return err(c, n->pos, "unsupported binary op %s", tokname(n->op)); } } static Type * cunop(Checker *c, Node *n) { Type *t = cexpr(c, n->lhs); switch (n->op) { case TK_MINUS: case TK_PLUS: if (!type_isnum(t)) return err(c, n->pos, "%s on non-numeric", tokname(n->op)); return t; case TK_NOT: if (!(t == ty_bool || t == ty_untyped_bool || t == ty_err)) err(c, n->pos, "! on non-bool"); return ty_bool; case TK_TILDE: if (!type_isint(t)) return err(c, n->pos, "~ on non-integer"); return t; case TK_STAR: /* deref */ if (t == ty_err) return ty_err; if (t->kind != TY_PTR) return err(c, n->pos, "cannot deref non-pointer %s", type_name(c->a, t)); return t->sub; case TK_AMP: /* address-of */ return type_ptr(c->a, t); default: return err(c, n->pos, "unsupported unary %s", tokname(n->op)); } } static Type * cexpr(Checker *c, Node *n) { if (n == NULL) return ty_err; switch (n->kind) { case N_INTLIT: if (n->tsuffix) { Type *t = lookup_builtin(n->tsuffix); n->type = t ? t : ty_untyped_int; } else { n->type = ty_untyped_int; } return n->type; case N_FLOATLIT: if (n->tsuffix) { Type *t = lookup_builtin(n->tsuffix); n->type = t ? t : ty_untyped_float; } else { n->type = ty_untyped_float; } return n->type; case N_STRLIT: n->type = ty_untyped_str; return n->type; case N_RUNELIT: n->type = ty_untyped_rune; return n->type; case N_TRUE: case N_FALSE: n->type = ty_untyped_bool; return n->type; case N_NIL: n->type = ty_untyped_nil; return n->type; case N_IDENT: { Sym *s = scope_lookup(c->cur, n->str); if (s == NULL) return n->type = err(c, n->pos, "undefined: %s", n->str); /* SK_USE has no concrete value type; the only legal use is * as the lhs of a DOT (module-qualified ref). Surface ty_err * here; the DOT case below resolves the qualified symbol. */ if (s->kind == SK_USE) return n->type = ty_err; n->type = s->type; return s->type; } case N_PARAM: return n->type = ty_err; /* shouldn't appear in expr ctx */ case N_BIN: n->type = cbinop(c, n); return n->type; case N_UN: n->type = cunop(c, n); return n->type; case N_CAST: { (void)cexpr(c, n->lhs); n->type = resolve_type(c, n->rhs); return n->type; } case N_DOT: { /* module-qualified: lhs is an N_IDENT bound as SK_USE. * Resolve to the symbol with the same leaf name. With * driver-side concatenation, all symbols live in flat * scope, so we lookup `n->str` directly. */ if (n->lhs && n->lhs->kind == N_IDENT) { Sym *ms = scope_lookup(c->cur, n->lhs->str); if (ms && ms->kind == SK_USE) { Sym *fs = scope_lookup(c->cur, n->str); if (fs) return n->type = fs->type; /* Leaf isn't in scope here — treat as an * external declaration. The codegen will * still emit CALL/MOVQ by the leaf name; the * linker fails if the symbol is truly * missing. */ return n->type = ty_err; } } Type *base = cexpr(c, n->lhs); if (base == NULL || base == ty_err) return n->type = ty_err; Type *u = (base->kind == TY_NAMED) ? base->under : base; if (u && u->kind == TY_PTR) u = u->sub; if (u && u->kind == TY_NAMED) u = u->under; /* built-in pseudo-fields on slice/str/array: .len, .cap, .ptr */ if (u && (u->kind == TY_SLICE || u->kind == TY_ARRAY || u->kind == TY_STR)) { if (strcmp(n->str, "len") == 0) return n->type = ty_i32; if (strcmp(n->str, "cap") == 0) return n->type = ty_i32; if (strcmp(n->str, "ptr") == 0) { Type *elem = (u->kind == TY_STR) ? ty_u8 : u->sub; return n->type = type_ptr(c->a, elem); } } if (u && u->kind == TY_STRUCT) { for (Tfield *f = u->fields; f; f = f->next) if (strcmp(f->name, n->str) == 0) return n->type = f->type; return n->type = err(c, n->pos, "no field '%s' in %s", n->str, type_name(c->a, base)); } /* tuple positional access: t.0, t.1, ... */ if (u && u->kind == TY_TUPLE && n->str) { int idx = 0; for (const char *q = n->str; *q; q++) { if (*q < '0' || *q > '9') { idx = -1; break; } idx = idx * 10 + (*q - '0'); } if (idx < 0) return n->type = err(c, n->pos, "tuple field must be numeric"); Tparam *tp = u->params; while (idx > 0 && tp) { tp = tp->next; idx--; } if (tp == NULL) return n->type = err(c, n->pos, "tuple index out of range"); return n->type = tp->type; } /* module-qualified: lhs is IDENT bound as SK_USE */ return n->type = ty_err; } case N_INDEX: { Type *base = cexpr(c, n->lhs); Type *idx = cexpr(c, n->rhs); if (idx != ty_err && !type_isint(idx)) err(c, n->pos, "index must be integer"); if (base == ty_err) return n->type = ty_err; Type *u = (base->kind == TY_NAMED) ? base->under : base; if (u && (u->kind == TY_SLICE || u->kind == TY_ARRAY)) return n->type = u->sub; if (u && u->kind == TY_STR) return n->type = ty_u8; if (u && u->kind == TY_PTR && u->sub && (u->sub->kind == TY_ARRAY || u->sub->kind == TY_SLICE)) return n->type = u->sub->sub; /* C-style pointer indexing: p[i] → *(p+i) */ if (u && u->kind == TY_PTR && u->sub) return n->type = u->sub; return n->type = err(c, n->pos, "indexing non-indexable %s", type_name(c->a, base)); } case N_CALL: { /* Hare-style builtins: len(x), append(s, v), alloc(...). * Recognised by name with no scope binding; we type-check * the args ourselves and skip the normal call resolution. */ if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str && strcmp(n->lhs->str, "len") == 0 && n->list != NULL && n->list->next == NULL) { (void)cexpr(c, n->list); n->type = ty_i32; n->lhs->type = ty_err; /* mark builtin: no real symbol */ return n->type; } if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str && strcmp(n->lhs->str, "append") == 0 && n->list != NULL && n->list->next != NULL) { for (Node *a = n->list; a; a = a->next) (void)cexpr(c, a); n->type = ty_void; n->lhs->type = ty_err; return n->type; } if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str && strcmp(n->lhs->str, "alloc") == 0 && n->list != NULL && n->list->next == NULL) { Type *t = cexpr(c, n->list); Type *def = type_default(t); n->type = type_ptr(c->a, def ? def : ty_void); n->lhs->type = ty_err; return n->type; } if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str && strcmp(n->lhs->str, "free") == 0 && n->list != NULL && n->list->next == NULL) { (void)cexpr(c, n->list); n->type = ty_void; n->lhs->type = ty_err; return n->type; } /* alloc([], n) — Hare-style fresh slice with cap n. We pin * the element type to u8 by default; the caller's declared * slice type drives the actual element size at codegen. */ if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str && strcmp(n->lhs->str, "alloc") == 0 && n->list && n->list->kind == N_ARRLIT && n->list->list == NULL && n->list->next && n->list->next->next == NULL) { (void)cexpr(c, n->list->next); n->type = type_slice(c->a, ty_u8); n->lhs->type = ty_err; return n->type; } Type *ft = cexpr(c, n->lhs); if (ft == ty_err) { /* Walk args anyway so cgen sees real types. The * common case is a module-qualified call whose leaf * isn't in this scope (raw w6c on a single file with * `use mod;` but no driver concatenation). */ for (Node *a = n->list; a; a = a->next) (void)cexpr(c, a); return n->type = ty_err; } Type *u = (ft->kind == TY_NAMED) ? ft->under : ft; if (u == NULL || u->kind != TY_FN) return n->type = err(c, n->pos, "calling non-function %s", type_name(c->a, ft)); Tparam *p = u->params; for (Node *a = n->list; a; a = a->next) { Type *at = cexpr(c, a); if (p == NULL) { if (!u->variadic) err(c, n->pos, "too many arguments"); continue; } if (!type_assignable(p->type, at) && at != ty_err && p->type != ty_err) err(c, a->pos, "argument type %s not assignable to %s", type_name(c->a, at), type_name(c->a, p->type)); p = p->next; } if (p != NULL) err(c, n->pos, "not enough arguments"); return n->type = u->ret ? u->ret : ty_void; } case N_ASSIGN: { Type *l = cexpr(c, n->lhs); Type *r = cexpr(c, n->rhs); if (l != ty_err && r != ty_err && !type_assignable(l, r)) err(c, n->pos, "cannot assign %s to %s", type_name(c->a, r), type_name(c->a, l)); return n->type = l; } case N_STRUCTLIT: { /* lhs may be an N_IDENT (the bare type name) or a real type * expression. Resolve via name lookup first; fall back to * resolve_type for the synthetic-type-expr case. */ Type *t = NULL; if (n->lhs && n->lhs->kind == N_IDENT) { Sym *s = scope_lookup(c->cur, n->lhs->str); if (s == NULL || s->kind != SK_TYPE) t = err(c, n->pos, "unknown struct type '%s'", n->lhs->str); else t = s->type; } else { t = resolve_type(c, n->lhs); } Type *u = (t && t->kind == TY_NAMED) ? t->under : t; for (Node *f = n->list; f; f = f->next) { Type *vt = cexpr(c, f->lhs); if (u && u->kind == TY_STRUCT) { Tfield *match = NULL; for (Tfield *fl = u->fields; fl; fl = fl->next) if (strcmp(fl->name, f->str) == 0) { match = fl; break; } if (match == NULL) err(c, f->pos, "no field '%s' in %s", f->str, type_name(c->a, t)); else if (vt != ty_err && !type_assignable(match->type, vt)) err(c, f->pos, "field %s: %s not assignable to %s", f->str, type_name(c->a, vt), type_name(c->a, match->type)); } } return n->type = t; } case N_ARRLIT: { Type *elt = NULL; u64 count = 0; for (Node *e = n->list; e; e = e->next) { if (e->kind == N_FIELD && e->str && strcmp(e->str, "...") == 0) continue; Type *t = cexpr(c, e); if (elt == NULL) elt = type_default(t); count++; } if (elt == NULL) elt = ty_i32; return n->type = type_array(c->a, elt, count); } case N_SPREAD: return n->type = cexpr(c, n->lhs); case N_SLICE: { Type *base = cexpr(c, n->lhs); if (n->rhs) (void)cexpr(c, n->rhs); if (n->cond) (void)cexpr(c, n->cond); Type *u = (base && base->kind == TY_NAMED) ? base->under : base; if (u && u->kind == TY_ARRAY) return n->type = type_slice(c->a, u->sub); if (u && u->kind == TY_SLICE) return n->type = base; if (u && u->kind == TY_STR) return n->type = ty_str; if (u && u->kind == TY_PTR && u->sub) return n->type = type_slice(c->a, u->sub); return n->type = err(c, n->pos, "cannot slice %s", type_name(c->a, base)); } case N_RECV: { Type *t = cexpr(c, n->lhs); Type *u = (t && t->kind == TY_NAMED) ? t->under : t; if (u && u->kind == TY_CHAN) return n->type = u->sub; return n->type = err(c, n->pos, "<- expects chan, got %s", type_name(c->a, t)); } case N_MATCH: { Type *st = cexpr(c, n->lhs); Type *u = (st && st->kind == TY_NAMED) ? st->under : st; if (u == NULL || u->kind != TY_TAGGED) { return n->type = err(c, n->pos, "match on non-tagged-union %s", type_name(c->a, st)); } for (Node *cs = n->list; cs; cs = cs->next) { Scope *saved = c->cur; c->cur = newscope(c->a, saved); /* Resolve the case pattern's type so codegen can map it * to the variant tag. Both `case T =>` and `case let v: T * =>` get this — `case =>` (default) leaves cs->type NULL. * For multi-pattern `case T1 | T2 =>` each alternative in * cs->list also gets its type resolved in place. */ if (cs->lhs) { Type *vt = resolve_type(c, cs->lhs); cs->type = vt; for (Node *alt = cs->list; alt; alt = alt->next) alt->type = resolve_type(c, alt); if (cs->str && cs->str[0]) scope_define(c->cur, cs->str, SK_VAR, vt, cs); } cstmt(c, cs->body); c->cur = saved; } n->type = ty_void; return n->type; } case N_TRYPROP: case N_TRYUNW: { Type *t = cexpr(c, n->lhs); Type *u = (t && t->kind == TY_NAMED) ? t->under : t; if (u == NULL || u->kind != TY_TAGGED) { return n->type = err(c, n->pos, "%s on non-tagged-union %s", n->kind == N_TRYPROP ? "?" : "!", type_name(c->a, t)); } /* Convention: first variant is the success type. */ Tparam *first = u->params; return n->type = first ? first->type : ty_err; } case N_TUPLE: { /* keep untyped element types; assignability is checked * element-wise at the consumer (return / mlet / massign). */ Type *t = newtype(c->a, TY_TUPLE); Tparam *head = NULL, *tail = NULL; for (Node *e = n->list; e; e = e->next) { Tparam *tp = amalloc(c->a, sizeof *tp); tp->type = cexpr(c, e); if (head == NULL) head = tp; else tail->next = tp; tail = tp; } t->params = head; return n->type = t; } default: return n->type = err(c, n->pos, "internal: unhandled expr kind %d", n->kind); } } /* ---- statements --------------------------------------------------- */ static void clet(Checker *c, Node *n) { Type *declared = n->lhs ? resolve_type(c, n->lhs) : NULL; Type *initt = NULL; if (n->rhs) initt = cexpr(c, n->rhs); Type *t = declared; if (t == NULL && initt) t = type_default(initt); if (t == NULL) { err(c, n->pos, "let needs a type or initialiser"); t = ty_err; } if (declared && initt && initt != ty_err && !type_assignable(declared, initt)) err(c, n->pos, "init %s not assignable to declared %s", type_name(c->a, initt), type_name(c->a, declared)); n->type = t; if (n->str && n->str[0]) scope_define(c->cur, n->str, SK_VAR, t, n); } static void cstmt(Checker *c, Node *n) { if (n == NULL) return; switch (n->kind) { case N_BLOCK: { Scope *saved = c->cur; c->cur = newscope(c->a, saved); for (Node *s = n->list; s; s = s->next) cstmt(c, s); c->cur = saved; break; } case N_EXPRSTMT: (void)cexpr(c, n->lhs); break; case N_LET: clet(c, n); break; case N_RETURN: { Type *rt = n->lhs ? cexpr(c, n->lhs) : ty_void; if (c->ret == NULL) { err(c, n->pos, "return outside function"); break; } if (c->ret == ty_void && n->lhs) err(c, n->pos, "return value in void function"); else if (c->ret != ty_void && rt != ty_err && c->ret != ty_err && !type_assignable(c->ret, rt)) err(c, n->pos, "return %s not assignable to %s", type_name(c->a, rt), type_name(c->a, c->ret)); break; } case N_IF: { Type *ct = cexpr(c, n->cond); if (ct != ty_err && ct != ty_bool && ct != ty_untyped_bool) err(c, n->pos, "if condition must be bool, got %s", type_name(c->a, ct)); cstmt(c, n->body); cstmt(c, n->els); break; } case N_FORRANGE: { Scope *saved = c->cur; c->cur = newscope(c->a, saved); c->loops++; Type *st = cexpr(c, n->lhs); Type *u = (st && st->kind == TY_NAMED) ? st->under : st; Type *elem = NULL; if (u && (u->kind == TY_SLICE || u->kind == TY_ARRAY)) elem = u->sub; else if (u && u->kind == TY_STR) elem = ty_u8; else err(c, n->pos, "for-range needs slice/array/str"); if (n->list != NULL) { /* tuple destructure: each name binds to a tuple field */ Type *etu = (elem && elem->kind == TY_NAMED) ? elem->under : elem; Tparam *tp = (etu && etu->kind == TY_TUPLE) ? etu->params : NULL; for (Node *nm = n->list; nm; nm = nm->next) { Type *ft = tp ? tp->type : ty_err; if (nm->str && nm->str[0]) scope_define(c->cur, nm->str, SK_VAR, ft, nm); if (tp) tp = tp->next; } } else if (n->str && n->str[0]) { scope_define(c->cur, n->str, SK_VAR, elem ? elem : ty_err, n); } cstmt(c, n->body); c->loops--; c->cur = saved; break; } case N_FOR: { Scope *saved = c->cur; c->cur = newscope(c->a, saved); c->loops++; if (n->lhs) cstmt(c, n->lhs); /* init may be a let or expr */ if (n->cond) { Type *ct = cexpr(c, n->cond); if (ct != ty_err && ct != ty_bool && ct != ty_untyped_bool) err(c, n->pos, "for condition must be bool, got %s", type_name(c->a, ct)); } if (n->rhs) (void)cexpr(c, n->rhs); cstmt(c, n->body); c->loops--; c->cur = saved; break; } case N_MLET: { Type *rt = cexpr(c, n->rhs); Type *u = (rt && rt->kind == TY_TUPLE) ? rt : NULL; if (u == NULL) { err(c, n->pos, "multi-let rhs is not a tuple (got %s)", type_name(c->a, rt)); } Tparam *tp = u ? u->params : NULL; for (Node *l = n->list; l; l = l->next) { Type *declared = l->lhs ? resolve_type(c, l->lhs) : NULL; Type *elem = tp ? tp->type : NULL; Type *t = declared ? declared : (elem ? type_default(elem) : ty_err); if (declared && elem && !type_assignable(declared, elem)) err(c, l->pos, "let %s: %s not assignable from %s", l->str, type_name(c->a, elem), type_name(c->a, declared)); l->type = t; if (l->str && l->str[0]) scope_define(c->cur, l->str, SK_VAR, t, l); if (tp) tp = tp->next; } if (u && tp != NULL) err(c, n->pos, "tuple has extra elements"); break; } case N_MASSIGN: { Type *rt = cexpr(c, n->rhs); Type *u = (rt && rt->kind == TY_TUPLE) ? rt : NULL; if (u == NULL) { err(c, n->pos, "multi-assign rhs is not a tuple (got %s)", type_name(c->a, rt)); } Tparam *tp = u ? u->params : NULL; for (Node *lv = n->list; lv; lv = lv->next) { Type *lt = cexpr(c, lv); Type *elem = tp ? tp->type : NULL; if (lt && elem && !type_assignable(lt, elem)) err(c, lv->pos, "cannot assign %s to %s", type_name(c->a, elem), type_name(c->a, lt)); if (tp) tp = tp->next; } break; } case N_DEFER: (void)cexpr(c, n->lhs); break; case N_BREAK: case N_CONTINUE: if (c->loops == 0) err(c, n->pos, "%s outside loop", n->kind == N_BREAK ? "break" : "continue"); break; case N_SWITCH: { Type *st = cexpr(c, n->lhs); (void)st; for (Node *cs = n->list; cs; cs = cs->next) { for (Node *e = cs->list; e; e = e->next) (void)cexpr(c, e); cstmt(c, cs->body); } break; } default: err(c, n->pos, "internal: unhandled stmt kind %d", n->kind); } } /* ---- top-level ---------------------------------------------------- */ static Type * build_fn_type(Checker *c, Node *fn) { Type *t = newtype(c->a, TY_FN); t->size = 8; t->align = 8; t->ret = fn->lhs ? resolve_type(c, fn->lhs) : ty_void; Tparam *head = NULL, *tail = NULL; for (Node *p = fn->list; p; p = p->next) { if (p->str && strcmp(p->str, "...") == 0) { t->variadic = 1; continue; } Tparam *tp = amalloc(c->a, sizeof *tp); tp->name = p->str; tp->type = resolve_type(c, p->lhs); if (head == NULL) head = tp; else tail->next = tp; tail = tp; } t->params = head; return t; } void check_init(Checker *c, Arena *a) { memset(c, 0, sizeof *c); c->a = a; typesinit(a); c->top = newscope(a, NULL); c->cur = c->top; } void check_file(Checker *c, Node *file) { if (file == NULL || file->kind != N_FILE) return; /* pass 1: install names (types first, then defs/fns). * For self-referential types we install the named-type placeholder * BEFORE resolving its body; the body may legitimately mention * the type itself (`type stream = struct { read: fn(*stream)... }`). */ for (Node *d = file->list; d; d = d->next) { if (d->kind != N_TYPEDECL) continue; Type *named = type_named(c->a, d->str, NULL); if (!scope_define(c->cur, d->str, SK_TYPE, named, d)) err(c, d->pos, "duplicate type %s", d->str); d->type = named; } for (Node *d = file->list; d; d = d->next) { if (d->kind != N_TYPEDECL) continue; Type *under = resolve_type(c, d->lhs); d->type->under = under; if (under) { d->type->size = under->size; d->type->align = under->align; } } for (Node *d = file->list; d; d = d->next) { switch (d->kind) { case N_USE: scope_define(c->cur, d->str, SK_USE, NULL, d); break; case N_DEF: { Type *t = resolve_type(c, d->lhs); d->type = t; if (!scope_define(c->cur, d->str, SK_DEF, t, d)) err(c, d->pos, "duplicate def %s", d->str); break; } case N_FNDECL: { Type *t = build_fn_type(c, d); d->type = t; if (!scope_define(c->cur, d->str, SK_FN, t, d)) err(c, d->pos, "duplicate fn %s", d->str); break; } case N_LET: { Type *t = d->lhs ? resolve_type(c, d->lhs) : NULL; d->type = t; if (d->str && d->str[0]) scope_define(c->cur, d->str, SK_VAR, t, d); break; } default: break; } } /* pass 2: check def initialisers and fn bodies */ for (Node *d = file->list; d; d = d->next) { switch (d->kind) { case N_DEF: { if (d->rhs) { Type *rt = cexpr(c, d->rhs); if (d->type && rt != ty_err && d->type != ty_err && !type_assignable(d->type, rt)) err(c, d->pos, "def %s init %s not assignable to %s", d->str, type_name(c->a, rt), type_name(c->a, d->type)); } break; } case N_FNDECL: { if (d->body == NULL) break; /* extern decl */ Scope *saved = c->cur; c->cur = newscope(c->a, saved); Type *fnt = d->type; for (Tparam *p = fnt->params; p; p = p->next) { if (p->name && p->name[0]) scope_define(c->cur, p->name, SK_PARAM, p->type, d); } Type *prev = c->ret; c->ret = fnt->ret; cstmt(c, d->body); c->ret = prev; c->cur = saved; break; } case N_LET: { if (d->rhs) { Type *rt = cexpr(c, d->rhs); if (d->type == NULL) d->type = type_default(rt); if (d->type && rt != ty_err && d->type != ty_err && !type_assignable(d->type, rt)) err(c, d->pos, "let %s init not assignable", d->str); } break; } default: break; } } }