diff --git a/cmd/wcc/check.c b/cmd/wcc/check.c index 1b763eb7..f098b85f 100644 --- a/cmd/wcc/check.c +++ b/cmd/wcc/check.c @@ -167,16 +167,56 @@ resolve_type(Checker *c, Node *n) 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; + 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; diff --git a/cmd/wcc/parse.c b/cmd/wcc/parse.c index b77246ee..de1ba2ba 100644 --- a/cmd/wcc/parse.c +++ b/cmd/wcc/parse.c @@ -191,9 +191,21 @@ parsetype(Parser *p) while (p->cur.kind != TK_RBRACE && p->cur.kind != TK_EOF) { Pos fp = p->cur.pos; Node *f = newnode(p->a, N_TFIELD, fp); - f->str = expectident(p); - expect(p, TK_COLON); - f->lhs = parsetype(p); + /* Three member forms: + * name: type — regular field + * struct { ... } — anonymous embedded struct + * Identifier — bare-name embedded type + * Embeds carry f->str == NULL and the type in f->lhs. */ + if (p->cur.kind == TK_STRUCT) { + f->lhs = parsetype(p); + } else if (p->cur.kind == TK_IDENT + && peek(p).kind != TK_COLON) { + f->lhs = parsetype(p); + } else { + f->str = expectident(p); + expect(p, TK_COLON); + f->lhs = parsetype(p); + } if (head == NULL) head = f; else tail->next = f; tail = f; diff --git a/test/wcc/700_e2e.c b/test/wcc/700_e2e.c index 985af942..81de89bb 100644 --- a/test/wcc/700_e2e.c +++ b/test/wcc/700_e2e.c @@ -344,6 +344,20 @@ static const struct row rows[] = { " os.assert(r == 42, \"sum mismatch\\n\");\n" " return r: i32;\n" "};", 42 }, + /* Hare-style struct embedding: bare-name embed + anonymous + * nested struct, fields promoted to the outer scope. Both + * field-by-field assignment and flat struct-literal init must + * see promoted fields. */ + { "type point = struct { x: i32, y: i32 };\n" + "type vec = struct {\n" + " point,\n" + " struct { z: i32 },\n" + " w: i32,\n" + "};\n" + "fn main() i32 = {\n" + " let v: vec = vec { x = 1, y = 2, z = 3, w = 36 };\n" + " return v.x + v.y + v.z + v.w;\n" + "};", 42 }, /* 3-field tuple destructure in for-range */ { "fn main() i32 = {\n" " let buf: [3]i64;\n"