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:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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; };
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
21
test/wcc/data/r5913_anon_embed/case.ww
Normal file
21
test/wcc/data/r5913_anon_embed/case.ww
Normal 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;
|
||||
};
|
||||
17
test/wcc/data/r5913_collide_reject/case.ww
Normal file
17
test/wcc/data/r5913_collide_reject/case.ww
Normal 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; };
|
||||
34
test/wcc/data/r5913_deep_fnptr/case.ww
Normal file
34
test/wcc/data/r5913_deep_fnptr/case.ww
Normal 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;
|
||||
};
|
||||
18
test/wcc/data/r5913_dup_after_embed_reject/case.ww
Normal file
18
test/wcc/data/r5913_dup_after_embed_reject/case.ww
Normal 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; };
|
||||
11
test/wcc/data/r5913_nonstruct_reject/case.ww
Normal file
11
test/wcc/data/r5913_nonstruct_reject/case.ww
Normal 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; };
|
||||
37
test/wcc/data/r5913_offset_shift_nested/case.ww
Normal file
37
test/wcc/data/r5913_offset_shift_nested/case.ww
Normal 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;
|
||||
};
|
||||
23
test/wcc/data/r5913_value_promoted_rw/case.ww
Normal file
23
test/wcc/data/r5913_value_promoted_rw/case.ww
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user