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:
Colby Mchenry
2026-10-07 10:16:45 +00:00
committed by GitHub
parent 61f8b09cb8
commit ed199e60d8
9 changed files with 591 additions and 12 deletions
+55 -5
View File
@@ -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, &params, &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),