parse/cgen: tuple-destructure assign + extra type-inference holes

Closes the gaps that kept ww_ww from rebuilding the wwstage byte-
for-byte. The pre-existing parser silently produced a broken AST
on `a, b = fn();` (a no-op exprstmt + a single assign that lost
the second tuple slot); the cgen leaned on a handful of cases
that the surface type-walker didn't yet cover, so 6a/6l/ww built
through wwstage drifted by a handful of bytes per file.

Parser:

- Multi-assign in parsestmt mirrors cmd/wwc/parse.c:1015-1031. If
  parseexpr is followed by `,`, switch into N_MASSIGN: collect
  the chained lvalues with parsebin(parseunary, 1) so they don't
  eat the trailing `=`, then absorb `= rhs;` and emit the node.

Cgen:

- N_MASSIGN handler stores AX into l0's slot, pops DX into l1's
  slot. Same shape as cmd/6c/cgen.c:2424-2440. Lvalues beyond
  two are dropped (C drops them too).
- N_INDEX added to node_isunsigned: `p[i]` where p is *u8/[]u8/
  [N]u8 now flags unsigned, so `p[i] >= 48u8` emits JAE instead
  of JGE. Bit 6a's parsenum.
- node_isstr's N_DOT branch now handles chained dots via
  dot_inner_struct_ptr, so `p.to.asym` (Adr.asym is str) flags
  as str and push_args_rev pushes both halves.
- index_base_esz returns 1 for str-typed struct fields. Was
  defaulting to 8, so `node.s[i]` scaled by 8 and used MOVQ
  instead of MOVZBQ. Bit ww/visit_seen.
- N_LET with no initializer zero-inits the slot when the
  underlying type is an 8-byte primitive (pointer, fn-ptr,
  i64/u64, ...) — matches cmd/6c/cgen.c:2181. Structs,
  strings, slices, etc. are left for per-field writes, even
  when their slot rounds up to 8 in scan_locals. New helper
  type_is_8byte_primitive walks the type AST to make the
  same call C's checker would.

Tests stay 17/17; new self-rebuild test (995) lands in the next
commit and gates on these fixes.
This commit is contained in:
2026-05-11 11:40:59 +09:00
parent a52a32e0ba
commit af8836cc8e
2 changed files with 159 additions and 17 deletions

View File

@@ -744,9 +744,31 @@ fn parsestmt(p: *parser) *node = {
expect_tok(p, TK_SEMI, "expected ';' after continue");
return newnode(p.a, N_CONTINUE, pf, pl, pc);
};
// expression statement
// expression statement, or tuple-destructure multi-assign:
// a, b = expr;
// Mirrors cmd/wwc/parse.c:1015-1031. We parse the first lvalue
// with parseexpr (matches the C side); subsequent lvalues go
// through parsebin(parseunary, 1) so the `=` stays for us to
// consume — parseexpr would absorb it.
let e: *node = parseexpr(p);
if (p.cur_kind == TK_COMMA) {
let m: *node = newnode(p.a, N_MASSIGN, pf, pl, pc);
let head: *node = e;
let tail: *node = e;
for (p.cur_kind == TK_COMMA) {
advance(p);
let lv: *node = parsebin(p, parseunary(p), 1);
tail.next = lv;
tail = lv;
};
expect_tok(p, TK_ASSIGN, "expected '=' after multi-assign lvalues");
m.rhs = parseexpr(p);
m.list = head;
expect_tok(p, TK_SEMI, "expected ';' after multi-assign");
return m;
};
let n: *node = newnode(p.a, N_EXPRSTMT, pf, pl, pc);
n.lhs = parseexpr(p);
n.lhs = e;
expect_tok(p, TK_SEMI, "expected ';' after expression statement");
return n;
};