fix(go): an interface or struct embedding a standard-library type counts its methods (#2759)

goImplementsEdges (#584) follows declared embeddings since #2402, but an
embedding of a type outside the index resolves to nothing, so it added no
methods: prometheus's TBRun (testing.TB plus Run) went to a struct with only
a Run, QueryLogger (slog.Handler plus io.Closer) had no implementations,
kapacitor's Diagnostic (a third-party Logger plus Log) took every struct with
a Log, and the predeclared `error` was not recorded at all.

- go-stdlib-methods.ts lists the methods of 130 standard-library types, each
  as `name:params/results`, generated from go1.27.3's source and counted the
  way goSignatureArity counts a stored signature.
- Both extractors record an embedded `error`, which stays unresolved. The
  predeclared-type set is unchanged, so alias and defined-type references
  still skip it.
- goImplementsEdges reads each Go type's failed `extends` rows and reads the
  package back off the line (goRefQualification, now exported). A
  standard-library type's methods join the method set, keyed as goMethodKey
  keys the project's (`std:<import path>` for an unexported one), and the
  arity gate holds them to the table's counts on both sides: a `sync.Mutex`
  makes a struct hugo's `Locker`, not etcd's gRPC `LockClient`. Any other
  outside type is met by a type embedding the same one, or by the only type
  with every other method the interface names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-10-21 18:13:54 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 1f3a274c53
commit 847f1df77d
11 changed files with 933 additions and 39 deletions
+5 -2
View File
@@ -936,7 +936,7 @@ impl<'t> Walker<'t> {
/// embedding names — `T`, `*T` (the `*` is a sibling token), `pkg.T` and
/// `T[X]` all embed `T`. A qualified type yields its name; resolution reads
/// the package back from the source at that position. None for any other
/// type and for a predeclared one.
/// type and for a predeclared one but `error`, an interface with a method.
fn embedded_type_name(&self, ty: Option<Node<'t>>) -> Option<Node<'t>> {
let mut ty = ty?;
if ty.kind() == "generic_type" {
@@ -944,7 +944,10 @@ impl<'t> Walker<'t> {
}
match ty.kind() {
"qualified_type" => ty.child_by_field_name("name"),
"type_identifier" if !is_go_predeclared_type(self.text(ty)) => Some(ty),
"type_identifier" => {
let name = self.text(ty);
(name == "error" || !is_go_predeclared_type(name)).then_some(ty)
}
_ => None,
}
}