Skip to content

fix(go): an implementer's methods must take and return the interface's types - #3185

Merged
colbymchenry merged 1 commit into
mainfrom
claude/fix-go-implementer-types
Nov 1, 2026
Merged

colbymchenry merged 1 commit into
mainfrom
claude/fix-go-implementer-types

Conversation

@colbymchenry

Copy link
Copy Markdown
Owner

goImplementsEdges matched method names and, since PR #2447, parameter and
result counts. Go wants the signatures identical, so a type with the right
number of the wrong types passed: caddy's TLS matchers, with
Match(*tls.ClientHelloInfo) bool, "implemented" its HTTP RequestMatcher,
whose Match takes an *http.Request, and every HTTP matcher its TLS
ConnectionMatcher. Each false entry also sent calls through the interface
to a method they never reach, and took a place under the per-interface cap.

The fix: at the counts that fit, typesFit compares each parameter's and
result's type, read off the stored signature (goItemTypes,
goComparableType). A type compares by its name with the package left off,
since the two declarations may write Context and caddy.Context from
different packages. Nothing is ruled out by a true alias, by a type
parameter of the type or of what it embeds, by a func/struct/interface
literal type, or by a method a standard-library embedding brings, whose
types the table doesn't hold. A gRPC server keeps crossing to its client
interface at the server's counts.

Validation:

  • go-implements-types.test.ts: 4 of 9 cases fail on main, all pass.
    go-promoted-dispatch.test.ts pinned the old behaviour (two series sets
    implementing each other's interface by name) and now says what Go says.
  • A/B against main 4499d00, implementers dropped: caddy 24, prometheus 261,
    etcd 36, hugo 169. A separate judge reads each dropped pair's signatures
    through embedded types with qualifiers and names removed: 490 of 490 differ.
    Calls dropped with them: caddy 24, prometheus 636, etcd 36, hugo 98.
    Added: prometheus 8 implementers and 30 moved dispatch edges, real
    implementers the false ones had pushed past the cap (blockSeriesSet,
    seriesSetAdapter). gin and cobra byte-identical; controls flask and
    express identical.
  • REG's 7 false entries on caddy are gone. The 11 real implementers caddy's
    Provisioner and Unmarshaler still lose against 1.6.2 are the cap's: both
    arms keep 40 real ones, of more than 40, in file order. That isn't changed
    here; raising the cap to 400 adds 239 implementers and 467 edges on caddy.
  • CPU, median of 3 interleaved: hugo 22.75 to 22.44 s, etcd 21.19 to 22.00 s
    (wall 7.23 to 7.14 s).

Suite: 874 files passed, 6 skipped; 10,847 tests passed, 2 expected fail, 87 skipped, with the kernel on Windows, on the stack of these 8 PRs at 00b79ff2 over main 1f188da. The A/B numbers are against 4499d00; the stack on 1f188da is edge for edge the same arm on jsoup, rocksdb's java/, Minestom, okhttp, tokio, caddy, Alamofire, nowinandroid and mihon, and those repos give the same graph on both mains.

🤖 Generated with Claude Code

…s types

goImplementsEdges matched method names and, since PR #2447, parameter and
result counts. Go wants the signatures identical, so a type with the right
number of the wrong types passed: caddy's TLS matchers, with
`Match(*tls.ClientHelloInfo) bool`, "implemented" its HTTP `RequestMatcher`,
whose `Match` takes an `*http.Request`, and every HTTP matcher its TLS
`ConnectionMatcher`. Each false entry also sent calls through the interface
to a method they never reach, and took a place under the per-interface cap.

The fix: at the counts that fit, typesFit compares each parameter's and
result's type, read off the stored signature (goItemTypes,
goComparableType). A type compares by its name with the package left off,
since the two declarations may write `Context` and `caddy.Context` from
different packages. Nothing is ruled out by a true alias, by a type
parameter of the type or of what it embeds, by a `func`/`struct`/`interface`
literal type, or by a method a standard-library embedding brings, whose
types the table doesn't hold. A gRPC server keeps crossing to its client
interface at the server's counts.

Validation:
- go-implements-types.test.ts: 4 of 9 cases fail on main, all pass.
  go-promoted-dispatch.test.ts pinned the old behaviour (two series sets
  implementing each other's interface by name) and now says what Go says.
- A/B against main 4499d00, implementers dropped: caddy 24, prometheus 261,
  etcd 36, hugo 169. A separate judge reads each dropped pair's signatures
  through embedded types with qualifiers and names removed: 490 of 490 differ.
  Calls dropped with them: caddy 24, prometheus 636, etcd 36, hugo 98.
  Added: prometheus 8 implementers and 30 moved dispatch edges, real
  implementers the false ones had pushed past the cap (`blockSeriesSet`,
  `seriesSetAdapter`). gin and cobra byte-identical; controls flask and
  express identical.
- REG's 7 false entries on caddy are gone. The 11 real implementers caddy's
  `Provisioner` and `Unmarshaler` still lose against 1.6.2 are the cap's: both
  arms keep 40 real ones, of more than 40, in file order. That isn't changed
  here; raising the cap to 400 adds 239 implementers and 467 edges on caddy.
- CPU, median of 3 interleaved: hugo 22.75 to 22.44 s, etcd 21.19 to 22.00 s
  (wall 7.23 to 7.14 s).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@colbymchenry
colbymchenry merged commit 608487e into main Nov 1, 2026
@colbymchenry
colbymchenry deleted the claude/fix-go-implementer-types branch November 1, 2026 09:34
bompus added a commit to bompus/codegraph that referenced this pull request Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant