mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-21 00:38:18 +08:00
fix(go): a struct links the types its fields are declared with (#2655)
A Go struct referenced nothing through its fields: etcd's `type
PutResponse struct { Header *ResponseHeader }` or gin's `type Context
struct { handlers HandlersChain }` had no `references` edge to those
types, so impact on `ResponseHeader` or `HandlersChain` missed every
struct holding one, and their users. Main had 0 such edges on gin, etcd,
prometheus and kubernetes; only embedded fields were read (`extends`).
TS extractor and Rust kernel, mirrored:
- goStructFieldTypeNames / struct_field_type_names walk each named
field's type and push a `references` ref per type name from the
struct, on the name. Fields mint no nodes: impact never climbs
`contains` upward (#536), so a field node would stop impact short of
the struct; Dart attributes field types to the class too (#2327).
- #2441's alias walk is factored into collectGoTypeNames /
collect_type_names + goDeclaredNames / declared_names, so struct
fields skip what aliases skip: predeclared types and, written bare,
the declaration's own type parameters and its own name (no self-edge,
no failed row for `next *node`).
- An embedded field stays `extends`; an embedded generic's type
arguments are references (client-go's
`listers.ResourceIndexer[*v1.Pod]`: 651 such fields in kubernetes).
Resolver: gateTargetKind's unknown-qualifier rule (#2417) now covers
every Go type declaration (GO_TYPE_KINDS). Without it, a field
`op clientv3.Op` under an unaliased go.etcd.io/etcd/client/v3 import
bound to the same file's own `Op`.
Explore: a `references` edge whose ends are both type declarations no
longer makes either end connected to an entry. With the new edges alone
the #1500 CG-10 gate failed: payroll's 8-line `RunResult` (holding a
`[]Payslip`) became connected and displaced the 51-line
runPayrollCycleAll body. The rule alone changes no rendered symbol on
gin, etcd, prometheus, kubernetes, bloc, bulletproof or refine-examples.
Validation (main de3eb71d, site-keyed A/B, every added edge judged
against real package clauses): gin +41, etcd +1006, prometheus +1637,
kubernetes +20203, all correct; 0 dropped/retargeted/metadata-only; no
node changes or self-loops; kernel == wasm. Index time within noise,
DB +0.4-1.4%.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+70
-24
@@ -581,6 +581,13 @@ impl<'t> Walker<'t> {
|
||||
// Struct embedding → extends (field_declaration without a
|
||||
// field_identifier), reached via the inheritance recursion.
|
||||
self.extract_inheritance(type_child, row);
|
||||
// The types its fields are declared with are the struct's
|
||||
// references: Go fields mint no nodes.
|
||||
let references = edge_kind_index("references").unwrap();
|
||||
for ty in self.struct_field_type_names(node) {
|
||||
let text = self.text(ty).to_string();
|
||||
self.push_ref_at(row, &text, references, ty);
|
||||
}
|
||||
let body = type_child.child_by_field_name("body").unwrap_or(type_child);
|
||||
for i in 0..body.named_child_count() {
|
||||
if let Some(c) = body.named_child(i) {
|
||||
@@ -897,6 +904,27 @@ impl<'t> Walker<'t> {
|
||||
}
|
||||
}
|
||||
|
||||
/// goDeclaredNames (languages/go.ts): what a type name written bare
|
||||
/// inside a type declaration can mean without naming another type — the
|
||||
/// declaration's own type parameters, and its own name, which in a
|
||||
/// recursive type is the declaration itself.
|
||||
fn declared_names(&self, decl: Node<'t>) -> HashSet<&'t str> {
|
||||
let mut names = HashSet::new();
|
||||
if let Some(name) = decl.child_by_field_name("name") {
|
||||
names.insert(self.text(name));
|
||||
}
|
||||
if let Some(list) = decl.child_by_field_name("type_parameters") {
|
||||
for param in (0..list.named_child_count()).filter_map(|i| list.named_child(i)) {
|
||||
for c in (0..param.named_child_count()).filter_map(|j| param.named_child(j)) {
|
||||
if c.kind() == "identifier" {
|
||||
names.insert(self.text(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
names
|
||||
}
|
||||
|
||||
/// goAliasTypeNames (languages/go.ts): the name nodes of the types an
|
||||
/// alias's or a defined type's `type` field names, in source order, but
|
||||
/// its own type parameters, the predeclared types and its own name written
|
||||
@@ -905,36 +933,54 @@ impl<'t> Walker<'t> {
|
||||
/// around an error, never reaches the kernel (its file defers to wasm).
|
||||
fn alias_type_names(&self, node: Node<'t>) -> Vec<Node<'t>> {
|
||||
let mut names = Vec::new();
|
||||
let Some(ty) = node.child_by_field_name("type") else { return names };
|
||||
let own = node.child_by_field_name("name").map(|n| self.text(n));
|
||||
let mut params: HashSet<&str> = HashSet::new();
|
||||
if let Some(list) = node.child_by_field_name("type_parameters") {
|
||||
for decl in (0..list.named_child_count()).filter_map(|i| list.named_child(i)) {
|
||||
for c in (0..decl.named_child_count()).filter_map(|j| decl.named_child(j)) {
|
||||
if c.kind() == "identifier" {
|
||||
params.insert(self.text(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(ty) = node.child_by_field_name("type") {
|
||||
self.collect_type_names(ty, &self.declared_names(node), false, &mut names);
|
||||
}
|
||||
self.collect_alias_type_names(ty, ¶ms, own, false, &mut names);
|
||||
names
|
||||
}
|
||||
|
||||
/// `qualified`: the name of a `pkg.Name`, which is that package's
|
||||
/// whatever this one declares.
|
||||
fn collect_alias_type_names(
|
||||
&self,
|
||||
node: Node<'t>,
|
||||
params: &HashSet<&str>,
|
||||
own: Option<&str>,
|
||||
qualified: bool,
|
||||
out: &mut Vec<Node<'t>>,
|
||||
) {
|
||||
/// goStructFieldTypeNames (languages/go.ts): the name nodes of the types a
|
||||
/// struct's fields are declared with, in source order, skipped as in
|
||||
/// alias_type_names. An embedded field's type is a supertype
|
||||
/// (embedded_type_name), but the types it is instantiated with are not
|
||||
/// (`Pod` in client-go's `listers.ResourceIndexer[*v1.Pod]`).
|
||||
fn struct_field_type_names(&self, node: Node<'t>) -> Vec<Node<'t>> {
|
||||
let mut names = Vec::new();
|
||||
let fields = node.child_by_field_name("type").and_then(|ty| {
|
||||
(0..ty.named_child_count())
|
||||
.filter_map(|i| ty.named_child(i))
|
||||
.find(|c| c.kind() == "field_declaration_list")
|
||||
});
|
||||
let Some(fields) = fields else { return names };
|
||||
let declared = self.declared_names(node);
|
||||
for field in (0..fields.named_child_count()).filter_map(|i| fields.named_child(i)) {
|
||||
if field.kind() != "field_declaration" {
|
||||
continue;
|
||||
}
|
||||
let mut ty = field.child_by_field_name("type");
|
||||
// Embedded: no field name (extract_inheritance's test).
|
||||
let embedded = !(0..field.named_child_count())
|
||||
.filter_map(|j| field.named_child(j))
|
||||
.any(|c| c.kind() == "field_identifier");
|
||||
if embedded {
|
||||
ty = ty
|
||||
.filter(|t| t.kind() == "generic_type")
|
||||
.and_then(|t| t.child_by_field_name("type_arguments"));
|
||||
}
|
||||
if let Some(ty) = ty {
|
||||
self.collect_type_names(ty, &declared, false, &mut names);
|
||||
}
|
||||
}
|
||||
names
|
||||
}
|
||||
|
||||
/// collectGoTypeNames (languages/go.ts). `qualified`: the name of a
|
||||
/// `pkg.Name`, which is that package's whatever this one declares.
|
||||
fn collect_type_names(&self, node: Node<'t>, declared: &HashSet<&str>, qualified: bool, out: &mut Vec<Node<'t>>) {
|
||||
stack_guard!();
|
||||
if node.kind() == "type_identifier" {
|
||||
let text = self.text(node);
|
||||
if !is_go_predeclared_type(text) && (qualified || (!params.contains(text) && own != Some(text))) {
|
||||
if !is_go_predeclared_type(text) && (qualified || !declared.contains(text)) {
|
||||
out.push(node);
|
||||
}
|
||||
return;
|
||||
@@ -942,7 +988,7 @@ impl<'t> Walker<'t> {
|
||||
let qualified = node.kind() == "qualified_type";
|
||||
for i in 0..node.named_child_count() {
|
||||
if let Some(c) = node.named_child(i) {
|
||||
self.collect_alias_type_names(c, params, own, qualified, out);
|
||||
self.collect_type_names(c, declared, qualified, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user