mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-28 14:08:56 +08:00
fix(go): an interface's methods link the types their signatures name (#2659)
A Go interface's methods are `method` nodes, but their signatures named
no types: etcd's `KV.Put(ctx context.Context, key, val string, opts
...OpOption) (*PutResponse, error)` had no edge to `OpOption` or
`PutResponse`. A call through the interface reaches that method, so
impact on those types missed the interface method and the callers that go
through it.
TS extractor and Rust kernel, mirrored:
- extractGoInterfaceMethods / extract_go_interface_methods give each
method a `references` ref per type name in its parameters and results
(goInterfaceMethodTypeNames / interface_method_type_names, on #2655's
collectGoTypeNames).
- Skipped: the predeclared types and, written bare, the interface's own
type parameters (goTypeParameterNames / type_parameter_names, split out
of goDeclaredNames). The interface's own name is a type its method names
(`Children() []Node`), so it is kept.
A/B on main 6692e051: gin +34, etcd +775, prometheus +294, kubernetes
+11079 edges; every one judged against the real package clause (bare →
own package, `pkg.T` → the import's package, kubernetes' one dot import
included); 0 dropped/retargeted/metadata-only. Kernel and wasm indexes
are identical. Explore keeps every named body on 29 flow queries.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
6692e05107
commit
383c2cd9e3
@@ -70,9 +70,15 @@ export function goEmbeddedTypeName(type: SyntaxNode | null | undefined, source:
|
||||
* its own name, which in a recursive type is the declaration itself.
|
||||
*/
|
||||
function goDeclaredNames(decl: SyntaxNode, source: string): Set<string> {
|
||||
const names = new Set<string>();
|
||||
const names = goTypeParameterNames(decl, source);
|
||||
const nameNode = getChildByField(decl, 'name');
|
||||
if (nameNode) names.add(getNodeText(nameNode, source));
|
||||
return names;
|
||||
}
|
||||
|
||||
/** The type parameters a Go type declaration declares: `T` and `K` in `type Store[T any, K comparable] …`. */
|
||||
function goTypeParameterNames(decl: SyntaxNode, source: string): Set<string> {
|
||||
const names = new Set<string>();
|
||||
for (const param of getChildByField(decl, 'type_parameters')?.namedChildren ?? []) {
|
||||
for (const c of param.namedChildren) {
|
||||
if (c.type === 'identifier') names.add(getNodeText(c, source));
|
||||
@@ -150,6 +156,25 @@ export function goStructFieldTypeNames(node: SyntaxNode, source: string): Syntax
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name nodes of the types a Go interface method's signature names, in
|
||||
* source order: `OpOption` and `PutResponse` in etcd's `Put(ctx
|
||||
* context.Context, key, val string, opts ...OpOption) (*PutResponse, error)`,
|
||||
* and `Context`, whose package stays in the source. `decl` declares the
|
||||
* interface. Skipped: Go's predeclared types and, written bare, the
|
||||
* interface's own type parameters (`T` in `type Store[T any] interface`). Its
|
||||
* own name is a type the method names (`Children() []Node`).
|
||||
*/
|
||||
export function goInterfaceMethodTypeNames(method: SyntaxNode, decl: SyntaxNode, source: string): SyntaxNode[] {
|
||||
const params = goTypeParameterNames(decl, source);
|
||||
const names: SyntaxNode[] = [];
|
||||
for (const field of ['parameters', 'result']) {
|
||||
const part = getChildByField(method, field);
|
||||
if (part) collectGoTypeNames(part, params, source, names);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
export const goExtractor: LanguageExtractor = {
|
||||
functionTypes: ['function_declaration'],
|
||||
classTypes: [], // Go doesn't have classes
|
||||
|
||||
@@ -25,7 +25,7 @@ import { stripCppTemplateArgs, isCppConstructorDeclaration, cDeclaratorIdentifie
|
||||
import { NestedIntervals, scanCppBraceScopes, type CppBraceScopes } from './languages/cpp-brace-scopes';
|
||||
import { appendCppMacroMethods } from './languages/cpp-macro-methods';
|
||||
import { rustImplTypeName } from './languages/rust';
|
||||
import { goAliasTypeNames, goEmbeddedTypeName, goStructFieldTypeNames } from './languages/go';
|
||||
import { goAliasTypeNames, goEmbeddedTypeName, goInterfaceMethodTypeNames, goStructFieldTypeNames } from './languages/go';
|
||||
import { dartMisparsedGenericCall, dartReceiverOf, isDartTypeName, pushDartTypeRefs } from './languages/dart';
|
||||
import { LiquidExtractor } from './liquid-extractor';
|
||||
import { RazorExtractor } from './razor-extractor';
|
||||
@@ -3909,7 +3909,7 @@ export class TreeSitterExtractor {
|
||||
// impl-navigation can see the contract. Go has no `implements` keyword, so
|
||||
// without the interface's method set there's nothing to match against.
|
||||
if (this.language === 'go' && typeChild) {
|
||||
this.extractGoInterfaceMethods(typeChild, interfaceNode.id);
|
||||
this.extractGoInterfaceMethods(typeChild, interfaceNode.id, node);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -3959,8 +3959,11 @@ export class TreeSitterExtractor {
|
||||
* interface). tree-sitter-go names these `method_elem` (newer) or
|
||||
* `method_spec` (older). Embedded interfaces (`Reader` inside `ReadWriter`)
|
||||
* are `type_elem`s, not methods, and are left to inheritance extraction.
|
||||
* Each method references the types its signature names, as a function does
|
||||
* (`decl` declares the interface): a call through the interface reaches the
|
||||
* method, so impact on a type reaches its callers.
|
||||
*/
|
||||
private extractGoInterfaceMethods(interfaceType: SyntaxNode, ifaceId: string): void {
|
||||
private extractGoInterfaceMethods(interfaceType: SyntaxNode, ifaceId: string, decl: SyntaxNode): void {
|
||||
this.nodeStack.push(ifaceId);
|
||||
for (let i = 0; i < interfaceType.namedChildCount; i++) {
|
||||
const m = interfaceType.namedChild(i);
|
||||
@@ -3969,9 +3972,20 @@ export class TreeSitterExtractor {
|
||||
if (!nameNode) continue;
|
||||
const mname = getNodeText(nameNode, this.source);
|
||||
if (mname) {
|
||||
this.createNode('method', mname, m, {
|
||||
const methodNode = this.createNode('method', mname, m, {
|
||||
signature: this.extractor?.getSignature?.(m, this.source),
|
||||
});
|
||||
if (methodNode) {
|
||||
for (const type of goInterfaceMethodTypeNames(m, decl, this.source)) {
|
||||
this.unresolvedReferences.push({
|
||||
fromNodeId: methodNode.id,
|
||||
referenceName: getNodeText(type, this.source),
|
||||
referenceKind: 'references',
|
||||
line: type.startPosition.row + 1,
|
||||
column: type.startPosition.column,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.nodeStack.pop();
|
||||
|
||||
Reference in New Issue
Block a user