`type tkind = enum i32 { TK_NONE = 0, TK_EOF = 1, ... TK_LAST = 86 }`
replaces the 87-line `def TK_*: i32 = N` cluster in lib/ww/lex/tok.ww.
Numeric values explicit so 990_selfhost's byte-diff against the C-side
`Tkind` enum still passes.
All ~270 reference sites in lib/ww and selfhost/cmd/{wcc,wwdump}
sed-renamed `TK_X` → `tkind.TK_X`. Struct fields (`tok.kind`,
`parser.curkind`) intentionally kept as `i32` — making them `tkind`
shifted some byte-positions in the cgen output and broke 990/993/995
byte-identity probes without an obvious win.
To make the rename non-cascading on every signature, type_assignable
and unify_arith in cmd/wcc/check+type relax to allow enum ↔ int
mixing when storage matches (a `tkind` value flows into an `i32`
slot and vice versa, no explicit cast). This deviates from Hare's
strict enum semantics; doc'd as an explicit pragmatic relaxation
for the compiler's internal enum-shaped kinds. External user code
can still get the type-safety benefit if they declare their
parameters with the enum type.
combined.ww files regenerated by ww build.
1661 lines
52 KiB
C
1661 lines
52 KiB
C
/*
|
|
* 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 <string.h>
|
|
|
|
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;
|
|
if (strcmp(name, "never") == 0) return ty_never;
|
|
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;
|
|
}
|
|
|
|
/* Variant identity for tagged unions. Mirrors cg_variant_match in
|
|
* cgen: NAMED types are nominal (pointer-identical) and don't unify
|
|
* with their underlying; everything else is structural type_eq. */
|
|
static int
|
|
variant_match(Type *a, Type *b)
|
|
{
|
|
if (a == NULL || b == NULL) return 0;
|
|
if (a->kind == TY_NAMED && b->kind == TY_NAMED) return a == b;
|
|
if (a->kind == TY_NAMED || b->kind == TY_NAMED) return 0;
|
|
return type_eq(a, b);
|
|
}
|
|
|
|
static int
|
|
variant_present(Tparam *head, Type *vt)
|
|
{
|
|
for (Tparam *p = head; p; p = p->next)
|
|
if (variant_match(p->type, vt)) return 1;
|
|
return 0;
|
|
}
|
|
|
|
/* match_yield_type — walk a match arm's body looking for the type
|
|
* of its first `yield expr;` statement. Returns NULL if no yield
|
|
* was found. Doesn't descend into nested match bodies — each match
|
|
* is its own yield scope. */
|
|
static Type *
|
|
match_yield_type(Node *body)
|
|
{
|
|
if (body == NULL) return NULL;
|
|
if (body->kind == N_YIELD) return body->lhs ? body->lhs->type : NULL;
|
|
if (body->kind == N_MATCH) return NULL; /* inner match: own scope */
|
|
if (body->kind == N_BLOCK) {
|
|
for (Node *s = body->list; s; s = s->next) {
|
|
Type *t = match_yield_type(s);
|
|
if (t) return t;
|
|
}
|
|
return NULL;
|
|
}
|
|
if (body->kind == N_IF) {
|
|
Type *t = match_yield_type(body->body);
|
|
if (t) return t;
|
|
return match_yield_type(body->els);
|
|
}
|
|
if (body->kind == N_FOR || body->kind == N_FORRANGE)
|
|
return match_yield_type(body->body);
|
|
return NULL;
|
|
}
|
|
|
|
/* tagged_has_errflag — true iff any variant is `!`-marked. Determines
|
|
* whether the union uses Hare's explicit error subset or the legacy
|
|
* "first variant = success" convention. */
|
|
static int
|
|
tagged_has_errflag(Type *u)
|
|
{
|
|
if (u == NULL || u->kind != TY_TAGGED) return 0;
|
|
for (Tparam *p = u->params; p; p = p->next)
|
|
if (p->type && p->type->iserror) return 1;
|
|
return 0;
|
|
}
|
|
|
|
/* tagged_is_error_variant — does `v` (a variant of `u`) belong to
|
|
* the error subset? Explicit-flag mode: only variants with iserror=1.
|
|
* Legacy mode (no flags): everything except the first variant. */
|
|
static int
|
|
tagged_is_error_variant(Type *u, Type *v)
|
|
{
|
|
if (u == NULL || u->kind != TY_TAGGED || v == NULL) return 0;
|
|
if (tagged_has_errflag(u)) return v->iserror != 0;
|
|
/* legacy: first variant is success, rest are errors */
|
|
return u->params && u->params->type != v;
|
|
}
|
|
|
|
/* tagged_success_type — the success variant's type. Explicit-flag
|
|
* mode: the first non-flagged variant. Legacy: the first variant. */
|
|
static Type *
|
|
tagged_success_type(Type *u)
|
|
{
|
|
if (u == NULL || u->kind != TY_TAGGED) return NULL;
|
|
if (tagged_has_errflag(u)) {
|
|
for (Tparam *p = u->params; p; p = p->next)
|
|
if (p->type && !p->type->iserror) return p->type;
|
|
return NULL;
|
|
}
|
|
return u->params ? u->params->type : NULL;
|
|
}
|
|
|
|
/* eval_enum_value — fold an enum member-value expression to a u64
|
|
* constant. Sees prior siblings via the `prev` Tfield list (each
|
|
* carries the member's name and resolved value in .offset). Returns
|
|
* 1 on success; on failure emits the error and returns 0. The op set
|
|
* is the constant subset typical of Hare-style flag enums:
|
|
* literal, sibling-ident, + - * / % & | ^ << >>, unary - and ~. */
|
|
static int
|
|
eval_enum_value(Checker *c, Node *n, Tfield *prev, u64 *out)
|
|
{
|
|
if (n == NULL) return 0;
|
|
switch (n->kind) {
|
|
case N_INTLIT:
|
|
case N_RUNELIT:
|
|
*out = n->uval;
|
|
return 1;
|
|
case N_TRUE: *out = 1; return 1;
|
|
case N_FALSE: *out = 0; return 1;
|
|
case N_IDENT: {
|
|
for (Tfield *f = prev; f; f = f->next) {
|
|
if (f->name && n->str &&
|
|
strcmp(f->name, n->str) == 0) {
|
|
*out = f->offset;
|
|
return 1;
|
|
}
|
|
}
|
|
err(c, n->pos, "enum value: unknown identifier '%s'",
|
|
n->str ? n->str : "?");
|
|
return 0;
|
|
}
|
|
case N_BIN: {
|
|
u64 a, b;
|
|
if (!eval_enum_value(c, n->lhs, prev, &a) ||
|
|
!eval_enum_value(c, n->rhs, prev, &b))
|
|
return 0;
|
|
switch (n->op) {
|
|
case TK_PLUS: *out = a + b; return 1;
|
|
case TK_MINUS: *out = a - b; return 1;
|
|
case TK_STAR: *out = a * b; return 1;
|
|
case TK_SLASH:
|
|
if (b == 0) goto divzero;
|
|
*out = a / b; return 1;
|
|
case TK_PERCENT:
|
|
if (b == 0) goto divzero;
|
|
*out = a % b; return 1;
|
|
case TK_AMP: *out = a & b; return 1;
|
|
case TK_PIPE: *out = a | b; return 1;
|
|
case TK_CARET: *out = a ^ b; return 1;
|
|
case TK_LSHIFT: *out = a << b; return 1;
|
|
case TK_RSHIFT: *out = a >> b; return 1;
|
|
default:
|
|
err(c, n->pos, "enum value: unsupported binary op %s",
|
|
tokname(n->op));
|
|
return 0;
|
|
}
|
|
divzero:
|
|
err(c, n->pos, "enum value: division by zero");
|
|
return 0;
|
|
}
|
|
case N_UN: {
|
|
u64 v;
|
|
if (!eval_enum_value(c, n->lhs, prev, &v))
|
|
return 0;
|
|
switch (n->op) {
|
|
case TK_MINUS: *out = (u64)(-(i64)v); return 1;
|
|
case TK_TILDE: *out = ~v; return 1;
|
|
case TK_PLUS: *out = v; return 1;
|
|
default:
|
|
err(c, n->pos, "enum value: unsupported unary op %s",
|
|
tokname(n->op));
|
|
return 0;
|
|
}
|
|
}
|
|
default:
|
|
err(c, n->pos,
|
|
"enum value must be a constant integer expression");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static Type *
|
|
resolve_type(Checker *c, Node *n)
|
|
{
|
|
if (n == NULL) return ty_void;
|
|
switch (n->kind) {
|
|
case N_TBANG: {
|
|
/* `!T` — mark the resolved type as an error type. Wrap
|
|
* primitives in a fresh NAMED-less copy so we don't taint
|
|
* the shared ty_void / ty_str / ty_i32 globals. NAMED
|
|
* types are already unique per alias decl, so we can flip
|
|
* the bit in place. */
|
|
Type *t = resolve_type(c, n->lhs);
|
|
if (t == NULL || t == ty_err) return t;
|
|
if (t->kind == TY_NAMED) {
|
|
t->iserror = 1;
|
|
return t;
|
|
}
|
|
Type *t2 = newtype(c->a, t->kind);
|
|
*t2 = *t;
|
|
t2->iserror = 1;
|
|
return t2;
|
|
}
|
|
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 == NULL) {
|
|
/* `[_]T` — length inferred at the use site (currently
|
|
* only `let x: [_]T = arrlit;`). Leave alen=0 as a
|
|
* sentinel; clet patches it from the initialiser. */
|
|
} else if (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-set normalization (Hare-style):
|
|
* - Flatten nested anonymous (A | B) | C → (A | B | C). Named
|
|
* aliases over tagged unions stay nominal — not flattened.
|
|
* - Drop `never`: bottom contributes no values.
|
|
* - Dedup variants. Equality follows cg_variant_match: NAMED
|
|
* types compare by pointer-identity, others structurally.
|
|
* - If exactly one variant remains, the tagged union collapses
|
|
* to that variant. (i32 | never) → i32.
|
|
* - If zero remain (all variants were `never`), the type is
|
|
* `never` itself. */
|
|
Type *t = newtype(c->a, TY_TAGGED);
|
|
Tparam *head = NULL, *tail = NULL;
|
|
u64 maxsz = 0, al = 8;
|
|
int nv = 0;
|
|
for (Node *e = n->list; e; e = e->next) {
|
|
Type *vt = resolve_type(c, e);
|
|
if (vt == ty_never) continue;
|
|
if (vt && vt->kind == TY_TAGGED) {
|
|
/* flatten anonymous nested tagged */
|
|
for (Tparam *src = vt->params; src; src = src->next) {
|
|
Type *st = src->type;
|
|
if (st == ty_never) continue;
|
|
if (variant_present(head, st)) continue;
|
|
Tparam *tp = amalloc(c->a, sizeof *tp);
|
|
tp->type = st;
|
|
if (st && st->size > maxsz) maxsz = st->size;
|
|
if (st && st->align > al) al = st->align;
|
|
if (head == NULL) head = tp;
|
|
else tail->next = tp;
|
|
tail = tp;
|
|
nv++;
|
|
}
|
|
continue;
|
|
}
|
|
if (variant_present(head, vt)) continue;
|
|
Tparam *tp = amalloc(c->a, sizeof *tp);
|
|
tp->type = vt;
|
|
if (vt && vt->size > maxsz) maxsz = vt->size;
|
|
if (vt && vt->align > al) al = vt->align;
|
|
if (head == NULL) head = tp;
|
|
else tail->next = tp;
|
|
tail = tp;
|
|
nv++;
|
|
}
|
|
if (nv == 0) return ty_never;
|
|
if (nv == 1 && head) return head->type;
|
|
t->params = head;
|
|
/* Nullable pointer folding: `(*T | void)` collapses to a
|
|
* single 8-byte pointer slot; null bit pattern is the void
|
|
* variant. Mirrors Hare's `(*T | null)`. Detected on exact
|
|
* two-variant shape with one TY_PTR and one TY_VOID. */
|
|
if (nv == 2) {
|
|
Tparam *a = head;
|
|
Tparam *b = head->next;
|
|
Type *au = (a->type && a->type->kind == TY_NAMED)
|
|
? a->type->under : a->type;
|
|
Type *bu = (b->type && b->type->kind == TY_NAMED)
|
|
? b->type->under : b->type;
|
|
int aptr = au && au->kind == TY_PTR;
|
|
int bptr = bu && bu->kind == TY_PTR;
|
|
int avoid = au && au->kind == TY_VOID;
|
|
int bvoid = bu && bu->kind == TY_VOID;
|
|
if ((aptr && bvoid) || (avoid && bptr)) {
|
|
t->nullable = 1;
|
|
t->size = 8;
|
|
t->align = 8;
|
|
return t;
|
|
}
|
|
}
|
|
/* Round value payload up to an 8-byte multiple so the slot
|
|
* layout (tag + N value words) stays word-aligned. The reg-
|
|
* passing ABI counts size/8 words; 12-byte unions like
|
|
* (i32 | void) would otherwise lose a value register. */
|
|
u64 vsz = (maxsz + 7) & ~(u64)7;
|
|
t->size = 8 + vsz;
|
|
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) {
|
|
Type *ft = resolve_type(c, f->lhs);
|
|
if (ft->align > maxalign) maxalign = ft->align;
|
|
off = (off + ft->align - 1) & ~(ft->align - 1);
|
|
if (f->str != NULL) {
|
|
/* regular named field */
|
|
for (Tfield *e = head; e; e = e->next)
|
|
if (e->name && strcmp(e->name, f->str) == 0) {
|
|
err(c, f->pos, "duplicate field '%s'",
|
|
f->str);
|
|
break;
|
|
}
|
|
Tfield *tf = amalloc(c->a, sizeof *tf);
|
|
tf->name = f->str;
|
|
tf->type = ft;
|
|
tf->offset = off;
|
|
off += ft->size;
|
|
if (head == NULL) head = tf;
|
|
else tail->next = tf;
|
|
tail = tf;
|
|
continue;
|
|
}
|
|
/* embed (anonymous struct or bare-name): the inner type
|
|
* must be a struct; its fields are promoted to the outer
|
|
* scope with offsets shifted by the embed base. */
|
|
Type *inner = (ft && ft->kind == TY_NAMED) ? ft->under : ft;
|
|
if (inner == NULL || inner->kind != TY_STRUCT) {
|
|
err(c, f->pos, "embedded type must be a struct");
|
|
off += ft ? ft->size : 0;
|
|
continue;
|
|
}
|
|
u64 base = off;
|
|
for (Tfield *src = inner->fields; src; src = src->next) {
|
|
for (Tfield *e = head; e; e = e->next)
|
|
if (e->name && src->name &&
|
|
strcmp(e->name, src->name) == 0) {
|
|
err(c, f->pos,
|
|
"embedded field '%s' "
|
|
"collides with existing field",
|
|
src->name);
|
|
break;
|
|
}
|
|
Tfield *tf = amalloc(c->a, sizeof *tf);
|
|
tf->name = src->name;
|
|
tf->type = src->type;
|
|
tf->offset = base + src->offset;
|
|
if (head == NULL) head = tf;
|
|
else tail->next = tf;
|
|
tail = tf;
|
|
}
|
|
off = base + inner->size;
|
|
}
|
|
t->fields = head;
|
|
t->align = maxalign;
|
|
t->size = (off + maxalign - 1) & ~(maxalign - 1);
|
|
return t;
|
|
}
|
|
case N_TENUM: {
|
|
Type *t = newtype(c->a, TY_ENUM);
|
|
Type *storage = ty_i32; /* default storage */
|
|
if (n->lhs) {
|
|
Type *s = resolve_type(c, n->lhs);
|
|
if (s == ty_err || !type_isint(s))
|
|
err(c, n->lhs->pos,
|
|
"enum storage type must be integer");
|
|
else
|
|
storage = s;
|
|
}
|
|
t->sub = storage;
|
|
t->size = storage->size;
|
|
t->align = storage->align;
|
|
Tfield *head = NULL, *tail = NULL;
|
|
u64 prev = (u64)-1; /* so first omitted → 0 */
|
|
for (Node *m = n->list; m; m = m->next) {
|
|
u64 val;
|
|
if (m->lhs == NULL) {
|
|
val = prev + 1;
|
|
} else if (!eval_enum_value(c, m->lhs, head, &val)) {
|
|
val = prev + 1;
|
|
}
|
|
prev = val;
|
|
for (Tfield *e = head; e; e = e->next) {
|
|
if (e->name && m->str &&
|
|
strcmp(e->name, m->str) == 0) {
|
|
err(c, m->pos,
|
|
"duplicate enum member '%s'",
|
|
m->str);
|
|
break;
|
|
}
|
|
}
|
|
Tfield *tf = amalloc(c->a, sizeof *tf);
|
|
tf->name = m->str;
|
|
tf->type = NULL;
|
|
tf->offset = val;
|
|
if (head == NULL) head = tf;
|
|
else tail->next = tf;
|
|
tail = tf;
|
|
}
|
|
t->fields = head;
|
|
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;
|
|
/* Enum ↔ integer storage: pair `(tkind, i32)` operands unify to
|
|
* the storage int. Mirrors the same relaxation in type_assignable;
|
|
* lets `cur.kind == TK_FN` typecheck without a cast on either side. */
|
|
{
|
|
Type *au = (a->kind == TY_NAMED) ? a->under : a;
|
|
Type *bu = (b->kind == TY_NAMED) ? b->under : b;
|
|
if (au && au->kind == TY_ENUM && type_isint(b) &&
|
|
type_eq(au->sub, b)) return b;
|
|
if (bu && bu->kind == TY_ENUM && type_isint(a) &&
|
|
type_eq(a, bu->sub)) 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_VOIDLIT: n->type = ty_void; return n->type;
|
|
case N_IDENT: {
|
|
if (n->str && n->str[0] == '\0')
|
|
return n->type = err(c, n->pos,
|
|
"`_` is only valid as a binding or discard lvalue");
|
|
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;
|
|
}
|
|
/* enum member access: TypeName.MEMBER → fold to
|
|
* the member's integer literal value. Type is the
|
|
* (named) enum type itself, so bitwise ops between
|
|
* members yield the same enum type via type_eq. */
|
|
if (ms && ms->kind == SK_TYPE && ms->type) {
|
|
Type *u = (ms->type->kind == TY_NAMED)
|
|
? ms->type->under : ms->type;
|
|
if (u && u->kind == TY_ENUM) {
|
|
for (Tfield *f = u->fields; f; f = f->next) {
|
|
if (f->name && n->str &&
|
|
strcmp(f->name, n->str) == 0) {
|
|
n->kind = N_INTLIT;
|
|
n->uval = f->offset;
|
|
n->str = aprintf(c->a, "%llu",
|
|
(unsigned long long)f->offset);
|
|
n->strlen = strlen(n->str);
|
|
n->lhs = NULL;
|
|
n->rhs = NULL;
|
|
n->tsuffix = NULL;
|
|
return n->type = ms->type;
|
|
}
|
|
}
|
|
return n->type = err(c, n->pos,
|
|
"no enum member '%s' in %s",
|
|
n->str ? n->str : "?",
|
|
ms->name);
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
/* Enum member access via a qualified base, e.g. `os.whence.CUR`.
|
|
* The inner N_DOT resolved through SK_USE → the SK_TYPE sym's
|
|
* named type. Fold the outer access to the member literal. */
|
|
if (u && u->kind == TY_ENUM) {
|
|
for (Tfield *f = u->fields; f; f = f->next) {
|
|
if (f->name && n->str &&
|
|
strcmp(f->name, n->str) == 0) {
|
|
n->kind = N_INTLIT;
|
|
n->uval = f->offset;
|
|
n->str = aprintf(c->a, "%llu",
|
|
(unsigned long long)f->offset);
|
|
n->strlen = strlen(n->str);
|
|
n->lhs = NULL;
|
|
n->rhs = NULL;
|
|
n->tsuffix = NULL;
|
|
return n->type = base;
|
|
}
|
|
}
|
|
return n->type = err(c, n->pos,
|
|
"no enum member '%s' in %s",
|
|
n->str ? n->str : "?",
|
|
type_name(c->a, base));
|
|
}
|
|
/* 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;
|
|
}
|
|
/* size(T) / align(T): fold to an integer literal. The arg is
|
|
* a type-expr node (planted by the parser, not a regular
|
|
* expression). */
|
|
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
|
|
(strcmp(n->lhs->str, "size") == 0 ||
|
|
strcmp(n->lhs->str, "align") == 0) &&
|
|
n->list != NULL) {
|
|
int is_size = strcmp(n->lhs->str, "size") == 0;
|
|
Type *t = resolve_type(c, n->list);
|
|
u64 v = 0;
|
|
if (t && t != ty_err) v = is_size ? t->size : t->align;
|
|
n->kind = N_INTLIT;
|
|
n->uval = v;
|
|
n->str = aprintf(c->a, "%llu", (unsigned long long)v);
|
|
n->strlen = strlen(n->str);
|
|
n->lhs = NULL;
|
|
n->list = NULL;
|
|
n->tsuffix = NULL;
|
|
n->type = ty_untyped_int;
|
|
return n->type;
|
|
}
|
|
/* offset(e.f): the byte offset of `f` inside the struct type of
|
|
* `e`. Folded to an integer literal at check-time. */
|
|
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
|
|
strcmp(n->lhs->str, "offset") == 0 &&
|
|
n->list != NULL && n->list->next == NULL &&
|
|
n->list->kind == N_DOT) {
|
|
Node *dot = n->list;
|
|
Type *bt = cexpr(c, dot->lhs);
|
|
Type *u = (bt && bt->kind == TY_NAMED) ? bt->under : bt;
|
|
if (u && u->kind == TY_PTR) u = u->sub;
|
|
if (u && u->kind == TY_NAMED) u = u->under;
|
|
u64 off = 0;
|
|
int found = 0;
|
|
if (u && u->kind == TY_STRUCT) {
|
|
for (Tfield *f = u->fields; f; f = f->next)
|
|
if (strcmp(f->name, dot->str) == 0) {
|
|
off = f->offset; found = 1; break;
|
|
}
|
|
}
|
|
if (!found)
|
|
err(c, n->pos, "offset: no field '%s'",
|
|
dot->str ? dot->str : "?");
|
|
n->kind = N_INTLIT;
|
|
n->uval = off;
|
|
n->str = aprintf(c->a, "%llu", (unsigned long long)off);
|
|
n->strlen = strlen(n->str);
|
|
n->lhs = NULL;
|
|
n->list = NULL;
|
|
n->tsuffix = NULL;
|
|
n->type = ty_untyped_int;
|
|
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;
|
|
}
|
|
/* assert(cond[, msg]) / abort([msg]) — runtime checks that
|
|
* call into rt_abort. msg must be a str when present.
|
|
* Only treated as builtins when no user symbol shadows the
|
|
* name; existing code that declares its own `abort`/`assert`
|
|
* (e.g. lib/os/os.ww) keeps working unchanged. */
|
|
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
|
|
strcmp(n->lhs->str, "abort") == 0 &&
|
|
scope_lookup(c->cur, "abort") == NULL) {
|
|
if (n->list) {
|
|
Type *mt = cexpr(c, n->list);
|
|
if (mt != ty_err && !type_assignable(ty_str, mt))
|
|
err(c, n->pos, "abort: message must be str");
|
|
if (n->list->next)
|
|
err(c, n->pos, "abort: at most one arg");
|
|
}
|
|
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, "assert") == 0 &&
|
|
n->list != NULL &&
|
|
scope_lookup(c->cur, "assert") == NULL) {
|
|
Type *ct = cexpr(c, n->list);
|
|
if (ct != ty_err && ct != ty_bool && ct != ty_untyped_bool)
|
|
err(c, n->pos, "assert: cond must be bool");
|
|
if (n->list->next) {
|
|
Type *mt = cexpr(c, n->list->next);
|
|
if (mt != ty_err && !type_assignable(ty_str, mt))
|
|
err(c, n->pos, "assert: message must be str");
|
|
if (n->list->next->next)
|
|
err(c, n->pos, "assert: at most two args");
|
|
}
|
|
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: {
|
|
/* `_` lvalue: discard the rhs. */
|
|
if (n->lhs && n->lhs->kind == N_IDENT &&
|
|
n->lhs->str && n->lhs->str[0] == '\0') {
|
|
(void)cexpr(c, n->rhs);
|
|
return n->type = ty_void;
|
|
}
|
|
/* Reject assignment to a const-bound name. */
|
|
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str) {
|
|
Sym *s = scope_lookup(c->cur, n->lhs->str);
|
|
if (s && s->is_const)
|
|
err(c, n->pos, "cannot assign to const `%s`",
|
|
n->lhs->str);
|
|
}
|
|
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));
|
|
}
|
|
int has_default = 0;
|
|
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 == NULL) {
|
|
has_default = 1;
|
|
} else {
|
|
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);
|
|
/* Validity: every `case T =>` pattern must
|
|
* refer to a variant of the scrutinee's
|
|
* tagged union. Mirrors the existing is/as
|
|
* check; `match (u) { case f64 => ... }`
|
|
* where f64 isn't a variant of u is dead code
|
|
* the dispatch never reaches, so refuse it. */
|
|
if (vt && vt != ty_err &&
|
|
!variant_present(u->params, vt))
|
|
err(c, cs->pos,
|
|
"case: %s is not a variant of %s",
|
|
type_name(c->a, vt),
|
|
type_name(c->a, st));
|
|
for (Node *alt = cs->list; alt; alt = alt->next) {
|
|
if (alt->type == NULL ||
|
|
alt->type == ty_err) continue;
|
|
if (!variant_present(u->params,
|
|
alt->type))
|
|
err(c, cs->pos,
|
|
"case: %s is not a variant of %s",
|
|
type_name(c->a, alt->type),
|
|
type_name(c->a, st));
|
|
}
|
|
if (cs->str && cs->str[0])
|
|
scope_define(c->cur, cs->str, SK_VAR, vt, cs);
|
|
}
|
|
cstmt(c, cs->body);
|
|
c->cur = saved;
|
|
}
|
|
/* Exhaustiveness: every variant must be handled. A default arm
|
|
* absorbs anything not otherwise covered. */
|
|
if (!has_default) {
|
|
for (Tparam *p = u->params; p; p = p->next) {
|
|
int covered = 0;
|
|
for (Node *cs = n->list; cs && !covered;
|
|
cs = cs->next) {
|
|
if (variant_match(cs->type, p->type)) {
|
|
covered = 1;
|
|
break;
|
|
}
|
|
for (Node *alt = cs->list; alt;
|
|
alt = alt->next)
|
|
if (variant_match(alt->type,
|
|
p->type)) {
|
|
covered = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!covered)
|
|
err(c, n->pos,
|
|
"match: variant %s not handled",
|
|
type_name(c->a, p->type));
|
|
}
|
|
}
|
|
/* match-as-expression: the type is the common yield type
|
|
* across arms. If no arm yields, the match is a statement
|
|
* and its type is void. */
|
|
Type *yt = NULL;
|
|
for (Node *cs = n->list; cs; cs = cs->next) {
|
|
Type *t = match_yield_type(cs->body);
|
|
if (t == NULL) continue;
|
|
if (yt == NULL) yt = t;
|
|
else if (!type_eq(yt, t) && !type_assignable(yt, t))
|
|
err(c, cs->pos,
|
|
"match arm yields %s, expected %s",
|
|
type_name(c->a, t), type_name(c->a, yt));
|
|
}
|
|
n->type = yt ? yt : ty_void;
|
|
return n->type;
|
|
}
|
|
case N_TYPETEST: case N_TYPEASSERT: {
|
|
/* `e is T` → bool; `e as T` → T.
|
|
* Requires lhs to be a tagged union and T to be one of its
|
|
* variants. The variant-index lookup lives in cgen (it knows
|
|
* NAMED-vs-structural matching for the success-variant rules);
|
|
* here we just check the LHS shape and resolve T. */
|
|
Type *t = cexpr(c, n->lhs);
|
|
Type *vt = resolve_type(c, n->rhs);
|
|
/* Stash the variant on rhs->type — cgen reads it uniformly
|
|
* whether the expression returns bool (is) or the variant
|
|
* itself (as). */
|
|
if (n->rhs) n->rhs->type = vt;
|
|
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
|
/* Enum ↔ integer cast: `enumval as intT` or `int as enumT`.
|
|
* Reinterpret-only — the storage shape is already integer, so
|
|
* cgen treats the cast as a no-op (the value lives in the same
|
|
* register). The `is` form is rejected; enums aren't sums. */
|
|
Type *uu = u;
|
|
Type *vu = (vt && vt->kind == TY_NAMED) ? vt->under : vt;
|
|
int lhs_enum = uu && uu->kind == TY_ENUM;
|
|
int rhs_enum = vu && vu->kind == TY_ENUM;
|
|
if (n->kind == N_TYPEASSERT && (lhs_enum || rhs_enum) &&
|
|
type_isint(t) && type_isint(vt)) {
|
|
return n->type = vt;
|
|
}
|
|
if (u == NULL || u->kind != TY_TAGGED) {
|
|
const char *op = (n->kind == N_TYPETEST) ? "is" : "as";
|
|
return n->type = err(c, n->pos,
|
|
"%s on non-tagged-union %s", op,
|
|
type_name(c->a, t));
|
|
}
|
|
/* Diagnostic-only: verify T appears as a variant. Mirrors
|
|
* cg_variant_match (NAMED ≡ pointer-identical, otherwise
|
|
* structural). Skipped silently if vt is ty_err. */
|
|
if (vt && vt != ty_err) {
|
|
int found = 0;
|
|
for (Tparam *p = u->params; p; p = p->next) {
|
|
if (p->type == NULL) continue;
|
|
if (p->type->kind == TY_NAMED &&
|
|
vt->kind == TY_NAMED) {
|
|
if (p->type == vt) { found = 1; break; }
|
|
} else if (p->type->kind == TY_NAMED ||
|
|
vt->kind == TY_NAMED) {
|
|
continue;
|
|
} else if (type_eq(p->type, vt)) {
|
|
found = 1; break;
|
|
}
|
|
}
|
|
if (!found)
|
|
err(c, n->pos, "%s is not a variant of %s",
|
|
type_name(c->a, vt), type_name(c->a, t));
|
|
}
|
|
return n->type = (n->kind == N_TYPETEST) ? ty_bool : vt;
|
|
}
|
|
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));
|
|
}
|
|
/* Error subset = `!`-flagged variants (Hare semantics) or
|
|
* everything-but-first when no flags are present (legacy).
|
|
*
|
|
* For ? : each error variant must be propagatable — i.e. it
|
|
* must be a variant of the enclosing function's return type
|
|
* (so the caller can match on it). cgen does the tag remap.
|
|
* For ! : no propagation, so no subset check. */
|
|
Type *succ = tagged_success_type(u);
|
|
int has_errors = 0;
|
|
for (Tparam *p = u->params; p; p = p->next)
|
|
if (tagged_is_error_variant(u, p->type)) {
|
|
has_errors = 1; break;
|
|
}
|
|
if (n->kind == N_TRYPROP && has_errors) {
|
|
Type *r = c->ret;
|
|
Type *ru = (r && r->kind == TY_NAMED) ? r->under : r;
|
|
if (ru == NULL || ru->kind != TY_TAGGED) {
|
|
err(c, n->pos,
|
|
"?: enclosing function must return a tagged "
|
|
"union to propagate errors (got %s)",
|
|
type_name(c->a, r));
|
|
} else {
|
|
for (Tparam *e = u->params; e; e = e->next) {
|
|
if (!tagged_is_error_variant(u, e->type))
|
|
continue;
|
|
int ok = 0;
|
|
for (Tparam *p = ru->params; p;
|
|
p = p->next)
|
|
if (variant_match(p->type,
|
|
e->type)) {
|
|
ok = 1; break;
|
|
}
|
|
if (!ok)
|
|
err(c, n->pos,
|
|
"?: error variant %s not in enclosing return %s",
|
|
type_name(c->a, e->type),
|
|
type_name(c->a, r));
|
|
}
|
|
}
|
|
}
|
|
return n->type = succ ? succ : 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);
|
|
/* `let xs: [_]T = arrlit;` — fill in the inferred length from the
|
|
* initialiser. `resolve_type` left alen=0 as a sentinel. */
|
|
if (declared && declared->kind == TY_ARRAY && declared->alen == 0 &&
|
|
initt) {
|
|
Type *iu = (initt->kind == TY_NAMED) ? initt->under : initt;
|
|
if (iu && iu->kind == TY_ARRAY)
|
|
declared = type_array(c->a, declared->sub, iu->alen);
|
|
else
|
|
err(c, n->pos, "[_]T needs an array-literal initialiser");
|
|
}
|
|
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;
|
|
}
|
|
/* An array literal with a trailing `...` repeat marker has
|
|
* "flexible" length — the last value fills the remaining slots.
|
|
* The literal's type carries the explicit-element count, which
|
|
* may not match the declared length. Trust the declared type
|
|
* when the marker is present. */
|
|
int has_arr_repeat = 0;
|
|
if (n->rhs && n->rhs->kind == N_ARRLIT) {
|
|
for (Node *e = n->rhs->list; e; e = e->next)
|
|
if (e->kind == N_FIELD && e->str &&
|
|
strcmp(e->str, "...") == 0) {
|
|
has_arr_repeat = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (declared && initt && initt != ty_err && !has_arr_repeat &&
|
|
!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]) {
|
|
Sym *s = scope_define(c->cur, n->str, SK_VAR, t, n);
|
|
if (s && n->op == TK_CONST) s->is_const = 1;
|
|
}
|
|
}
|
|
|
|
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--;
|
|
if (n->els) cstmt(c, n->els);
|
|
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--;
|
|
/* `else` block: runs at normal cond-false exit (skipped by
|
|
* break). Outside the loop count — break/continue inside the
|
|
* else target an enclosing loop, not this one. */
|
|
if (n->els) cstmt(c, n->els);
|
|
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]) {
|
|
Sym *s = scope_define(c->cur, l->str, SK_VAR, t, l);
|
|
if (s && n->op == TK_CONST) s->is_const = 1;
|
|
}
|
|
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) {
|
|
/* `_` lvalue: skip type check, advance the tuple cursor. */
|
|
if (lv->kind == N_IDENT && lv->str && lv->str[0] == '\0') {
|
|
if (tp) tp = tp->next;
|
|
continue;
|
|
}
|
|
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_YIELD: if (n->lhs) (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)... }`).
|
|
* USE declarations are installed in this same step so dotted type
|
|
* references (`strconv.invalid`) resolve when typedecl bodies are
|
|
* walked in the next pass. */
|
|
for (Node *d = file->list; d; d = d->next) {
|
|
if (d->kind == N_USE) {
|
|
scope_define(c->cur, d->str, SK_USE, NULL, d);
|
|
continue;
|
|
}
|
|
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;
|
|
d->type->iserror = under->iserror;
|
|
}
|
|
}
|
|
for (Node *d = file->list; d; d = d->next) {
|
|
switch (d->kind) {
|
|
case N_USE:
|
|
/* already installed in pass 1; no-op here so the
|
|
* old fall-through doesn't re-define. */
|
|
break;
|
|
case N_DEF: {
|
|
Type *t = resolve_type(c, d->lhs);
|
|
d->type = t;
|
|
Sym *prev = scope_lookup_local(c->cur, d->str);
|
|
if (prev && prev->kind == SK_USE) {
|
|
prev->kind = SK_DEF; prev->type = t; prev->decl = d;
|
|
} else 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;
|
|
Sym *prev = scope_lookup_local(c->cur, d->str);
|
|
if (prev && prev->kind == SK_USE) {
|
|
prev->kind = SK_FN; prev->type = t; prev->decl = d;
|
|
} else 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]) {
|
|
Sym *prev = scope_lookup_local(c->cur, d->str);
|
|
if (prev && prev->kind == SK_USE) {
|
|
prev->kind = SK_VAR; prev->type = t;
|
|
prev->decl = d;
|
|
} else
|
|
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;
|
|
}
|
|
}
|
|
}
|