Bläddra i källkod

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

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>
ctype_lab 1 månad sedan
förälder
incheckning
85e9ac6762

+ 10 - 4
codegraph-kernel/src/ccpp/mod.rs

@@ -812,7 +812,10 @@ impl<'t> Walker<'t> {
         } else if self.variant == Variant::Cpp && kind == "class_specifier" {
             self.extract_class(node);
             skip_children = true;
-        } else if kind == "struct_specifier" {
+        } else if matches!(kind, "struct_specifier" | "union_specifier") {
+            // `union_specifier` mirrors structTypes on the TS side: a named
+            // `union U { … };` is a definition, extracted with kind "struct"
+            // (NodeKind has no "union"). Bodiless stays a forward declaration.
             self.extract_struct(node);
             skip_children = true;
         } else if kind == "enum_specifier" {
@@ -1041,7 +1044,9 @@ impl<'t> Walker<'t> {
                 resolved = Some("enum");
                 break;
             }
-            if child.kind() == "struct_specifier" && child.child_by_field_name("body").is_some() {
+            if matches!(child.kind(), "struct_specifier" | "union_specifier")
+                && child.child_by_field_name("body").is_some()
+            {
                 resolved = Some("struct");
                 break;
             }
@@ -1059,7 +1064,8 @@ impl<'t> Walker<'t> {
             self.stack.push(Scope { row, kind: "struct", name });
             let type_child = node
                 .child_by_field_name("type")
-                .or_else(|| self.find_child_by_kind(node, "struct_specifier"));
+                .or_else(|| self.find_child_by_kind(node, "struct_specifier"))
+                .or_else(|| self.find_child_by_kind(node, "union_specifier"));
             if let Some(tc) = type_child {
                 self.extract_inheritance(tc, row);
                 let body = tc.child_by_field_name("body").unwrap_or(tc);
@@ -1556,7 +1562,7 @@ impl<'t> Walker<'t> {
             self.extract_class(node);
             return;
         }
-        if kind == "struct_specifier" {
+        if matches!(kind, "struct_specifier" | "union_specifier") {
             self.extract_struct(node);
             return;
         }

+ 4 - 2
codegraph-kernel/src/rustlang.rs

@@ -446,7 +446,9 @@ impl<'t> Walker<'t> {
         } else if kind == "trait_item" {
             self.extract_interface(node);
             skip_children = true;
-        } else if kind == "struct_item" {
+        } else if matches!(kind, "struct_item" | "union_item") {
+            // `union_item` mirrors structTypes on the TS side: same `body:`
+            // field, same extractor, kind "struct" (NodeKind has no "union").
             self.extract_struct(node);
             skip_children = true;
         } else if kind == "enum_item" {
@@ -1130,7 +1132,7 @@ impl<'t> Walker<'t> {
         }
 
         // Structural nodes inside bodies.
-        if kind == "struct_item" {
+        if matches!(kind, "struct_item" | "union_item") {
             self.extract_struct(node);
             return;
         }

+ 24 - 6
src/extraction/languages/c-cpp.ts

@@ -186,7 +186,11 @@ export const cExtractor: LanguageExtractor = {
   classTypes: [],
   methodTypes: [],
   interfaceTypes: [],
-  structTypes: ['struct_specifier'],
+  // `union U { … };` is a type DEFINITION, same as `struct U { … };` — it
+  // declares a named type whose members other code refers to. Extracted with
+  // kind `struct` because NodeKind has no `union`; a bodiless `union U;` is a
+  // forward declaration and still falls out via extractStruct's body guard.
+  structTypes: ['struct_specifier', 'union_specifier'],
   enumTypes: ['enum_specifier'],
   enumMemberTypes: ['enumerator'],
   typeAliasTypes: ['type_definition'], // typedef
@@ -207,12 +211,18 @@ export const cExtractor: LanguageExtractor = {
   resolveTypeAliasKind: (node, _source) => {
     // C typedef: `typedef enum { ... } name;` or `typedef struct { ... } name;`
     // The inner enum_specifier/struct_specifier is anonymous, but we want the typedef name
-    // to become the enum/struct node name.
+    // to become the enum/struct node name. `typedef union { ... } name;` takes the
+    // same route — otherwise the union body would mint a second, `<anonymous>` node
+    // beside the alias.
     for (let i = 0; i < node.namedChildCount; i++) {
       const child = node.namedChild(i);
       if (!child) continue;
       if (child.type === 'enum_specifier' && getChildByField(child, 'body')) return 'enum';
-      if (child.type === 'struct_specifier' && getChildByField(child, 'body')) return 'struct';
+      if (
+        (child.type === 'struct_specifier' || child.type === 'union_specifier') &&
+        getChildByField(child, 'body')
+      )
+        return 'struct';
     }
     return undefined;
   },
@@ -1551,7 +1561,10 @@ export const cppExtractor: LanguageExtractor = {
   skipBodilessClass: true,
   methodTypes: ['function_definition'],
   interfaceTypes: [],
-  structTypes: ['struct_specifier'],
+  // See the C extractor: a named `union U { … };` is a definition, not an
+  // alias. C++ unions additionally carry member functions, which extract
+  // through the same body walk as a struct's.
+  structTypes: ['struct_specifier', 'union_specifier'],
   enumTypes: ['enum_specifier'],
   enumMemberTypes: ['enumerator'],
   typeAliasTypes: ['type_definition', 'alias_declaration'], // typedef and using
@@ -1581,12 +1594,17 @@ export const cppExtractor: LanguageExtractor = {
     return undefined;
   },
   resolveTypeAliasKind: (node, _source) => {
-    // C++ typedef: `typedef enum { ... } name;` or `typedef struct { ... } name;`
+    // C++ typedef: `typedef enum { ... } name;`, `typedef struct { ... } name;`,
+    // or `typedef union { ... } name;` — see the C extractor.
     for (let i = 0; i < node.namedChildCount; i++) {
       const child = node.namedChild(i);
       if (!child) continue;
       if (child.type === 'enum_specifier' && getChildByField(child, 'body')) return 'enum';
-      if (child.type === 'struct_specifier' && getChildByField(child, 'body')) return 'struct';
+      if (
+        (child.type === 'struct_specifier' || child.type === 'union_specifier') &&
+        getChildByField(child, 'body')
+      )
+        return 'struct';
     }
     return undefined;
   },

+ 9 - 2
src/extraction/languages/objc.ts

@@ -102,7 +102,9 @@ export const objcExtractor: LanguageExtractor = {
   methodTypes: ['method_definition'],
   interfaceTypes: ['protocol_declaration'],
   interfaceKind: 'protocol',
-  structTypes: ['struct_specifier'],
+  // Objective-C is a C superset: `union U { … };` is a definition, same as in
+  // the C extractor.
+  structTypes: ['struct_specifier', 'union_specifier'],
   enumTypes: ['enum_specifier'],
   enumMemberTypes: ['enumerator'],
   typeAliasTypes: ['type_definition'],
@@ -128,7 +130,12 @@ export const objcExtractor: LanguageExtractor = {
       const child = node.namedChild(i);
       if (!child) continue;
       if (child.type === 'enum_specifier' && getChildByField(child, 'body')) return 'enum';
-      if (child.type === 'struct_specifier' && getChildByField(child, 'body')) return 'struct';
+      // `typedef union { … } name;` resolves like `typedef struct` — see the C extractor.
+      if (
+        (child.type === 'struct_specifier' || child.type === 'union_specifier') &&
+        getChildByField(child, 'body')
+      )
+        return 'struct';
     }
     return undefined;
   },

+ 4 - 1
src/extraction/languages/rust.ts

@@ -41,7 +41,10 @@ export const rustExtractor: LanguageExtractor = {
   classTypes: [], // Rust has impl blocks
   methodTypes: ['function_item', 'function_signature_item'],
   interfaceTypes: ['trait_item'],
-  structTypes: ['struct_item'],
+  // `union U { … }` is a definition like `struct U { … }` — same `body:`
+  // (`field_declaration_list`) and the same `impl Trait for U` attachment
+  // point. Extracted with kind `struct` because NodeKind has no `union`.
+  structTypes: ['struct_item', 'union_item'],
   enumTypes: ['enum_item'],
   enumMemberTypes: ['enum_variant'],
   typeAliasTypes: ['type_item'], // Rust type aliases