fix(go): field-chain calls resolve via validated type inference, never bare-name guessing (#1316)

target.conn.Exec("insert") with `conn *sql.DB` emitted a BARE `Exec`
ref (the receiver chain was dropped for non-identifier receivers), and
exact-match then bound it to the only local `Exec` — an unrelated
interface's method — fabricating an internal dependency (#1276).

Extraction now keeps Go 2-hop selector chains (`base.field.Method`),
and a dedicated matcher resolves them EXCLUSIVELY via two inference
hops: base's type from the enclosing scope (#1108 machinery), field's
declared type from the struct's own declaration lines (comment-
stripped, per-line — chi's "the tree router" doc comment otherwise
donates a phantom type). resolveMethodOnType validates the target.
Package-qualified field types are followed only when the package is
in-module — `handler http.Handler` must not bind a same-named local
decoy. Failure at any hop leaves the ref unresolved: chained Go
receivers never fall through to the bare-name strategies (they were
never emitted before, so no prior recall depends on that path).

chi before/after: node count stable (1,181); 8 correct field-chain
edges gained (mx.tree.FindRoute/InsertRoute/routes, validated,
including the unexported `node` type); the removed edges are the
prior bare-name guesses on external receivers.

Fixes #1276

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-07-16 15:49:24 -05:00
committed by GitHub
co-authored by Claude Fable 5
parent 2ec877b08c
commit 41c2029798
4 changed files with 262 additions and 0 deletions
+15
View File
@@ -4431,6 +4431,21 @@ export class TreeSitterExtractor {
// scope keywords: such calls previously emitted a bare method
// name, which either failed to resolve or resolved ambiguously.
calleeName = `${getNodeText(receiver, this.source)}.${methodName}`;
} else if (
this.language === 'go' &&
receiver &&
receiver.type === 'selector_expression' &&
/^[A-Za-z_]\w*\.[A-Za-z_]\w*$/.test(getNodeText(receiver, this.source).replace(/\s+/g, ''))
) {
// Go 2-hop field chain `target.conn.Exec(...)`: keep the
// receiver chain so resolution can infer `conn`'s declared type
// from the Target struct. Previously this emitted the bare
// method name, and when the field's type is EXTERNAL (sql.DB)
// the bare name exact-matched an unrelated same-named local
// method — a fabricated internal dependency (#1276). Chained
// Go receivers resolve strictly via validated field-hop
// inference (see matchGoFieldChainCall) or stay unresolved.
calleeName = `${getNodeText(receiver, this.source).replace(/\s+/g, '')}.${methodName}`;
} else {
calleeName = methodName;
}