Skip to content

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

Description

@ctype-lab

In CodeGraph's C, C++, Objective-C, and Rust extractors, a union declaration
produces no symbol at all. The type is absent from the graph, and so is
everything attached to it.

union_specifier (C/C++/ObjC) and union_item (Rust) appear nowhere in the
extraction layer — no <x>Types list on the TS side, no dispatch branch in
either kernel walker:

$ grep -rn 'union_specifier\|union_item' src/ codegraph-kernel/src/
(no output)

Structs and enums beside them extract normally. The grammars parse unions fine,
so this is in symbol extraction, not the grammar.

Environment

  • codegraph: 1.5.0 (npm), reproduced again on main @ d6d1728
  • Backend: node:sqlite (WAL)
  • Platform: Linux x64
  • C, C++, and Rust reproduce on both extraction paths: the native kernel and
    the wasm/TS walker (CODEGRAPH_KERNEL=0). Objective-C continues to use its
    existing wasm/TS extraction path.

Repro

src/lib.rs:

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() } }

pub fn dump(r: &Reg) -> String { r.describe() }

src/mem.c:

union Word { int i; char b[4]; };
struct Ctl  { int x; };
codegraph init .
sqlite3 .codegraph/codegraph.db "SELECT kind, name FROM nodes WHERE kind='struct';"

Expected

Reg, Ctl, Word, Ctl — and two implements edges.

Actual

The two unions are missing, and with them Reg's realization of Describe:

struct  Ctl        (lib.rs)
struct  Ctl        (mem.c)
implements:  Ctl -> Describe          # Reg -> Describe never forms

Impact

Three separate losses, and the second is the one that hurts.

1. The type is gone. It cannot be searched for, does not appear in
codegraph_explore, and any function taking union Reg * has no type to point
at.

2. Its methods become orphans that point at a type which does not exist.
This is worse than a plain omission, because the graph ends up internally
inconsistent rather than merely incomplete:

Describe::describe   <-  contains from  trait:Describe            ✅
Ctl::describe        <-  contains from  file:lib.rs + struct:Ctl  ✅
Reg::describe        <-  contains from  file:lib.rs               ❌   its qualifiedName says "Reg"

Asking what Reg::describe belongs to gets the file. The Reg in its own
qualified name resolves to nothing.

3. Trait-dispatch synthesis silently under-reports. The synthesized
(provenance='heuristic') fan-out is Describe::describe -> Ctl::describe and
nothing else. A union implementor is not a candidate, because it is not a node —
so "who implements this trait" is quietly wrong rather than visibly incomplete.

In C the loss splits by form:

form today
union U { … }; no node at all
typedef union { … } U; survives as type_alias:U — name kept, kind wrong, no members

Unions are load-bearing in C: tagged variants, register overlays, protocol
frames, and — relevant to this repo — function-pointer dispatch tables, which
c-fnptr-synthesizer.ts finds by scanning nodes with kind === 'struct'. A
union-shaped dispatch table is invisible to it.

Cause

extractStruct is only reached for node types registered in a language's
structTypes, and no language lists its union node type. Same in both kernel
walkers, where the dispatch is a hardcoded kind == "struct_specifier" /
kind == "struct_item" branch.

I have a fix ready and will open a PR referencing this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions