Skip to content

implementations: fall back to a method-set fingerprint when types.Implements fails across test-variant universes - #312

Open
alokkumary2j wants to merge 1 commit into
scip-code:mainfrom
alokkumary2j:implementations-fingerprint-fallback
Open

alokkumary2j wants to merge 1 commit into
scip-code:mainfrom
alokkumary2j:implementations-fingerprint-fallback

Conversation

@alokkumary2j

Copy link
Copy Markdown

Problem. loader.addImportsToPkgs keys packages by PkgPath and keeps the variant with MORE files, i.e. the white-box test
variant p [p.test] whenever p has an in-package _test.go. Implementation extraction therefore sees only the test variant's
*types.Named for types of p. An implementer in another package q was type-checked against the PLAIN p, so
types.Implements(Impl, p.Iface) fails for any interface method whose signature names a type declared in p (two distinct
*types.Named for the same p.Opts). Result: is_implementation edges silently missing for every cross-package implementer
of such an interface in any package that has an in-package test file.

Measured (prometheus @ 21fd457, GOWORK=off, scip-go v0.2.7):

run discovery.Config implementers (cross-pkg) whole module, type->interface edges (non-test files)
v0.2.7 4 (0) 1,624
NeedDeps added to the loader mode 4 (0) — not the cause n/a
--skip-tests 37 (36) — but every test-file symbol is lost 1,905
this patch 40 (36) 1,944 (superset of both baselines: +320 / +39)
44 interfaces lose implementers on prometheus alone (Config 36, Iterator 19, AppenderV2 14, ...). Debug trace:
missing=NewDiscoverer ... wrongType=true while the method-name mask passes.

Fix (~40 lines). When types.Implements fails, compare gopls-methodsets identity: every interface method's
Id + signature fingerprint must be present in the candidate type's method set. Types with type parameters (tricky fingerprints)
stay on types.Implements only, so the fallback can only ADD edges where the type checker was fooled by the universe split.

Minimal repro.

go.mod        module example.com/impl
p/p.go        package p; type Opts struct{}; type Svc interface{ Run(o *Opts) error }
p/p_test.go   package p; import "testing"; func TestOpts(t *testing.T) { _ = Opts{} }
q/q.go        package q; import "example.com/impl/p"; type Impl struct{}; func (Impl) Run(o *p.Opts) error { return nil }

scip-go -> no is_implementation relationship from q.Impl to p.Svc (delete p/p_test.go and it appears).
With the patch the edge is emitted.

Patch: scip-go-v0.2.7-fingerprint-fallback.patch (touches internal/implementations/{extractor,implementations}.go).

Still present on v0.2.9 (173cadc): the minimal repro above emits no is_implementation relationship unpatched and emits
q.Impl# -> p.Svc# (and Impl#Run(). -> Svc#Run.) patched. The patch applies cleanly to main, and go test ./... passes.

🤖 Generated with Claude Code

…lements fails

loader.addImportsToPkgs keeps the white-box test variant `p [p.test]` of a
package that has an in-package _test.go, so implementation extraction sees
that variant's *types.Named while a cross-package implementer was checked
against the plain `p`. types.Implements then fails for any interface method
whose signature names a type declared in `p`, and the is_implementation
relationship is silently dropped.

When types.Implements fails, compare method-set identity (each interface
method's Id + signature fingerprint present in the type's method set).
Types with type parameters stay on types.Implements only, so the fallback
can only add edges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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