fix(rust): index unit structs — bodiless is a definition, not a forward decl (#1800)

Land upstream PR #1514 for issue #1513 by cherry-picking
ctype_lab's a94c9dc94eee348bf63c7e2dd567c0b43577678a.

Keep allowBodilessStruct as a Rust-only opt-in, with matching behavior in
the wasm/TypeScript walker and native kernel. Create the node before
checking for a body and walk members only when present. Resolve against
main's shared struct/union walker while retaining its stack guard, kinds,
fields, and existing impl-receiver fixes.

Verified FAIL to PASS on Linux x64 with Node 22.19.0 after rebuilding via
tsc, copy-assets, and build:kernel. The identical fixture on main 7b339373
had two structs and two implements edges; fresh wasm (CODEGRAPH_KERNEL=0)
and native indexes now have three of each. UnitStruct and its Greet
implements edge are recovered; tuple and brace structs remain intact.
The native run loaded the rebuilt kernel and completed without fallback.

Focused extraction.test.ts and kernel-rustlang-parity.test.ts runs:
645 tests passed with the kernel disabled, and 645 with it enabled;
all three parity tests ran in each configuration, with no skips.

(cherry picked from commit a94c9dc94eee348bf63c7e2dd567c0b43577678a)

Co-authored-by: ctype_lab <cksgud1226@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-09-08 16:53:08 -05:00
committed by GitHub
co-authored by ctype_lab Claude Opus 5
parent 7b339373b4
commit 71d049cd28
8 changed files with 68 additions and 8 deletions
+9 -4
View File
@@ -20,8 +20,7 @@
//! kind is always `variable`, no signature, and EVERY direct `identifier`
//! child mints a node (`const MAX: u32 = OTHER;` → two nodes, `MAX` + the
//! phantom `OTHER`). Top-level initializer values are never body-walked.
//! - Unit structs (`struct Unit;`, no body field) mint NO node; `mod_item`
//! mints no module node and adds no QN prefix.
//! - `mod_item` mints no module node and adds no QN prefix.
//! - Chained-call re-encode is scoped_identifier-gated (`Foo::new().bar()` →
//! `Foo::new().bar`); a call through a field of the enclosing type keeps
//! the owner-field shape (`self.inner.run()` → `self.inner.run`, #1585);
@@ -590,10 +589,12 @@ impl<'t> Walker<'t> {
self.stack.pop();
}
/// Extract a Rust struct or union with a body; unit structs remain skipped.
/// Extract a Rust struct or union — the body field is OPTIONAL. A unit
/// struct (`struct U;`) has no body and is still a complete definition,
/// so it mints a node with no members; tuple structs' ordered_field_declaration_list
/// is a body. Mirrors the TS reference's `allowBodilessStruct`.
fn extract_aggregate(&mut self, node: Node<'t>, kind: &'static str) {
stack_guard!();
let Some(body) = node.child_by_field_name("body") else { return };
let name = self.extract_name(node);
let extra = Extra {
docstring: preceding_docstring(node, self.src),
@@ -603,6 +604,10 @@ impl<'t> Walker<'t> {
let Some(row) = self.create_node(kind, &name, node, extra) else { return };
self.extract_inheritance(node, row);
// Unit structs have no body to walk — the node itself is the whole
// definition.
let Some(body) = node.child_by_field_name("body") else { return };
self.stack.push(Scope { row, kind, name });
for i in 0..body.named_child_count() {
if let Some(c) = body.named_child(i) {