diff --git a/internal/implementations/implementations.go b/internal/implementations/implementations.go index 177ede2b..675465db 100644 --- a/internal/implementations/implementations.go +++ b/internal/implementations/implementations.go @@ -108,8 +108,9 @@ func AddImplementationRelationships( pkgs loader.PackageLookup, allPackages loader.PackageLookup, symbols *lookup.Global, -) error { - return output.WithProgress("Indexing Implementations", func() error { +) ([]*scip.SymbolInformation, error) { + var externalSymbols []*scip.SymbolInformation + err := output.WithProgress("Indexing Implementations", func() error { localInterfaces, localTypes, err := extractInterfacesAndConcreteTypes(pkgs, symbols) if err != nil { return err @@ -123,7 +124,8 @@ func AddImplementationRelationships( remotePackages[pkgID] = pkg } - remoteInterfaces, _, err := extractInterfacesAndConcreteTypes(remotePackages, symbols) + remoteInterfaces, remoteTypes, err := extractInterfacesAndConcreteTypes( + remotePackages, symbols) if err != nil { return err } @@ -134,15 +136,22 @@ func AddImplementationRelationships( // local type -> remote interface findImplementations(localTypes, remoteInterfaces, symbols) - // TODO(author: tjdevries, issue: https://github.com/sourcegraph/scip-go/issues/64) - // We should consider what this would even look like? - // I don't think this makes sense the current way that we are emitting - // implementations. You wouldn't even catch these anyways when uploading // remote type -> local interface - // findImplementations(remoteTypes, localInterfaces, symbols) + // We emit these as external symbols so index consumer can merge them. + findImplementations(remoteTypes, localInterfaces, symbols) + + // Collect remote type symbols that gained relationships + for _, typ := range remoteTypes { + if sym, ok := symbols.GetSymbolInformation(typ.Pkg, typ.Ident.Pos()); ok { + if len(sym.Relationships) > 0 { + externalSymbols = append(externalSymbols, sym) + } + } + } return nil }) + return externalSymbols, err } func implementationsForType(ty ImplDef, tyMethods *intsets.Sparse, interfaceToMethodSet map[*scip.SymbolInformation]*intsets.Sparse) (matching []*scip.SymbolInformation) { diff --git a/internal/index/scip.go b/internal/index/scip.go index 3ad22b8a..3cec2eeb 100644 --- a/internal/index/scip.go +++ b/internal/index/scip.go @@ -98,13 +98,16 @@ func Index(writer func(proto.Message) error, opts config.IndexOpts) error { return err } + var externalSymbols []*scip.SymbolInformation pathToDocument, globalSymbols := indexVisitPackages(opts, projectPackages, allPackages) if !opts.SkipImplementations { - if err := impls.AddImplementationRelationships( + implSymbols, err := impls.AddImplementationRelationships( projectPackages, allPackages, globalSymbols, - ); err != nil { + ) + if err != nil { return err } + externalSymbols = implSymbols } pkgIDs := slices.Sorted(maps.Keys(projectPackages)) @@ -156,7 +159,18 @@ func Index(writer func(proto.Message) error, opts config.IndexOpts) error { output.WithProgressParallel(&wg, "Visiting Project Files", &count, uint64(pkgLen)) - return writeErr + if writeErr != nil { + return writeErr + } + + // Emit external symbols for remote types that implement local interfaces + for _, sym := range externalSymbols { + if err := writer(sym); err != nil { + return err + } + } + + return nil } func indexVisitPackages( diff --git a/internal/testdata/snapshots/input/pr198/dep/dep.go b/internal/testdata/snapshots/input/pr198/dep/dep.go new file mode 100644 index 00000000..dd103801 --- /dev/null +++ b/internal/testdata/snapshots/input/pr198/dep/dep.go @@ -0,0 +1,9 @@ +package dep + +import "fmt" + +type T struct{} + +func (t *T) Bar() { + fmt.Println("Bar") +} diff --git a/internal/testdata/snapshots/input/pr198/dep/go.mod b/internal/testdata/snapshots/input/pr198/dep/go.mod new file mode 100644 index 00000000..e3a155cf --- /dev/null +++ b/internal/testdata/snapshots/input/pr198/dep/go.mod @@ -0,0 +1,3 @@ +module github.com/example/dep + +go 1.22 diff --git a/internal/testdata/snapshots/input/pr198/go.mod b/internal/testdata/snapshots/input/pr198/go.mod new file mode 100644 index 00000000..6d877815 --- /dev/null +++ b/internal/testdata/snapshots/input/pr198/go.mod @@ -0,0 +1,7 @@ +module sg/pr198 + +go 1.25 + +require github.com/example/dep v0.0.0 + +replace github.com/example/dep => ./dep diff --git a/internal/testdata/snapshots/input/pr198/pr198.go b/internal/testdata/snapshots/input/pr198/pr198.go new file mode 100644 index 00000000..013f8441 --- /dev/null +++ b/internal/testdata/snapshots/input/pr198/pr198.go @@ -0,0 +1,17 @@ +package pr198 + +import "github.com/example/dep" + +// Foo is an interface defined downstream of the type that implements it. +// The dep.T type (from a dependency) implements Foo, and scip-go should +// emit an external symbol for dep.T with an IsImplementation relationship +// pointing to Foo. +type Foo interface { + Bar() +} + +func UseFoo(f Foo) {} + +func Example() { + UseFoo(&dep.T{}) +} diff --git a/internal/testdata/snapshots/input/testdata/implementations.go b/internal/testdata/snapshots/input/testdata/implementations.go index 316eca0b..2f4ba640 100644 --- a/internal/testdata/snapshots/input/testdata/implementations.go +++ b/internal/testdata/snapshots/input/testdata/implementations.go @@ -34,7 +34,7 @@ type Foo int func (r Foo) nonExportedMethod() {} func (r Foo) ExportedMethod() {} -func (r Foo) Close() error { return nil } +func (r Foo) ScipTestMethod() {} type SharedOne interface { Shared() diff --git a/internal/testdata/snapshots/input/testdata/implementations_embedded.go b/internal/testdata/snapshots/input/testdata/implementations_embedded.go index 2bad2377..96e5fe84 100644 --- a/internal/testdata/snapshots/input/testdata/implementations_embedded.go +++ b/internal/testdata/snapshots/input/testdata/implementations_embedded.go @@ -1,11 +1,13 @@ package testdata -import "io" - type I3 interface { - Close() error + ScipTestMethod() +} + +type EmbeddedI3 interface { + ScipTestMethod() } type TClose struct { - io.Closer + EmbeddedI3 } diff --git a/internal/testdata/snapshots/output/pr198/external_symbols.txt b/internal/testdata/snapshots/output/pr198/external_symbols.txt new file mode 100755 index 00000000..90e850c2 --- /dev/null +++ b/internal/testdata/snapshots/output/pr198/external_symbols.txt @@ -0,0 +1,2 @@ +github.com/example/dep 0.1.test `github.com/example/dep`/T# + relationship 0.1.test `sg/pr198`/Foo# implementation \ No newline at end of file diff --git a/internal/testdata/snapshots/output/pr198/pr198.go b/internal/testdata/snapshots/output/pr198/pr198.go new file mode 100755 index 00000000..abc20be1 --- /dev/null +++ b/internal/testdata/snapshots/output/pr198/pr198.go @@ -0,0 +1,52 @@ + package pr198 +// ^^^^^ definition 0.1.test `sg/pr198`/ +// display_name pr198 +// signature_documentation +// > package pr198 + + import "github.com/example/dep" +// ^^^^^^^^^^^^^^^^^^^^^^ reference github.com/example/dep 0.1.test `github.com/example/dep`/ + + // Foo is an interface defined downstream of the type that implements it. + // The dep.T type (from a dependency) implements Foo, and scip-go should + // emit an external symbol for dep.T with an IsImplementation relationship + // pointing to Foo. + type Foo interface { +// ^^^ definition 0.1.test `sg/pr198`/Foo# +// signature_documentation +// > type Foo interface{ Bar() } +// documentation +// > Foo is an interface defined downstream of the type that implements it. +// > The dep.T type (from a dependency) implements Foo, and scip-go should +// > emit an external symbol for dep.T with an IsImplementation relationship +// > pointing to Foo. + Bar() +// ^^^ definition 0.1.test `sg/pr198`/Foo#Bar. +// signature_documentation +// > func (Foo).Bar() + } + +//⌄ enclosing_range_start 0.1.test `sg/pr198`/UseFoo(). + func UseFoo(f Foo) {} +// ^^^^^^ definition 0.1.test `sg/pr198`/UseFoo(). +// signature_documentation +// > func UseFoo(f Foo) +// ^ definition local 0 +// display_name f +// signature_documentation +// > var f Foo +// ^^^ reference 0.1.test `sg/pr198`/Foo# +// ⌃ enclosing_range_end 0.1.test `sg/pr198`/UseFoo(). + +//⌄ enclosing_range_start 0.1.test `sg/pr198`/Example(). + func Example() { +// ^^^^^^^ definition 0.1.test `sg/pr198`/Example(). +// signature_documentation +// > func Example() + UseFoo(&dep.T{}) +// ^^^^^^ reference 0.1.test `sg/pr198`/UseFoo(). +// ^^^ reference github.com/example/dep 0.1.test `github.com/example/dep`/ +// ^ reference github.com/example/dep 0.1.test `github.com/example/dep`/T# + } +//⌃ enclosing_range_end 0.1.test `sg/pr198`/Example(). + diff --git a/internal/testdata/snapshots/output/testdata/implementations.go b/internal/testdata/snapshots/output/testdata/implementations.go index d0473204..33e91c94 100755 --- a/internal/testdata/snapshots/output/testdata/implementations.go +++ b/internal/testdata/snapshots/output/testdata/implementations.go @@ -112,7 +112,7 @@ // ^^^ definition 0.1.test `sg/testdata`/Foo# // signature_documentation // > type Foo int -// relationship github.com/golang/go/src go1.22 io/Closer# implementation +// relationship 0.1.test `sg/testdata`/EmbeddedI3# implementation // relationship 0.1.test `sg/testdata`/I3# implementation // relationship 0.1.test `sg/testdata`/InterfaceWithExportedMethod# implementation // relationship 0.1.test `sg/testdata`/InterfaceWithNonExportedMethod# implementation @@ -141,19 +141,19 @@ // > func (Foo).ExportedMethod() // relationship 0.1.test `sg/testdata`/InterfaceWithExportedMethod#ExportedMethod. implementation // ⌃ enclosing_range_end 0.1.test `sg/testdata`/Foo#ExportedMethod(). -//⌄ enclosing_range_start 0.1.test `sg/testdata`/Foo#Close(). - func (r Foo) Close() error { return nil } +//⌄ enclosing_range_start 0.1.test `sg/testdata`/Foo#ScipTestMethod(). + func (r Foo) ScipTestMethod() {} // ^ definition local 5 // display_name r // signature_documentation // > var r Foo // ^^^ reference 0.1.test `sg/testdata`/Foo# -// ^^^^^ definition 0.1.test `sg/testdata`/Foo#Close(). -// signature_documentation -// > func (Foo).Close() error -// relationship github.com/golang/go/src go1.22 io/Closer#Close. implementation -// relationship 0.1.test `sg/testdata`/I3#Close. implementation -// ⌃ enclosing_range_end 0.1.test `sg/testdata`/Foo#Close(). +// ^^^^^^^^^^^^^^ definition 0.1.test `sg/testdata`/Foo#ScipTestMethod(). +// signature_documentation +// > func (Foo).ScipTestMethod() +// relationship 0.1.test `sg/testdata`/EmbeddedI3#ScipTestMethod. implementation +// relationship 0.1.test `sg/testdata`/I3#ScipTestMethod. implementation +// ⌃ enclosing_range_end 0.1.test `sg/testdata`/Foo#ScipTestMethod(). type SharedOne interface { // ^^^^^^^^^ definition 0.1.test `sg/testdata`/SharedOne# diff --git a/internal/testdata/snapshots/output/testdata/implementations_embedded.go b/internal/testdata/snapshots/output/testdata/implementations_embedded.go index 14faa1a4..6d98b834 100755 --- a/internal/testdata/snapshots/output/testdata/implementations_embedded.go +++ b/internal/testdata/snapshots/output/testdata/implementations_embedded.go @@ -1,30 +1,38 @@ package testdata // ^^^^^^^^ definition 0.1.test `sg/testdata`/ - import "io" -// ^^ reference github.com/golang/go/src go1.22 io/ - type I3 interface { // ^^ definition 0.1.test `sg/testdata`/I3# // signature_documentation -// > type I3 interface{ Close() error } - Close() error -// ^^^^^ definition 0.1.test `sg/testdata`/I3#Close. -// signature_documentation -// > func (I3).Close() error +// > type I3 interface{ ScipTestMethod() } + ScipTestMethod() +// ^^^^^^^^^^^^^^ definition 0.1.test `sg/testdata`/I3#ScipTestMethod. +// signature_documentation +// > func (I3).ScipTestMethod() + } + + type EmbeddedI3 interface { +// ^^^^^^^^^^ definition 0.1.test `sg/testdata`/EmbeddedI3# +// signature_documentation +// > type EmbeddedI3 interface{ ScipTestMethod() } + ScipTestMethod() +// ^^^^^^^^^^^^^^ definition 0.1.test `sg/testdata`/EmbeddedI3#ScipTestMethod. +// signature_documentation +// > func (EmbeddedI3).ScipTestMethod() +// relationship 0.1.test `sg/testdata`/EmbeddedI3#ScipTestMethod. implementation +// relationship 0.1.test `sg/testdata`/I3#ScipTestMethod. implementation } type TClose struct { // ^^^^^^ definition 0.1.test `sg/testdata`/TClose# // signature_documentation -// > type TClose struct{ Closer } -// relationship github.com/golang/go/src go1.22 io/Closer# implementation +// > type TClose struct{ EmbeddedI3 } +// relationship 0.1.test `sg/testdata`/EmbeddedI3# implementation // relationship 0.1.test `sg/testdata`/I3# implementation - io.Closer -// ^^ reference github.com/golang/go/src go1.22 io/ -// ^^^^^^ definition 0.1.test `sg/testdata`/TClose#Closer. -// signature_documentation -// > struct field Closer io.Closer -// ^^^^^^ reference github.com/golang/go/src go1.22 io/Closer# + EmbeddedI3 +// ^^^^^^^^^^ definition 0.1.test `sg/testdata`/TClose#EmbeddedI3. +// signature_documentation +// > struct field EmbeddedI3 sg/testdata.EmbeddedI3 +// ^^^^^^^^^^ reference 0.1.test `sg/testdata`/EmbeddedI3# }