wwstage: port struct embedding; graduate #59.13

The last frontend-gap pin: wwstage had no Hare struct embedding
(struct { hash.hash, ... }), rejecting lib/crypto/sha256 at parse.

- parse.ww: the three member forms (named / anonymous struct / bare
  dotted-ident embed), consume-then-branch since this parser has no
  peek; embeds carry f.str == "" and the type in f.lhs.
- check.ww N_TSTRUCT flatten: promote the inner struct's flattened
  fields at base+src.offset (check.c:961-990); the embed is one
  nested-struct unit in the slot ladder; the resolved inner AST is
  planted on the TFIELD rhs for cgen.
- check.ww walkers: astoffset / exprtype N_DOT / #251 struct-lit
  field lookups descend embeds through shared helpers; the collision
  and non-struct-embed rejects live in validatestructfields (the
  once-per-decl diagnostic site).
- cgenutil.ww registerstruct: regfieldrun walks the AST against the
  flattened tfield cursor, descending embeds via the planted inner
  AST so promoted fieldinfo entries keep the inner field's own name
  and type node.
- wwi printers unchanged (both stages already emit nameless fields).

sha256_test compiles byte-identically end to end and its 6 tests
pass; 989_lib_byteid is now 44 id / 0 divergent / 0 wwreject.
Fixtures r5913_* (promoted rw, offset shift, anonymous embed,
two-level embed + promoted fn-ptr callee, three rejects); corpus pin
1485/2970.
This commit is contained in:
2026-08-08 02:37:50 +09:00
parent 6553d60e91
commit 5abb1e6069
13 changed files with 472 additions and 95 deletions

View File

@@ -32,9 +32,9 @@ categories out of the ordinary developer target.
| Fixed point and self-host | `test-bootstrap` |
| Host linker/platform behavior | `test-platform` |
The live declarative compiler corpus has 1,478 fixtures and 2,956 C/WW cells:
335 expected rejections (311 shared and 24 stage-specific), 17 compile-only
successes, 191 exit-zero programs, and 935 explicit-exit programs.
The live declarative compiler corpus has 1,485 fixtures and 2,970 C/WW cells:
338 expected rejections (314 shared and 24 stage-specific), 17 compile-only
successes, 191 exit-zero programs, and 939 explicit-exit programs.
147 native C carriers remain. They are partitioned exactly once as five
in-process units, 24 byte/artifact gates, six bootstrap gates, one platform

View File

@@ -1,13 +1,13 @@
package wwfixture;
def protocolversion: i32 = 1;
def corpuscount: i32 = 1478;
def errorcount: i32 = 335;
def corpuscount: i32 = 1485;
def errorcount: i32 = 338;
def compilecount: i32 = 17;
def runcount: i32 = 191;
def runexitcount: i32 = 935;
def nativecount: i32 = 2956;
def corpushash: str = "2ac2342a80c4148968250bf4ce93852b78cdefb6af96622fa533eea4a68837c6";
def runexitcount: i32 = 939;
def nativecount: i32 = 2970;
def corpushash: str = "69453e64f903a75b8a3f27c79e336ae766b3fb7ecfaac85d13f5d1f0c0dc4f50";
type directive = enum i32 {
ERROR = 0,

View File

@@ -214,11 +214,37 @@ fn parsetype(p: *parser) *node = {
let fpl = p.curline;
let fpc = p.curcol;
let f = newnode(nkind.N_TFIELD, fpf, fpl, fpc);
let fid: str;
expectident(p, &fid);
f.str = fid;
expecttok(p, tkind.TK_COLON, "expected ':' in field");
f.lhs = parsetype(p);
// Three member forms (cstage parse.c:230-244):
// name: type — regular field
// struct { ... } — anonymous embedded struct
// Identifier — bare dotted-ident embedded type
// Embeds carry f.str == "" and the type in f.lhs. The
// cstage branches on peek≠':'; this parser has no peek,
// so consume the leading ident first and branch on ':'.
if (p.curkind == tkind.TK_STRUCT) {
f.lhs = parsetype(p);
} else {
let fid: str;
expectident(p, &fid);
if (p.curkind == tkind.TK_COLON) {
advance(p);
f.str = fid;
f.lhs = parsetype(p);
} else {
// Rebuild the dotted TNAME the consumed ident
// began (parsetype's TK_IDENT collapse, L280-285).
let tn = newnode(nkind.N_TNAME, fpf, fpl, fpc);
let acc = fid;
for (p.curkind == tkind.TK_DOT) {
advance(p);
if (p.curkind != tkind.TK_IDENT) { break; };
acc = joindotted(acc, p.curtext);
advance(p);
};
tn.str = acc;
f.lhs = tn;
};
};
if (fhead == nil) { fhead = f; ftail = f; }
else { ftail.next = f; ftail = f; };
if (!accepttok(p, tkind.TK_COMMA)) { break; };

View File

@@ -2990,6 +2990,53 @@ fn fieldsize(c: *cgen, tnode: *syntax.node) i32 = {
// non-TFIELD identically), so they advance in exact step. si.totsize keeps
// the slot-padded total (stack-slot allocator's number) from ti.slotsize,
// already 8-rounded at check.ww:2259-2261.
// regfieldwalk — cursor state threaded through regfieldrun's embed-
// descending AST/tfield lock-step walk.
type regfieldwalk = struct {
tf: *syntax.tfield,
head: *fieldinfo,
tail: *fieldinfo,
};
// regfieldrun — walk an AST TFIELD list against the flattened
// tinfo.fields cursor. A named field consumes one tfield; an embed
// (#59.13, f.str == "") descends into the resolved inner struct AST
// the checker planted on f.rhs (check.ww N_TSTRUCT flatten) — its
// fields were promoted into THIS struct's tinfo run in the same
// order, so the one cursor stays in exact step and each promoted
// fieldinfo carries the inner field's own name and type node.
fn regfieldrun(c: *cgen, flist: *syntax.node, w: *regfieldwalk) void = {
let f: *syntax.node = flist;
for (f != nil) {
if (f.kind == syntax.nkind.N_TFIELD) {
if (f.str.len == 0) {
if (f.rhs == nil) {
let msg: str = "#59.13: registerstruct: embed field has no checker-planted inner struct AST\n";
os.write(2, msg.ptr, msg.len: u64);
os.exit(1);
};
regfieldrun(c, f.rhs.list, w);
} else {
if (w.tf == nil) {
let msg: str = "#44/#55: registerstruct: AST/tfield walk desync (more TFIELDs than tinfo.fields)\n";
os.write(2, msg.ptr, msg.len: u64);
os.exit(1);
};
let fi: *fieldinfo = alloc(fieldinfo{
fname = f.str,
foff = w.tf.offset: i32,
fsz = w.tf.type_.size: i32,
tnode = f.lhs,
})!;
if (w.head == nil) { w.head = fi; w.tail = fi; }
else { w.tail.finext = fi; w.tail = fi; };
w.tf = w.tf.tnext;
};
};
f = f.next;
};
};
fn registerstruct(c: *cgen, name: str, srcmod: str, tstruct: *syntax.node) void = {
let si: *structinfo = alloc(structinfo{
sname = name,
@@ -3006,30 +3053,12 @@ fn registerstruct(c: *cgen, name: str, srcmod: str, tstruct: *syntax.node) void
os.write(2, msg.ptr, msg.len: u64);
os.exit(1);
};
let head: *fieldinfo = nil;
let tail: *fieldinfo = nil;
let f: *syntax.node = tstruct.list;
let tf: *syntax.tfield = ti.fields;
for (f != nil) {
if (f.kind == syntax.nkind.N_TFIELD) {
if (tf == nil) {
let msg: str = "#44/#55: registerstruct: AST/tfield walk desync (more TFIELDs than tinfo.fields)\n";
os.write(2, msg.ptr, msg.len: u64);
os.exit(1);
};
let fi: *fieldinfo = alloc(fieldinfo{
fname = f.str,
foff = tf.offset: i32,
fsz = tf.type_.size: i32,
tnode = f.lhs,
})!;
if (head == nil) { head = fi; tail = fi; }
else { tail.finext = fi; tail = fi; };
tf = tf.tnext;
};
f = f.next;
};
si.fields = head;
let w: regfieldwalk;
w.tf = ti.fields;
w.head = nil;
w.tail = nil;
regfieldrun(c, tstruct.list, &w);
si.fields = w.head;
si.totsize = ti.slotsize: i32;
si.sinext = c.structs;
c.structs = si;

View File

@@ -1563,6 +1563,64 @@ fn yieldclass(t: *syntax.tinfo) i32 = {
return 0i32;
};
// aststructoffset — byte offset of `name` inside struct AST `stn`,
// walking fields with per-field alignment and descending embeds
// (#59.13: promoted names live at base + inner offset; cstage folds
// offset() over the flattened tinfo, check.c:1691-1698, so it is
// embed-transparent there). -1 on a miss.
fn aststructoffset(c: *checker, stn: *syntax.node, name: str, depth: i32) i64 = {
if (depth > EMBEDDEPTHMAX) { return -1i64; };
let off: i64 = 0i64;
let f: *syntax.node = stn.list;
for (f != nil) {
if (f.kind == syntax.nkind.N_TFIELD) {
let fa: i64 = astalign(c, f.lhs);
// packed: no inter-field padding (harec type_store.c:206-213).
if (stn.packed == 0) {
off = (off + fa - 1i64) & ~(fa - 1i64);
};
if (f.str.len != 0) {
if (syntax.streq(f.str, name)) { return off; };
} else {
let inner: *syntax.node = structembedbody(c, f.lhs);
if (inner != nil) {
let r: i64 = aststructoffset(c, inner, name, depth + 1);
if (r >= 0i64) { return off + r; };
};
};
off += astsize(c, f.lhs);
};
f = f.next;
};
return -1i64;
};
// aststructfieldtype — the declared type expr of `name` inside struct
// AST `stn`, descending embeds (#59.13: a promoted name resolves to
// the inner field's own type node — the exact node the non-embed walk
// would have returned had the field been declared inline). nil on a
// miss. Shared by the exprtype N_DOT struct arm and the #251
// struct-lit field walk.
fn aststructfieldtype(c: *checker, stn: *syntax.node, name: str, depth: i32) *syntax.node = {
if (depth > EMBEDDEPTHMAX) { return nil; };
let f: *syntax.node = stn.list;
for (f != nil) {
if (f.kind == syntax.nkind.N_TFIELD) {
if (f.str.len != 0) {
if (syntax.streq(f.str, name)) { return f.lhs; };
} else {
let inner: *syntax.node = structembedbody(c, f.lhs);
if (inner != nil) {
let r: *syntax.node = aststructfieldtype(c, inner, name, depth + 1);
if (r != nil) { return r; };
};
};
};
f = f.next;
};
return nil;
};
// astoffset — byte offset of `dot.str` inside the struct type of
// `dot.lhs`. Mirrors cstage cmd/wcc/check.c:932-961: peel one N_TPTR
// (for `p.field` where p is *Struct), require N_TSTRUCT, walk fields
@@ -1580,21 +1638,7 @@ fn astoffset(c: *checker, dot: *syntax.node) i64 = {
};
if (rtyp == nil) { return -1i64; };
if (rtyp.kind != syntax.nkind.N_TSTRUCT) { return -1i64; };
let off: i64 = 0i64;
let f: *syntax.node = rtyp.list;
for (f != nil) {
if (f.kind == syntax.nkind.N_TFIELD) {
let fa: i64 = astalign(c, f.lhs);
// packed: no inter-field padding (harec type_store.c:206-213).
if (rtyp.packed == 0) {
off = (off + fa - 1i64) & ~(fa - 1i64);
};
if (syntax.streq(f.str, dot.str)) { return off; };
off += astsize(c, f.lhs);
};
f = f.next;
};
return -1i64;
return aststructoffset(c, rtyp, dot.str, 0);
};
// arenau64tos — decimal string for the folded INTLIT's `str` field.
@@ -1910,34 +1954,122 @@ fn stampenumvals(c: *checker, n: *syntax.node) void = {
};
};
// structembedbody — resolve an embed member's type expr to its struct
// body AST, or nil (non-struct embed; the caller owns the diagnostic).
fn structembedbody(c: *checker, t: *syntax.node) *syntax.node = {
let u: *syntax.node = resolvealias(c, unwrapbang(t));
if (u == nil) { return nil; };
if (u.kind != syntax.nkind.N_TSTRUCT) { return nil; };
return u;
};
// Embed-descend depth cap (#59.13). Embed cycles are loud-rejected by
// circularnamed at the tinfo flatten; the AST diagnostic/lookup walks
// only have to TERMINATE on them, not report them twice.
def EMBEDDEPTHMAX: i32 = 32;
// structhasfield — does struct AST `stn` declare `name`, directly or
// promoted through an embed?
fn structhasfield(c: *checker, stn: *syntax.node, name: str, depth: i32) bool = {
if (depth > EMBEDDEPTHMAX) { return false; };
let f: *syntax.node = stn.list;
for (f != nil) {
if (f.kind == syntax.nkind.N_TFIELD) {
if (f.str.len != 0) {
if (syntax.streq(f.str, name)) { return true; };
} else {
let inner: *syntax.node = structembedbody(c, f.lhs);
if (inner != nil) {
if (structhasfield(c, inner, name, depth + 1)) { return true; };
};
};
};
f = f.next;
};
return false;
};
// earlierhasfield — does any field of `outer` declared BEFORE `upto`
// carry `name`, directly or promoted through an embed? The "existing
// field" set of cstage check.c:945-950 / :972-980.
fn earlierhasfield(c: *checker, outer: *syntax.node, upto: *syntax.node, name: str) bool = {
let e: *syntax.node = outer.list;
for (e != upto) {
if (e.kind == syntax.nkind.N_TFIELD) {
if (e.str.len != 0) {
if (syntax.streq(e.str, name)) { return true; };
} else {
let inner: *syntax.node = structembedbody(c, e.lhs);
if (inner != nil) {
if (structhasfield(c, inner, name, 0)) { return true; };
};
};
};
e = e.next;
};
return false;
};
// embedcollides — err for every name `inner` promotes into `outer`
// that a field before `upto` already declares. Mirrors cstage
// check.c:971-980.
fn embedcollides(c: *checker, outer: *syntax.node, upto: *syntax.node, inner: *syntax.node, depth: i32) void = {
if (depth > EMBEDDEPTHMAX) { return; };
let f: *syntax.node = inner.list;
for (f != nil) {
if (f.kind == syntax.nkind.N_TFIELD) {
if (f.str.len != 0) {
if (earlierhasfield(c, outer, upto, f.str)) {
cerr(upto.file);
cerr(": error: embedded field '");
cerr(f.str);
cerr("' collides with existing field\n");
c.errs += 1;
};
} else {
let deeper: *syntax.node = structembedbody(c, f.lhs);
if (deeper != nil) {
embedcollides(c, outer, upto, deeper, depth + 1);
};
};
};
f = f.next;
};
};
// validatestructfields — reject a struct decl carrying two fields with
// the same name. Pure diagnostic: a read-only O(n^2) walk over the named
// field list, no n.type_ / offset / checker-state mutation (the byte-id
// safety condition — valid programs have no dup, so codegen is untouched).
// Cstage twin: cmd/wcc/check.c:943-950 (the regular-named-field arm of
// resolve_type's N_TSTRUCT). The embed-collision arm there (check.c:961-
// 985) is NOT ported — ww has no struct embedding (parser gap catB-89),
// so every N_TFIELD is a regular named field. Fires once per struct decl
// from resolvewalk's eager type-decl dispatch (rule-10 symmetric with
// cstage's once-per-resolve_type), not from the per-query size/align/
// offset recompute arms (use-site = double-fire, drew-dv ruling).
// the same name, and the two invalid embed shapes (#59.13: a non-struct
// embed; an embed whose promoted name collides with an existing field).
// Pure diagnostic: a read-only walk, no n.type_ / offset / checker-state
// mutation (the byte-id safety condition — valid programs have no dup,
// so codegen is untouched). Cstage twin: cmd/wcc/check.c:943-950 (named
// arm) + :961-985 (embed arm) of resolve_type's N_TSTRUCT; cstage errs
// inside the flatten, ww keeps the flatten silent because it re-runs
// per size/align query. Fires once per struct decl from resolvewalk's
// eager type-decl dispatch (rule-10 symmetric with cstage's
// once-per-resolve_type), not from the per-query size/align/offset
// recompute arms (use-site = double-fire, drew-dv ruling).
fn validatestructfields(c: *checker, n: *syntax.node) void = {
let f: *syntax.node = n.list;
for (f != nil) {
if (f.kind == syntax.nkind.N_TFIELD && f.str.len != 0) {
let e: *syntax.node = n.list;
for (e != f) {
if (e.kind == syntax.nkind.N_TFIELD
&& e.str.len != 0
&& syntax.streq(e.str, f.str)) {
if (f.kind == syntax.nkind.N_TFIELD) {
if (f.str.len != 0) {
if (earlierhasfield(c, n, f, f.str)) {
cerr(f.file);
cerr(": error: duplicate field '");
cerr(f.str);
cerr("'\n");
c.errs += 1;
break;
};
e = e.next;
} else {
let inner: *syntax.node = structembedbody(c, f.lhs);
if (inner == nil) {
cerr(f.file);
cerr(": error: embedded type must be a struct\n");
c.errs += 1;
} else {
embedcollides(c, n, f, inner, 0);
};
};
};
f = f.next;
@@ -2417,7 +2549,7 @@ fn tinfofornode(c: *checker, n: *syntax.node) *syntax.tinfo = {
case syntax.nkind.N_TSTRUCT:
// Cstage cmd/wcc/check.c:468-527: per-field alignment, max
// align for the whole record, total rounded up to alignment.
// Anonymous-embed promotion is deferred (#13).
// Embeds promote the inner struct's fields (#59.13, below).
//
// Pre-bind into the cache BEFORE walking fields so a
// self-referential pointer field (e.g., `next: *node` inside
@@ -2444,8 +2576,6 @@ fn tinfofornode(c: *checker, n: *syntax.node) *syntax.tinfo = {
// A.6.3f-a) for the head/tail append pattern. Harec cite:
// ref/harec/include/types.h:109-115 struct_field and
// ref/harec/src/type_store.c:314-347 struct_init_from_atype.
// Anonymous-embed promotion not populated here (#13 per
// the cstage cite at check.ww:1263).
let fh: *syntax.tfield = nil;
let ft_: *syntax.tfield = nil;
let off: u64 = 0u64;
@@ -2468,11 +2598,46 @@ fn tinfofornode(c: *checker, n: *syntax.node) *syntax.tinfo = {
if (n.packed == 0 && ft.align > 0u64) {
off = (off + ft.align - 1u64) & ~(ft.align - 1u64);
};
let fldoff: u64 = off;
let tf: *syntax.tfield = alloc(syntax.tfield{name=f.str, type_=ft, offset=fldoff, tnext=nil})!;
if (fh == nil) { fh = tf; } else { ft_.tnext = tf; };
ft_ = tf;
off += ft.size;
if (f.str.len == 0) {
// Embed (#59.13): promote the inner struct's
// already-flattened fields at base+src.offset,
// mirroring cstage check.c:961-990. Diagnostics
// (non-struct embed, name collisions) live in
// validatestructfields — this arm can re-run
// per size/align query and must stay silent
// (the drew-dv once-per-decl ruling).
let inner: *syntax.tinfo = ft;
for (inner != nil && inner.kind == syntax.tykind.TY_NAMED) {
inner = inner.under;
};
if (inner != nil && inner.kind == syntax.tykind.TY_STRUCT) {
let base: u64 = off;
let src: *syntax.tfield = inner.fields;
for (src != nil) {
let tf: *syntax.tfield = alloc(syntax.tfield{name=src.name, type_=src.type_, offset=base + src.offset, tnext=nil})!;
if (fh == nil) { fh = tf; } else { ft_.tnext = tf; };
ft_ = tf;
src = src.tnext;
};
off = base + inner.size;
// cgenutil registerstruct descends embeds
// through the resolved inner struct AST;
// plant it on the (otherwise unused) TFIELD
// rhs — cgen has no alias resolver.
f.rhs = resolvealias(c, unwrapbang(f.lhs));
} else {
// error-path layout kept defined (cstage
// `off += ft ? ft->size : 0`); the build
// already fails via validatestructfields.
off += ft.size;
};
} else {
let fldoff: u64 = off;
let tf: *syntax.tfield = alloc(syntax.tfield{name=f.str, type_=ft, offset=fldoff, tnext=nil})!;
if (fh == nil) { fh = tf; } else { ft_.tnext = tf; };
ft_ = tf;
off += ft.size;
};
// Slot-padded layout (mirror of cgenutil
// fieldsize + registerstruct align rules).
let fsz: u64 = fieldslotsize(ft);
@@ -4019,15 +4184,14 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
};
};
};
// Struct field walk. Cstage L843-849 errors on missing field.
// Struct field walk. Cstage L843-849 errors on missing
// field. Embed-descending (#59.13): promoted names stamp
// the inner field's own type node.
if (bu != nil) { if (bu.kind == syntax.nkind.N_TSTRUCT) {
let f: *syntax.node = bu.list;
for (f != nil) {
if (f.kind == syntax.nkind.N_TFIELD) { if (syntax.streq(f.str, e.str)) {
e.type_ = tinfofornode(c, f.lhs): *void;
return f.lhs;
}; };
f = f.next;
let ftn: *syntax.node = aststructfieldtype(c, bu, e.str, 0);
if (ftn != nil) {
e.type_ = tinfofornode(c, ftn): *void;
return ftn;
};
}; };
// Tuple positional access `t.0`, `t.1`, …. Cstage L850-866
@@ -4103,14 +4267,7 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
for (fi != nil) {
if (fi.kind == syntax.nkind.N_FIELD
&& fi.lhs != nil) {
let ftn: *syntax.node = nil;
let tf: *syntax.node = stn.list;
for (tf != nil) {
if (tf.kind == syntax.nkind.N_TFIELD) {
if (syntax.streq(tf.str, fi.str)) { ftn = tf.lhs; };
};
tf = tf.next;
};
let ftn: *syntax.node = aststructfieldtype(c, stn, fi.str, 0);
if (ftn != nil) {
// #120: `S{ f: 1.0 }` narrows the
// field init to the field's f32.

View File

@@ -193,8 +193,12 @@ static const struct ent ents[] = {
{ .fixture = "lib/ww/syntax/asttest.ww", .inc = "lib/ww",
.mode = M_ID, .cite = "#59.12 graduated by #146 str==" },
/* -------- wwstage front-end gaps (task #59) -------------- */
/* #59.13 graduated: wwstage struct embedding (parser three member
* forms, checker tinfo flatten + promoted-name walkers, cgen
* registerstruct embed descend) — the hash.hash embed compiles
* byte-identically end to end. Runtime pins: r5913_*. */
{ .fixture = "lib/crypto/sha256/sha256_test.ww",
.mode = M_WWREJECT, .cite = "#59.13" },
.mode = M_ID, .cite = "#59.13 graduated by the embed port" },
/* #59.14 graduated to M_ID by #27: the #27b enum-as-int cgen fix
* (value passthrough, not a tagged assertion) + the fnmatch.ww:126
* `'\\': u8` explicit cast (the rune-lit→u8 narrow w6c_ww rejects at

View File

@@ -0,0 +1,21 @@
//ww:run-exit 42
package main;
// #59.13: anonymous embedded struct — its fields promote to the outer
// scope like a bare-name embed's.
type withanon = struct {
struct {
ax: i32,
ay: i32,
},
z: i32,
};
fn main() i32 = {
let w: withanon;
w.ax = 13;
w.ay = 14;
w.z = 15;
return w.ax + w.ay + w.z;
};

View File

@@ -0,0 +1,17 @@
//ww:error "embedded field 'x' collides with existing field"
package main;
// #59.13: a promoted name colliding with an earlier named field is a
// loud reject on both stages (cstage check.c:971-980).
type inner = struct {
x: i32,
y: i32,
};
type outer = struct {
x: i32,
inner,
};
fn main() i32 = { return 0; };

View File

@@ -0,0 +1,34 @@
//ww:run-exit 33
package main;
// #59.13: two-level embedding (outer2 embeds deep embeds cbs) — the
// promotion flattens transitively, and a promoted `*fn(...)` field
// still resolves as an indirect callee (the fieldinfo run keeps the
// inner field's own type node).
type cbs = struct {
cb: *fn(x: i32) i32,
k: i32,
};
type deep = struct {
cbs,
extra: i32,
};
type outer2 = struct {
deep,
tail: i32,
};
fn twice(x: i32) i32 = { return x * 2; };
fn main() i32 = {
let o: outer2;
o.cb = &twice;
o.k = 4;
o.extra = 5;
o.tail = 6;
let r: i32 = o.cb(9);
return r + o.k + o.extra + o.tail;
};

View File

@@ -0,0 +1,18 @@
//ww:error "duplicate field 'x'"
package main;
// #59.13: a named field colliding with an earlier embed's promoted
// name is the plain duplicate-field reject (cstage check.c:943-950
// compares against the flattened head, which includes promotions).
type inner = struct {
x: i32,
y: i32,
};
type outer = struct {
inner,
x: i32,
};
fn main() i32 = { return 0; };

View File

@@ -0,0 +1,11 @@
//ww:error "embedded type must be a struct"
package main;
// #59.13: only struct types embed (cstage check.c:964-968).
type outer = struct {
i32,
c: i32,
};
fn main() i32 = { return 0; };

View File

@@ -0,0 +1,37 @@
//ww:run-exit 42
package main;
// #59.13: fields declared after an embed sit at base+inner.size, the
// offset() fold sees promoted names, and promoted access works through
// a pointer receiver and a nested chain (o.p.y — promoted struct field
// then its own member).
type pos = struct {
x: i32,
y: i32,
};
type mid = struct {
p: pos,
tag: i32,
};
type outer = struct {
mid,
c: i32,
};
fn bump(o: *outer) void = {
o.tag += 1;
o.p.y = o.p.x + 2;
};
fn main() i32 = {
let o: outer;
o.p.x = 5;
o.tag = 9;
o.c = 20;
bump(&o);
let shift: i64 = offset(o.c) - offset(o.tag);
return o.p.y + o.tag + o.c + shift: i32 + 1;
};

View File

@@ -0,0 +1,23 @@
//ww:run-exit 42
package main;
// #59.13: bare-name struct embedding — promoted fields read and write
// like inline declarations. Min repro of the wwstage parse cascade.
type inner = struct {
a: i32,
b: i32,
};
type outer = struct {
inner,
c: i32,
};
fn main() i32 = {
let o: outer;
o.a = 7;
o.b = 11;
o.c = 24;
return o.a + o.b + o.c;
};