mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-19 06:22:38 +08:00
fix(go): a type alias type A = B is indexed and links to the type it names (#2417)
tree-sitter-go parses `type Event = mvccpb.Event` as a `type_alias`, beside
the `type_spec` of a defined type, and both extractors read `type_spec`
only. etcd's client/v3 `Event` had no node; a use through its package
(`clientv3.Event`, prometheus's `storage.AOptions{…}`) linked to nothing,
and a use inside it linked by name to any type so named, another package's
included.
- Extraction (TS and kernel): an alias is a `type_alias` node referencing
each type its right-hand side names, at the name so resolution reads the
package back; an alias of a struct or interface literal is a struct or
interface. The alias's own type parameters and Go's predeclared types are
skipped. A generic alias, which tree-sitter-go 0.23 parses as a
`type_spec` around an error, reads as an alias on wasm; the kernel defers
its file.
- A method called on an alias-typed value is the aliased type's: an alias
of a project type is followed in its own package, through pointers and
further aliases; one of an outside type resolves by name as before.
- An alias's target written through a package that is none of the file's
imports as indexed stays unresolved, as an embedded type's does, so the
alias never links to itself or a namesake.
- With Go defined types as implementers (#2419), an alias that owns
methods written with it as the receiver implements what they satisfy,
in place of the type it names.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -395,7 +395,7 @@ impl<'t> Walker<'t> {
|
||||
} else if kind == "method_declaration" {
|
||||
self.extract_method(node);
|
||||
skip_children = true;
|
||||
} else if kind == "type_spec" {
|
||||
} else if kind == "type_spec" || kind == "type_alias" {
|
||||
skip_children = self.extract_type_alias(node);
|
||||
} else if matches!(kind, "var_declaration" | "short_var_declaration" | "const_declaration")
|
||||
&& !self.inside_class_like()
|
||||
@@ -529,7 +529,8 @@ impl<'t> Walker<'t> {
|
||||
self.stack.pop();
|
||||
}
|
||||
|
||||
/// extractTypeAlias for Go: type_spec → struct / interface / plain alias.
|
||||
/// extractTypeAlias for Go: type_spec (`type A B`) and type_alias
|
||||
/// (`type A = B`) → struct / interface / plain alias.
|
||||
fn extract_type_alias(&mut self, node: Node<'t>) -> bool {
|
||||
stack_guard!();
|
||||
let name = self.extract_name(node);
|
||||
@@ -582,14 +583,22 @@ impl<'t> Walker<'t> {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.create_node(
|
||||
let row = self.create_node(
|
||||
"type_alias",
|
||||
&name,
|
||||
node,
|
||||
Extra { docstring, is_exported, ..Extra::default() },
|
||||
);
|
||||
// (go type_spec has no `value` field — no type-ref walk; TS/tsx member
|
||||
// extraction is TS-family-only)
|
||||
// (go has no `value` field — no TS-style type-ref walk or member
|
||||
// extraction.) An alias references what its `type` field names; a
|
||||
// defined type (`type_spec`) declares a type of its own.
|
||||
if let Some(row) = row {
|
||||
let references = edge_kind_index("references").unwrap();
|
||||
for ty in self.alias_type_names(node) {
|
||||
let text = self.text(ty).to_string();
|
||||
self.push_ref_at(row, &text, references, ty);
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
@@ -863,6 +872,47 @@ impl<'t> Walker<'t> {
|
||||
}
|
||||
}
|
||||
|
||||
/// goAliasTypeNames (languages/go.ts): the name nodes of the types an
|
||||
/// alias's `type` field names, in source order, but its own type
|
||||
/// parameters and the predeclared types. Only a `type_alias` is one here:
|
||||
/// a generic alias, which tree-sitter-go 0.23 parses as a `type_spec`
|
||||
/// 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();
|
||||
if node.kind() != "type_alias" {
|
||||
return names;
|
||||
}
|
||||
let Some(ty) = node.child_by_field_name("type") else { return names };
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.collect_alias_type_names(ty, ¶ms, &mut names);
|
||||
names
|
||||
}
|
||||
|
||||
fn collect_alias_type_names(&self, node: Node<'t>, params: &HashSet<&str>, out: &mut Vec<Node<'t>>) {
|
||||
stack_guard!();
|
||||
if node.kind() == "type_identifier" {
|
||||
let text = self.text(node);
|
||||
if !params.contains(text) && !is_go_predeclared_type(text) {
|
||||
out.push(node);
|
||||
}
|
||||
return;
|
||||
}
|
||||
for i in 0..node.named_child_count() {
|
||||
if let Some(c) = node.named_child(i) {
|
||||
self.collect_alias_type_names(c, params, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// extractInheritance — the Go branches: interface embedding (a type_elem
|
||||
/// holding one named type; a union, `~T` or basic type is a constraint)
|
||||
/// and struct embedding (field_declaration without a field_identifier),
|
||||
|
||||
Reference in New Issue
Block a user