diff --git a/docs/test-system-v2.md b/docs/test-system-v2.md index e1f9d96a..79d61855 100644 --- a/docs/test-system-v2.md +++ b/docs/test-system-v2.md @@ -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 diff --git a/internal/wwfixture/types.ww b/internal/wwfixture/types.ww index 5fa66bd7..8e3c2b40 100644 --- a/internal/wwfixture/types.ww +++ b/internal/wwfixture/types.ww @@ -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, diff --git a/lib/ww/syntax/parse.ww b/lib/ww/syntax/parse.ww index 5e6a3025..e4a6d891 100644 --- a/lib/ww/syntax/parse.ww +++ b/lib/ww/syntax/parse.ww @@ -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; }; diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 456e2030..1b4e3c00 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -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; diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index bd986590..ff23b92f 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -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. diff --git a/test/wcc/989_lib_byteid.c b/test/wcc/989_lib_byteid.c index 4953b3b9..4e4ae237 100644 --- a/test/wcc/989_lib_byteid.c +++ b/test/wcc/989_lib_byteid.c @@ -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 diff --git a/test/wcc/data/r5913_anon_embed/case.ww b/test/wcc/data/r5913_anon_embed/case.ww new file mode 100644 index 00000000..a2a2c79c --- /dev/null +++ b/test/wcc/data/r5913_anon_embed/case.ww @@ -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; +}; diff --git a/test/wcc/data/r5913_collide_reject/case.ww b/test/wcc/data/r5913_collide_reject/case.ww new file mode 100644 index 00000000..58b3daf1 --- /dev/null +++ b/test/wcc/data/r5913_collide_reject/case.ww @@ -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; }; diff --git a/test/wcc/data/r5913_deep_fnptr/case.ww b/test/wcc/data/r5913_deep_fnptr/case.ww new file mode 100644 index 00000000..afbdc8b3 --- /dev/null +++ b/test/wcc/data/r5913_deep_fnptr/case.ww @@ -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; +}; diff --git a/test/wcc/data/r5913_dup_after_embed_reject/case.ww b/test/wcc/data/r5913_dup_after_embed_reject/case.ww new file mode 100644 index 00000000..c2e94b6e --- /dev/null +++ b/test/wcc/data/r5913_dup_after_embed_reject/case.ww @@ -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; }; diff --git a/test/wcc/data/r5913_nonstruct_reject/case.ww b/test/wcc/data/r5913_nonstruct_reject/case.ww new file mode 100644 index 00000000..2122ec6a --- /dev/null +++ b/test/wcc/data/r5913_nonstruct_reject/case.ww @@ -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; }; diff --git a/test/wcc/data/r5913_offset_shift_nested/case.ww b/test/wcc/data/r5913_offset_shift_nested/case.ww new file mode 100644 index 00000000..370cf4d3 --- /dev/null +++ b/test/wcc/data/r5913_offset_shift_nested/case.ww @@ -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; +}; diff --git a/test/wcc/data/r5913_value_promoted_rw/case.ww b/test/wcc/data/r5913_value_promoted_rw/case.ww new file mode 100644 index 00000000..5f20cf6a --- /dev/null +++ b/test/wcc/data/r5913_value_promoted_rw/case.ww @@ -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; +};