Repository navigation
Code Analysis Go Interface Candidates
Validated in Draft, not Labs: #1285, head 209f4857c4b746a4f0ac6fd88bdd449ae2d2e080, starts at #1284's source branch codex/code-analysis-go-gopls-eval from 59f1a80058c5e5fd8859be960b436a05a799d8e7 and targets that branch. No approval, merge, release, installation or labs/code-analysis update.
#1284 bound interface-typed calls to captured interface declarations, but a change question for a concrete method still reported only direct callers: Echo *context.RealIP showed 3 positions instead of 9, client_golang *Registry.Gather 47 instead of 56.
This round attaches the interface-mediated positions to the changed implementation while keeping them separate from direct calls:
-
knownReferences: direct calls on the changed method, unchanged. -
interfaceCandidates: calls whose static receiver is an interface that the changed type's complete method set satisfies.
Each candidate carries the chain call site → interface method declaration → satisfying concrete type and method, the valueSatisfies/pointerSatisfies flags, the other captured implementations, and runtimeTarget: "not-determined". Every candidate has a reason; no candidate is presented as a definite runtime caller.
Example (Echo client_ip_security_test.go:37):
call site → Context.RealIP (context.go:46:2) → *context.RealIP (context.go:298)
value does not satisfy; *context does
Source-first expectations were recorded in expectations.md before implementation. Afero did not participate in prior Go rule design (Cobra, Chi, gRPC, Echo, client_golang).
| Study | Repository and commits | Changed definition | Interface | Direct before → after | Candidates | Wrong | Missed |
|---|---|---|---|---|---|---|---|
| A | labstack/echo fa780cd → ebbb855
|
(*context).RealIP context.go:298
|
Context.RealIP context.go:46:2
|
3 → 3 | 6 | 0 | 0 |
| B | prometheus/client_golang 7c438e9 → 8f7c887
|
(*Registry).Gather registry.go:455
|
Gatherer.Gather registry.go:173:2
|
47 → 47 | 9 | 0 | 0 |
| C | spf13/afero effad5e → 8d2a95c
|
(*RegexpFs).OpenFile regexpfs.go:118
|
Fs.OpenFile afero.go:74:2
|
0 → 0 | 38 | 0 | 0 |
Build scope: root module only, Darwin/arm64, cgo disabled, no custom tags, tests included, Go 1.27.2. Afero's nested modules (sftpfs/, gcsfs/) were not loaded.
Client's 47 direct references exceed the normal 40-item display bound: knownReferences.total stays 47 while 40 are listed and 7 omitted. The omitted seven are not missing bindings.
Method-set rules, verified with go/types:
- A type is an edge only if its whole method set implements the interface.
HttpFshas a matchingOpenFile(name string, flag int, perm os.FileMode) (File, error), but itsOpenreturnshttp.File, so it does not implementFsand is absent. -
Tand*Tstay distinct.contextdoes not satisfyContext;*contextdoes.RegexpFsand other pointer-receiver implementations arevalueSatisfies: false,pointerSatisfies: true;OsFs/FromIOFSare value-and-pointer. - Sibling interfaces with the same method spelling stay separate. Client calls through
TransactionalGatherer, and direct calls toGatherers.Gather/GathererFunc.Gather, are not candidates for*Registry.Gather. - Two types implementing the same interface both appear. Afero:
*RegexpFsand*MemMapFsboth satisfyFs; 36 of the 38 candidates areFs-typed calls. - Methods promoted from an embedded interface field are explicit alternatives. Afero's
AferoandregexpStatErrorFssatisfyFs, but theirOpenFileis promoted, not a captured concrete body (reason: promoted-interface-method). Two*afero.Aferowrapper calls (tarfs/tarfs_test.go:310,324) bind withreceiverPromotion: embedded-interface. - Candidates are deduplicated by snapshot and call fact. A call appears once even when the method implements several interfaces.
Not proven:
- Runtime assignment. Every interface-typed call is a candidate, even when a local value is visibly a different implementation.
runtimeTargetis alwaysnot-determined. - Interfaces with external or anonymous embeds, generic interfaces, generic concrete types and type aliases are skipped and counted in
interfaceEvidence. Client has one incompletedelegatorinterface, afero one incompleteFileinterface (ioembeds). Absence is unknown, not non-implementation. - Function variables, anonymous interfaces, type-parameter receivers and uncaptured third-party bodies are outside candidate coverage.
gopls v0.24.0 was an independent oracle only, not a dependency. Archify still uses the built-in Go helper with no language-server process.
| Study | gopls concrete refs | direct + candidates | gopls interface refs | interface-only extras | gopls implementations | captured concrete methods |
|---|---|---|---|---|---|---|
| A | 9 | 3 + 6 = 9 | 9 | 0 | 1 | 1 |
| B | 56 | 47 + 9 = 56 | 61 | 5 | 3 | 3 |
| C | 38 | 0 + 38 = 38 | 41 | 3 | 11 | 11 |
The concrete-reference sets agree in every study. Interface-method references are broader because they include direct calls to sibling implementations: five in B (Gatherers.Gather, GathererFunc.Gather) and three in C (*CopyOnWriteFs, *MemMapFs). Those are direct implementation calls, not interface receivers, and correctly stay outside interfaceCandidates. Captured edges and gopls agree on 1/3/11 concrete implementation methods; afero's two promoted wrappers are additional captured candidates, and no gopls-only concrete implementation was found.
- Go adapter revision bumped 4 → 5;
go-source-direct-v3addsinterfaceImplementationsedges andlanguageScope.interfaceEvidence. - A capture without the new evidence shows
coverage: "unavailable",total: null, and the direct references already captured; it is not shown as zero candidates. The change question reportsinterface-implementation-evidence-unavailableand marks the side partial. - Direct call binding is unchanged.
-
Local:
npm test19/19; code-analysis suite 424/424 with zero skips. New tests cover direct/candidate separation, no runtime claim, full method-set rejection (HttpFs-style), T/*T flags, sibling interfaces, promoted wrappers, package-test calls and the old-capture unavailable path. - Source CLI: the fixed benchmark exits 0 for A/B/C with all source-first positions, wrong = 0, missed = 0, no duplicate candidates.
-
Source identity: every displayed direct call and all 15 previous-round candidates match the source answer on repository version, target file, declaration anchor and owning type; all 38 Afero candidates match too. The same-named
Gatherer.Gather->TransactionalGatherer.Gatherswap is rejected, as are the four independent field controls. Per-item tables:identity-recheck.md. -
ZIP: unpacked
archify.zipSHA256835c60ed21a21b16cf2be50528f24c07644539cf039be6dc56804143fd6a53deproduces a byte-identical normalizedsummary.jsonto the source CLI. The extracted tree needs the normal Code Analysis dependencies; the ZIP does not vendor them. -
Remote: CI run 42459007509 succeeded at
209f4857c(test, full-regression, browser-regression, Windows portability, package smoke, zip-freshness, website; deploy jobs skipped by design). - Integration: Draft only; no merge, approval, release, installation or Labs update.
Reproduction: python3 benchmarks/code-analysis-go-interface-candidates/run.py with the source or ZIP CLI, the #1284 baseline CLI at 59f1a8005, preserved snapshots at the fixed commits with a readable Git identity, and optional gopls. evidence/summary.json contains the per-position classification and negative-control result; identity-recheck.md itemizes direct calls and candidates.
Decision input: keep improving the built-in parser. Method-set edges answer "which interface positions should I inspect and why" without claiming runtime identity; adopting gopls is not required.