wcc,lib/ww/syntax: resolve qualified struct-literal pkg.Type{...} (#76)

The parser folded a qualified type pkg.Type into two different node shapes by position: declaration position collapsed it into one N_TNAME (resolved via the strrchr-leaf path), but literal position left an N_DOT chain that the struct-literal typeref handoff had no resolver arm for, so pkg.Type{...} rejected with "expected type expression".

Normalize the literal-position N_DOT chain into the same source-order N_TNAME the declaration path emits, reusing the existing resolver; no new checker arm. cstage flattens at parseprimary struct-lit handoff; wwstage (no token peek) folds dots in parsepostfix and normalizes there, guarding numeric tuple components and staying in the postfix loop so trailing ops still chain. Both stages emit identical N_STRUCTLIT(N_TNAME). Prereq for qualifying wcc syntax refs (#75).
This commit is contained in:
2026-06-16 22:20:42 +09:00
parent 697e413113
commit 01b657a7ff
6 changed files with 656 additions and 1 deletions

View File

@@ -7249,6 +7249,31 @@ fn parsearglist(p: *parser, closekind: tkind, headout: **node) void = {
*headout = head;
};
// #76: flatten a qualified `pkg.Type` N_DOT chain into its source-order
// dotted string ("a.b.C"), byte-identical to cstage parsetype's aprintf
// accumulation (cmd/wcc/parse.c) and to flattendotstr in parse.c.
fn flattendotstr(n: *node) str = {
if (n.kind == nkind.N_IDENT) { return n.str; };
return joindotted(flattendotstr(n.lhs), n.str);
};
// #76: an N_DOT chain qualifies as a type path only when it is rooted at
// a bare IDENT and every component is an identifier. cstage folds only
// peek==TK_IDENT dots in parseprimary, so its chain is inherently
// all-ident; ww folds ALL dots (incl tuple `t.0`) in parsepostfix, so we
// guard out numeric-first-byte components here to restore exact symmetry.
fn isqualpath(n: *node) bool = {
if (n.kind != nkind.N_DOT) { return false; };
let cur: *node = n;
for (cur.kind == nkind.N_DOT) {
if (cur.str.len < 1) { return false; };
let c0: u8 = cur.str[0];
if (c0 >= '0') { if (c0 <= '9') { return false; }; };
cur = cur.lhs;
};
return cur.kind == nkind.N_IDENT;
};
fn parsepostfix(p: *parser, lhs: *node) *node = {
let cur: *node = lhs;
for (true) {
@@ -7334,6 +7359,51 @@ fn parsepostfix(p: *parser, lhs: *node) *node = {
cur = n;
continue;
};
// #76: qualified struct literal `pkg.Type{...}`. cstage folds
// the dotted chain in parseprimary (peek) and hits `{` there;
// the ww parser has no token-peek and folds dots HERE, so the
// struct-lit handoff lives here too. Fire only on an all-ident
// N_DOT chain (isqualpath); bare `Foo{` stays in parseprimary.
// Build the literal IN-LOOP and `continue` (req a) so trailing
// ops (`.f`, `[i]`, `()`, `as T`) still apply, matching cstage's
// return-to-outer-parsepostfix. The flattened N_TNAME (req c:
// source order) lets the checker's existing N_TNAME arm resolve.
if (p.curkind == tkind.TK_LBRACE) {
if (isqualpath(cur)) {
let tn: *node = newnode(nkind.N_TNAME, pf, pl, pc);
tn.str = flattendotstr(cur);
advance(p);
let s: *node = newnode(nkind.N_STRUCTLIT, pf, pl, pc);
s.lhs = tn;
let head: *node = nil;
let tail: *node = nil;
for (p.curkind != tkind.TK_RBRACE) {
if (p.curkind == tkind.TK_EOF) { break; };
if (p.curkind == tkind.TK_ELLIPSIS) {
advance(p);
s.op = tkind.TK_ELLIPSIS;
break;
};
let fpf: str = p.curfile;
let fpl: i32 = p.curline;
let fpc: i32 = p.curcol;
let id: str;
expectident(p, &id);
expecttok(p, tkind.TK_ASSIGN, "expected '=' in struct lit field");
let v: *node = parseexpr(p);
let f: *node = newnode(nkind.N_FIELD, fpf, fpl, fpc);
f.str = id;
f.lhs = v;
if (head == nil) { head = f; tail = f; }
else { tail.next = f; tail = f; };
if (!accepttok(p, tkind.TK_COMMA)) { break; };
};
expecttok(p, tkind.TK_RBRACE, "expected '}' after struct literal");
s.list = head;
cur = s;
continue;
};
};
if (p.curkind == tkind.TK_COLON) {
if (p.nocast != 0) {
return cur;

View File

@@ -7249,6 +7249,31 @@ fn parsearglist(p: *parser, closekind: tkind, headout: **node) void = {
*headout = head;
};
// #76: flatten a qualified `pkg.Type` N_DOT chain into its source-order
// dotted string ("a.b.C"), byte-identical to cstage parsetype's aprintf
// accumulation (cmd/wcc/parse.c) and to flattendotstr in parse.c.
fn flattendotstr(n: *node) str = {
if (n.kind == nkind.N_IDENT) { return n.str; };
return joindotted(flattendotstr(n.lhs), n.str);
};
// #76: an N_DOT chain qualifies as a type path only when it is rooted at
// a bare IDENT and every component is an identifier. cstage folds only
// peek==TK_IDENT dots in parseprimary, so its chain is inherently
// all-ident; ww folds ALL dots (incl tuple `t.0`) in parsepostfix, so we
// guard out numeric-first-byte components here to restore exact symmetry.
fn isqualpath(n: *node) bool = {
if (n.kind != nkind.N_DOT) { return false; };
let cur: *node = n;
for (cur.kind == nkind.N_DOT) {
if (cur.str.len < 1) { return false; };
let c0: u8 = cur.str[0];
if (c0 >= '0') { if (c0 <= '9') { return false; }; };
cur = cur.lhs;
};
return cur.kind == nkind.N_IDENT;
};
fn parsepostfix(p: *parser, lhs: *node) *node = {
let cur: *node = lhs;
for (true) {
@@ -7334,6 +7359,51 @@ fn parsepostfix(p: *parser, lhs: *node) *node = {
cur = n;
continue;
};
// #76: qualified struct literal `pkg.Type{...}`. cstage folds
// the dotted chain in parseprimary (peek) and hits `{` there;
// the ww parser has no token-peek and folds dots HERE, so the
// struct-lit handoff lives here too. Fire only on an all-ident
// N_DOT chain (isqualpath); bare `Foo{` stays in parseprimary.
// Build the literal IN-LOOP and `continue` (req a) so trailing
// ops (`.f`, `[i]`, `()`, `as T`) still apply, matching cstage's
// return-to-outer-parsepostfix. The flattened N_TNAME (req c:
// source order) lets the checker's existing N_TNAME arm resolve.
if (p.curkind == tkind.TK_LBRACE) {
if (isqualpath(cur)) {
let tn: *node = newnode(nkind.N_TNAME, pf, pl, pc);
tn.str = flattendotstr(cur);
advance(p);
let s: *node = newnode(nkind.N_STRUCTLIT, pf, pl, pc);
s.lhs = tn;
let head: *node = nil;
let tail: *node = nil;
for (p.curkind != tkind.TK_RBRACE) {
if (p.curkind == tkind.TK_EOF) { break; };
if (p.curkind == tkind.TK_ELLIPSIS) {
advance(p);
s.op = tkind.TK_ELLIPSIS;
break;
};
let fpf: str = p.curfile;
let fpl: i32 = p.curline;
let fpc: i32 = p.curcol;
let id: str;
expectident(p, &id);
expecttok(p, tkind.TK_ASSIGN, "expected '=' in struct lit field");
let v: *node = parseexpr(p);
let f: *node = newnode(nkind.N_FIELD, fpf, fpl, fpc);
f.str = id;
f.lhs = v;
if (head == nil) { head = f; tail = f; }
else { tail.next = f; tail = f; };
if (!accepttok(p, tkind.TK_COMMA)) { break; };
};
expecttok(p, tkind.TK_RBRACE, "expected '}' after struct literal");
s.list = head;
cur = s;
continue;
};
};
if (p.curkind == tkind.TK_COLON) {
if (p.nocast != 0) {
return cur;