Skip to content

fix(c,cpp,objc,rust): index union declarations - #1516

Open
ctype-lab wants to merge 8 commits into
colbymchenry:mainfrom
ctype-lab:fix/union-declarations-not-indexed
Open

fix(c,cpp,objc,rust): index union declarations#1516
ctype-lab wants to merge 8 commits into
colbymchenry:mainfrom
ctype-lab:fix/union-declarations-not-indexed

Conversation

@ctype-lab

@ctype-lab ctype-lab commented Aug 6, 2026

Copy link
Copy Markdown

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 structTypes and emitting
kind: '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 two
separately shipped fixes.

State Union declaration in the graph
Upstream main absent
Early, unmerged commits in this PR present as struct
This PR head present as first-class union

The 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

union is appended to the TypeScript and native-kernel node-kind tables. The
append-only placement leaves all existing wire-code values unchanged. C/C++,
Objective-C, and Rust register union syntax separately from structTypes; the
shared extractors walk both aggregate forms through the same logic while
creating the explicit kind supplied by the registration.

structTypes: ['struct_specifier'],
unionTypes: ['union_specifier'],

The native C/C++ and Rust walkers mirror this dispatch. Union members, C++
member methods, Rust impl attachment, containment, and inheritance handling
continue to use the existing aggregate walk, but the node and its scope retain
kind: 'union'.

typedef union { ... } word_t keeps the typedef name and produces one named
union 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:

  • class-like scopes and method-owner lookup
  • import/type resolution and search filtering
  • context and MCP definition-kind lists
  • name-match ranking for type instantiation
  • C function-pointer dispatch-table synthesis
  • native-kernel and wasm parity tables
  • resolver promotion from calls to instantiates

The resolver coverage closes a C++ case the structural extraction tests do not
exercise: Packet() value-initializes a union. Extraction initially records a
call-shaped reference; after Packet resolves to a union, that edge must be
instantiates, as it already is for classes and structs. An instantiates
reference 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:

  • C function-pointer dispatch through a named union field
  • inline union dispatch tables with macro-built entries
  • union dispatch tables declared through an object-macro type alias
  • default context retrieval of a union definition
  • an imported C++ union static-member call (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

pub union Reg { pub raw: u32, pub bits: [u8; 4] }
pub struct Ctl { pub n: u32 }
pub trait Describe { fn describe(&self) -> String; }
impl Describe for Reg { fn describe(&self) -> String { "reg".into() } }
impl Describe for Ctl { fn describe(&self) -> String { "ctl".into() } }
union Packet { unsigned int raw; };
void initialize() { Packet(); }
Query/result Upstream main Final PR head
Rust Reg node absent union:Reg
impl Describe for Reg attachment absent attached to union:Reg
C++ Packet() edge no union target exists instantiates -> union:Packet
Union kind available to graph consumers no yes

Compatibility

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 upstream main has no
union node to migrate.

Tests

Regression coverage includes:

  • Rust union extraction, impl Trait for Union, and method containment
  • named C unions and skipped bodiless forward declarations
  • typedef union { ... } name_t without an anonymous duplicate
  • C++ union member ownership
  • native-kernel to wasm parity fixtures for C/C++ and Rust
  • C++ union value initialization promoted to instantiates
  • union-over-function selection for instantiates references
  • named, inline macro-built, and object-macro-alias C union dispatch tables
  • default context retrieval of a union definition
  • imported C++ union static-member resolution

Validated on the final PR head:

npm run build
npm run build:kernel
npm test

170 test files, 2,932 passed, 6 platform/environment-gated skips

ctype-lab and others added 7 commits August 6, 2026 15:46
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Union declarations are not indexed in C, C++, Objective-C, or Rust

1 participant