ww: hare-feature batch (_, const, [_]T, ..., size/offset, assert, for-else)
This commit is contained in:
163
cmd/wcc/check.c
163
cmd/wcc/check.c
@@ -91,10 +91,15 @@ resolve_type(Checker *c, Node *n)
|
||||
return type_slice(c->a, resolve_type(c, n->lhs));
|
||||
case N_TARRAY: {
|
||||
u64 len = 0;
|
||||
if (n->rhs && n->rhs->kind == N_INTLIT)
|
||||
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
|
||||
} 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:
|
||||
@@ -304,6 +309,9 @@ cexpr(Checker *c, Node *n)
|
||||
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: {
|
||||
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);
|
||||
@@ -417,6 +425,59 @@ cexpr(Checker *c, Node *n)
|
||||
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) {
|
||||
@@ -443,6 +504,43 @@ cexpr(Checker *c, Node *n)
|
||||
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. */
|
||||
@@ -488,6 +586,19 @@ cexpr(Checker *c, Node *n)
|
||||
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))
|
||||
@@ -641,19 +752,45 @@ 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;
|
||||
}
|
||||
if (declared && initt && initt != 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])
|
||||
scope_define(c->cur, n->str, SK_VAR, t, n);
|
||||
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
|
||||
@@ -721,6 +858,7 @@ cstmt(Checker *c, Node *n)
|
||||
}
|
||||
cstmt(c, n->body);
|
||||
c->loops--;
|
||||
if (n->els) cstmt(c, n->els);
|
||||
c->cur = saved;
|
||||
break;
|
||||
}
|
||||
@@ -738,6 +876,10 @@ cstmt(Checker *c, Node *n)
|
||||
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;
|
||||
}
|
||||
@@ -759,8 +901,10 @@ cstmt(Checker *c, Node *n)
|
||||
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 (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)
|
||||
@@ -776,6 +920,11 @@ cstmt(Checker *c, Node *n)
|
||||
}
|
||||
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))
|
||||
|
||||
Reference in New Issue
Block a user