fix(c,cpp,objc,rust): index union declarations - #1516
Open
ctype-lab wants to merge 8 commits into
Open
Conversation
A `union` declaration produced no symbol at all in any of the four
languages that have one. The type never entered the graph, and neither
did anything attached to it — in Rust every `impl Trait for MyUnion`
lost its edge, and the impl's methods were left with a qualifiedName
pointing at a type the graph did not contain.
`union_specifier` / `union_item` were absent from the extraction layer
entirely: no `<x>Types` list on the TS side, no dispatch branch in
either kernel walker.
They join `structTypes` (kind `struct` — NodeKind has no `union`), which
is the extension point the table-driven extractors already provide. The
body guard in extractStruct is untouched, so a bodiless `union U;` stays
a forward declaration and is still skipped, exactly like `struct U;`.
`resolveTypeAliasKind` accepts `union_specifier` too, so
`typedef union { … } N;` takes the typedef's name the way
`typedef struct { … } N;` already did. Without it the anonymous union
body would mint a second `<anonymous>` node beside the alias.
Both walkers change together so kernel<->wasm parity holds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four cases in extraction.test.ts: a Rust union carrying an `impl Trait for` edge and owning the impl's method; a named C union alongside a forward declaration that must NOT mint a node; a `typedef union` taking the typedef name with no `<anonymous>` twin; a C++ union with a member function. Verified they fail without the fix on BOTH extraction paths — the wasm walker via CODEGRAPH_KERNEL=0 and the kernel with a staged build. torture.c / torture.rs gain the same shapes. The parity gate compares the two walkers rather than a snapshot, so the fixtures do not detect the bug on their own — they pin that the fix stays SYMMETRIC. The regression tests above are what pin that it is present. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The two kernel port checklists record the extractor configs as surveyed at porting time; their structTypes lines are marked superseded rather than rewritten, so the surveys stay readable as history. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1515.
Summary
On upstream main (revision d6d1728, 2026-08-06), C, C++, Objective-C, and
Rust union declarations do not enter the graph at all. Tree-sitter gives
struct and union declarations different AST node types: in C-family languages,
struct declarations are named "struct_specifier" and unions are named
"union_specifier"; in Rust, they are "struct_item" and "union_item". The
extractor registrations and native C/C++ and Rust walkers recognize only the
struct forms, so extraction never starts for a union. Consequently a union
cannot be searched, referenced, or own members, and Rust impl Trait for Union
cannot attach to a type node.
The first three commits in this PR were an unmerged intermediate repair:
they made unions extract by routing them through
structTypesand emittingkind: 'struct'. That representation has never been merged or released.Before review, the later commits replace it in the same PR with the correct
public model:
kind: 'union'. Review this PR as its final head, not as twoseparately shipped fixes.
mainstructunionThe final change is therefore not a follow-up to a merged fix. It resolves
#1515 directly while preserving the semantic distinction that the temporary
representation would have lost.
What Changes
unionis appended to the TypeScript and native-kernel node-kind tables. Theappend-only placement leaves all existing wire-code values unchanged. C/C++,
Objective-C, and Rust register union syntax separately from
structTypes; theshared extractors walk both aggregate forms through the same logic while
creating the explicit kind supplied by the registration.
The native C/C++ and Rust walkers mirror this dispatch. Union members, C++
member methods, Rust
implattachment, containment, and inheritance handlingcontinue to use the existing aggregate walk, but the node and its scope retain
kind: 'union'.typedef union { ... } word_tkeeps the typedef name and produces one namedunion node, rather than an anonymous duplicate. A bodiless C/C++/Objective-C
declaration such as
union U;remains a forward declaration and is skipped;this PR changes node identity, not the forward-declaration rule.
Downstream Audit
A separate node kind must be recognized wherever CodeGraph treats a node as a
type or class-like container. The final PR head updates:
callstoinstantiatesThe resolver coverage closes a C++ case the structural extraction tests do not
exercise:
Packet()value-initializes a union. Extraction initially records acall-shaped reference; after
Packetresolves to a union, that edge must beinstantiates, as it already is for classes and structs. Aninstantiatesreference also prefers a union definition over a same-named function.
Downstream Follow-up
Additional regression coverage verifies consumers that depend on unions being
a distinct container kind:
Ops::run())The function-pointer cases cover both the TypeScript and native-kernel
scanners, so union-shaped dispatch tables remain visible after extraction
rather than being limited to struct-shaped tables.
Reproduction
mainRegnodeunion:Regimpl Describe for Regattachmentunion:RegPacket()edgeinstantiates -> union:PacketCompatibility
The wire table change is append-only, so codes for existing node kinds do not
move. Existing indexes must nevertheless be rebuilt after upgrade: any union
node produced by this PR is stored as
union, while upstreammainhas nounion node to migrate.
Tests
Regression coverage includes:
impl Trait for Union, and method containmenttypedef union { ... } name_twithout an anonymous duplicateinstantiatesinstantiatesreferencesValidated on the final PR head: