gopls/internal/lsp/regtest: move @implementation to new framework
This change is a simple migration of the old @implementation tests
into the new marker framework. The tests themselves are logically
unchanged.
Change-Id: I431e13814612180a5500dba18b26ce8c66dbbe8b
Reviewed-on: https://go-review.googlesource.com/c/tools/+/479295
gopls-CI: kokoro <noreply+kokoro@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Alan Donovan <adonovan@google.com>
diff --git a/gopls/internal/lsp/lsp_test.go b/gopls/internal/lsp/lsp_test.go
index dac7407..75e91d2 100644
--- a/gopls/internal/lsp/lsp_test.go
+++ b/gopls/internal/lsp/lsp_test.go
@@ -25,7 +25,6 @@
"golang.org/x/tools/gopls/internal/lsp/tests/compare"
"golang.org/x/tools/gopls/internal/span"
"golang.org/x/tools/internal/bug"
- "golang.org/x/tools/internal/diff"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/testenv"
)
@@ -769,35 +768,6 @@
}
}
-func (r *runner) Implementation(t *testing.T, spn span.Span, wantSpans []span.Span) {
- sm, err := r.data.Mapper(spn.URI())
- if err != nil {
- t.Fatal(err)
- }
- loc, err := sm.SpanLocation(spn)
- if err != nil {
- t.Fatal(err)
- }
- gotImpls, err := r.server.Implementation(r.ctx, &protocol.ImplementationParams{
- TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(loc),
- })
- if err != nil {
- t.Fatalf("Server.Implementation(%s): %v", spn, err)
- }
- gotLocs, err := tests.LocationsToSpans(r.data, gotImpls)
- if err != nil {
- t.Fatal(err)
- }
- sanitize := func(s string) string {
- return strings.ReplaceAll(s, r.data.Config.Dir, "gopls/internal/lsp/testdata")
- }
- want := sanitize(tests.SortAndFormatSpans(wantSpans))
- got := sanitize(tests.SortAndFormatSpans(gotLocs))
- if got != want {
- t.Errorf("implementations(%s):\n%s", sanitize(fmt.Sprint(spn)), diff.Unified("want", "got", want, got))
- }
-}
-
func (r *runner) Highlight(t *testing.T, src span.Span, spans []span.Span) {
m, err := r.data.Mapper(src.URI())
if err != nil {
diff --git a/gopls/internal/lsp/regtest/marker.go b/gopls/internal/lsp/regtest/marker.go
index 8b4c61e..179d40b 100644
--- a/gopls/internal/lsp/regtest/marker.go
+++ b/gopls/internal/lsp/regtest/marker.go
@@ -34,7 +34,6 @@
"golang.org/x/tools/gopls/internal/lsp/source"
"golang.org/x/tools/gopls/internal/lsp/tests"
"golang.org/x/tools/gopls/internal/lsp/tests/compare"
- "golang.org/x/tools/internal/diff"
"golang.org/x/tools/internal/jsonrpc2"
"golang.org/x/tools/internal/jsonrpc2/servertest"
"golang.org/x/tools/internal/testenv"
@@ -130,6 +129,10 @@
// src location, and checks that the result is the dst location, with hover
// content matching "hover.md" in the golden data g.
//
+// - implementations(src location, want ...location): makes a
+// textDocument/implementation query at the src location and
+// checks that the resulting set of locations matches want.
+//
// - loc(name, location): specifies the name for a location in the source. These
// locations may be referenced by other markers.
//
@@ -258,7 +261,6 @@
// - FunctionExtractions
// - MethodExtractions
// - Definitions
-// - Implementations
// - Highlights
// - Renames
// - PrepareRenames
@@ -428,9 +430,7 @@
if i < len(fn.converters) {
convert = fn.converters[i]
} else if !fn.variadic {
- mark.errorf("got %d arguments to %s, expect %d",
- len(mark.note.Args), mark.note.Name, len(fn.converters))
- return
+ goto arity // too many args
}
// Special handling for the blank identifier: treat it as the zero value.
@@ -447,8 +447,16 @@
}
args = append(args, reflect.ValueOf(out))
}
+ if len(args) < len(fn.converters) {
+ goto arity // too few args
+ }
fn.fn.Call(args)
+ return
+
+arity:
+ mark.errorf("got %d arguments to %s, want %d",
+ len(mark.note.Args), mark.note.Name, len(fn.converters))
}
// Supported marker functions.
@@ -459,15 +467,16 @@
// Marker funcs should not mutate the test environment (e.g. via opening files
// or applying edits in the editor).
var markerFuncs = map[string]markerFunc{
- "complete": makeMarkerFunc(completeMarker),
- "def": makeMarkerFunc(defMarker),
- "diag": makeMarkerFunc(diagMarker),
- "hover": makeMarkerFunc(hoverMarker),
- "loc": makeMarkerFunc(locMarker),
- "rename": makeMarkerFunc(renameMarker),
- "renameerr": makeMarkerFunc(renameErrMarker),
- "suggestedfix": makeMarkerFunc(suggestedfixMarker),
- "refs": makeMarkerFunc(refsMarker),
+ "complete": makeMarkerFunc(completeMarker),
+ "def": makeMarkerFunc(defMarker),
+ "diag": makeMarkerFunc(diagMarker),
+ "hover": makeMarkerFunc(hoverMarker),
+ "implementation": makeMarkerFunc(implementationMarker),
+ "loc": makeMarkerFunc(locMarker),
+ "rename": makeMarkerFunc(renameMarker),
+ "renameerr": makeMarkerFunc(renameErrMarker),
+ "suggestedfix": makeMarkerFunc(suggestedfixMarker),
+ "refs": makeMarkerFunc(refsMarker),
}
// markerTest holds all the test data extracted from a test txtar archive.
@@ -1354,37 +1363,17 @@
// refsMarker implements the @refs marker.
func refsMarker(mark marker, src protocol.Location, want ...protocol.Location) {
refs := func(includeDeclaration bool, want []protocol.Location) error {
- params := &protocol.ReferenceParams{
+ got, err := mark.run.env.Editor.Server.References(mark.run.env.Ctx, &protocol.ReferenceParams{
TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(src),
Context: protocol.ReferenceContext{
IncludeDeclaration: includeDeclaration,
},
- }
-
- got, err := mark.run.env.Editor.Server.References(mark.run.env.Ctx, params)
+ })
if err != nil {
return err
}
- // Compare the sets of locations.
- toString := func(locs []protocol.Location) string {
- // TODO(adonovan): use generic JoinValues(locs, fmtLoc).
- strs := make([]string, len(locs))
- for i, loc := range locs {
- strs[i] = mark.run.fmtLoc(loc)
- }
- sort.Strings(strs)
- return strings.Join(strs, "\n")
- }
- gotStr := toString(got)
- wantStr := toString(want)
- if gotStr != wantStr {
- return fmt.Errorf("incorrect references (got %d, want %d) at %s:\n%s",
- len(got), len(want),
- mark.run.fmtLoc(src),
- diff.Unified("want", "got", wantStr, gotStr))
- }
- return nil
+ return compareLocations(mark, got, want)
}
for _, includeDeclaration := range []bool{false, true} {
@@ -1401,3 +1390,35 @@
}
}
}
+
+// implementationMarker implements the @implementation marker.
+func implementationMarker(mark marker, src protocol.Location, want ...protocol.Location) {
+ got, err := mark.run.env.Editor.Server.Implementation(mark.run.env.Ctx, &protocol.ImplementationParams{
+ TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(src),
+ })
+ if err != nil {
+ mark.errorf("implementation at %s failed: %v", src, err)
+ return
+ }
+ if err := compareLocations(mark, got, want); err != nil {
+ mark.errorf("implementation: %v", err)
+ }
+}
+
+// compareLocations returns an error message if got and want are not
+// the same set of locations. The marker is used only for fmtLoc.
+func compareLocations(mark marker, got, want []protocol.Location) error {
+ toStrings := func(locs []protocol.Location) []string {
+ strs := make([]string, len(locs))
+ for i, loc := range locs {
+ strs[i] = mark.run.fmtLoc(loc)
+ }
+ sort.Strings(strs)
+ return strs
+ }
+ if diff := cmp.Diff(toStrings(want), toStrings(got)); diff != "" {
+ return fmt.Errorf("incorrect result locations: (got %d, want %d):\n%s",
+ len(got), len(want), diff)
+ }
+ return nil
+}
diff --git a/gopls/internal/lsp/testdata/implementation/implementation.go b/gopls/internal/lsp/testdata/implementation/implementation.go
deleted file mode 100644
index 4c1a22d..0000000
--- a/gopls/internal/lsp/testdata/implementation/implementation.go
+++ /dev/null
@@ -1,37 +0,0 @@
-package implementation
-
-import "golang.org/lsptests/implementation/other"
-
-type ImpP struct{} //@ImpP,implementations("ImpP", Laugher, OtherLaugher)
-
-func (*ImpP) Laugh() { //@mark(LaughP, "Laugh"),implementations("Laugh", Laugh, OtherLaugh)
-}
-
-type ImpS struct{} //@ImpS,implementations("ImpS", Laugher, OtherLaugher)
-
-func (ImpS) Laugh() { //@mark(LaughS, "Laugh"),implementations("Laugh", Laugh, OtherLaugh)
-}
-
-type Laugher interface { //@Laugher,implementations("Laugher", ImpP, OtherImpP, ImpS, OtherImpS, embedsImpP)
- Laugh() //@Laugh,implementations("Laugh", LaughP, OtherLaughP, LaughS, OtherLaughS)
-}
-
-type Foo struct { //@implementations("Foo", Joker)
- other.Foo
-}
-
-type Joker interface { //@Joker
- Joke() //@Joke,implementations("Joke", ImpJoker)
-}
-
-type cryer int //@implementations("cryer", Cryer)
-
-func (cryer) Cry(other.CryType) {} //@mark(CryImpl, "Cry"),implementations("Cry", Cry)
-
-type Empty interface{} //@implementations("Empty")
-
-var _ interface{ Joke() } //@implementations("Joke", ImpJoker)
-
-type embedsImpP struct { //@embedsImpP
- ImpP //@implementations("ImpP", Laugher, OtherLaugher)
-}
diff --git a/gopls/internal/lsp/testdata/implementation/implementation_generics.go b/gopls/internal/lsp/testdata/implementation/implementation_generics.go
deleted file mode 100644
index 1f02d16..0000000
--- a/gopls/internal/lsp/testdata/implementation/implementation_generics.go
+++ /dev/null
@@ -1,16 +0,0 @@
-//go:build go1.18
-// +build go1.18
-
-package implementation
-
-// -- generics --
-
-type GenIface[T any] interface { //@mark(GenIface, "GenIface"),implementations("GenIface", GC)
- F(int, string, T) //@mark(GenIfaceF, "F"),implementations("F", GCF)
-}
-
-type GenConc[U any] int //@mark(GenConc, "GenConc"),implementations("GenConc", GI)
-
-func (GenConc[V]) F(int, string, V) {} //@mark(GenConcF, "F"),implementations("F", GIF)
-
-type GenConcString struct{ GenConc[string] } //@mark(GenConcString, "GenConcString"),implementations(GenConcString, GIString)
diff --git a/gopls/internal/lsp/testdata/implementation/other/other.go b/gopls/internal/lsp/testdata/implementation/other/other.go
deleted file mode 100644
index aff825e..0000000
--- a/gopls/internal/lsp/testdata/implementation/other/other.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package other
-
-type ImpP struct{} //@mark(OtherImpP, "ImpP")
-
-func (*ImpP) Laugh() { //@mark(OtherLaughP, "Laugh")
-}
-
-type ImpS struct{} //@mark(OtherImpS, "ImpS")
-
-func (ImpS) Laugh() { //@mark(OtherLaughS, "Laugh")
-}
-
-type ImpI interface { //@mark(OtherLaugher, "ImpI")
- Laugh() //@mark(OtherLaugh, "Laugh")
-}
-
-type Foo struct { //@implementations("Foo", Joker)
-}
-
-func (Foo) Joke() { //@mark(ImpJoker, "Joke"),implementations("Joke", Joke)
-}
-
-type CryType int
-
-type Cryer interface { //@Cryer
- Cry(CryType) //@Cry,implementations("Cry", CryImpl)
-}
diff --git a/gopls/internal/lsp/testdata/implementation/other/other_generics.go b/gopls/internal/lsp/testdata/implementation/other/other_generics.go
deleted file mode 100644
index 4b4c29f..0000000
--- a/gopls/internal/lsp/testdata/implementation/other/other_generics.go
+++ /dev/null
@@ -1,16 +0,0 @@
-//go:build go1.18
-// +build go1.18
-
-package other
-
-// -- generics (limited support) --
-
-type GI[T any] interface { //@mark(GI, "GI"),implementations("GI", GenConc)
- F(int, string, T) //@mark(GIF, "F"),implementations("F", GenConcF)
-}
-
-type GIString GI[string] //@mark(GIString, "GIString"),implementations("GIString", GenConcString)
-
-type GC[U any] int //@mark(GC, "GC"),implementations("GC", GenIface)
-
-func (GC[V]) F(int, string, V) {} //@mark(GCF, "F"),implementations("F", GenIfaceF)
diff --git a/gopls/internal/lsp/testdata/implementation/other/other_test.go b/gopls/internal/lsp/testdata/implementation/other/other_test.go
deleted file mode 100644
index 846e0d5..0000000
--- a/gopls/internal/lsp/testdata/implementation/other/other_test.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package other
-
-import (
- "testing"
-)
-
-// This exists so the other.test package comes into existence.
-
-func TestOther(t *testing.T) {
-}
diff --git a/gopls/internal/lsp/testdata/summary.txt.golden b/gopls/internal/lsp/testdata/summary.txt.golden
index bce3cf8..cd9abc8 100644
--- a/gopls/internal/lsp/testdata/summary.txt.golden
+++ b/gopls/internal/lsp/testdata/summary.txt.golden
@@ -26,6 +26,5 @@
WorkspaceSymbolsCount = 20
SignaturesCount = 33
LinksCount = 7
-ImplementationsCount = 16
SelectionRangesCount = 3
diff --git a/gopls/internal/lsp/testdata/summary_go1.18.txt.golden b/gopls/internal/lsp/testdata/summary_go1.18.txt.golden
index 79efbca..d5662f0 100644
--- a/gopls/internal/lsp/testdata/summary_go1.18.txt.golden
+++ b/gopls/internal/lsp/testdata/summary_go1.18.txt.golden
@@ -26,6 +26,5 @@
WorkspaceSymbolsCount = 20
SignaturesCount = 33
LinksCount = 7
-ImplementationsCount = 26
SelectionRangesCount = 3
diff --git a/gopls/internal/lsp/tests/tests.go b/gopls/internal/lsp/tests/tests.go
index 0ac02c8..531240c 100644
--- a/gopls/internal/lsp/tests/tests.go
+++ b/gopls/internal/lsp/tests/tests.go
@@ -82,7 +82,6 @@
type FunctionExtractions = map[span.Span]span.Span
type MethodExtractions = map[span.Span]span.Span
type Definitions = map[span.Span]Definition
-type Implementations = map[span.Span][]span.Span
type Highlights = map[span.Span][]span.Span
type Renames = map[span.Span]string
type PrepareRenames = map[span.Span]*source.PrepareItem
@@ -116,7 +115,6 @@
FunctionExtractions FunctionExtractions
MethodExtractions MethodExtractions
Definitions Definitions
- Implementations Implementations
Highlights Highlights
Renames Renames
InlayHints InlayHints
@@ -164,7 +162,6 @@
FunctionExtraction(*testing.T, span.Span, span.Span)
MethodExtraction(*testing.T, span.Span, span.Span)
Definition(*testing.T, span.Span, Definition)
- Implementation(*testing.T, span.Span, []span.Span)
Highlight(*testing.T, span.Span, []span.Span)
InlayHints(*testing.T, span.Span)
Rename(*testing.T, span.Span, string)
@@ -313,7 +310,6 @@
RankCompletions: make(RankCompletions),
CaseSensitiveCompletions: make(CaseSensitiveCompletions),
Definitions: make(Definitions),
- Implementations: make(Implementations),
Highlights: make(Highlights),
Renames: make(Renames),
PrepareRenames: make(PrepareRenames),
@@ -457,38 +453,37 @@
// Collect any data that needs to be used by subsequent tests.
if err := datum.Exported.Expect(map[string]interface{}{
- "codelens": datum.collectCodeLens,
- "diag": datum.collectDiagnostics,
- "item": datum.collectCompletionItems,
- "complete": datum.collectCompletions(CompletionDefault),
- "unimported": datum.collectCompletions(CompletionUnimported),
- "deep": datum.collectCompletions(CompletionDeep),
- "fuzzy": datum.collectCompletions(CompletionFuzzy),
- "casesensitive": datum.collectCompletions(CompletionCaseSensitive),
- "rank": datum.collectCompletions(CompletionRank),
- "snippet": datum.collectCompletionSnippets,
- "fold": datum.collectFoldingRanges,
- "format": datum.collectFormats,
- "import": datum.collectImports,
- "semantic": datum.collectSemanticTokens,
- "godef": datum.collectDefinitions,
- "implementations": datum.collectImplementations,
- "typdef": datum.collectTypeDefinitions,
- "hoverdef": datum.collectHoverDefinitions,
- "highlight": datum.collectHighlights,
- "inlayHint": datum.collectInlayHints,
- "rename": datum.collectRenames,
- "prepare": datum.collectPrepareRenames,
- "symbol": datum.collectSymbols,
- "signature": datum.collectSignatures,
- "link": datum.collectLinks,
- "suggestedfix": datum.collectSuggestedFixes,
- "extractfunc": datum.collectFunctionExtractions,
- "extractmethod": datum.collectMethodExtractions,
- "incomingcalls": datum.collectIncomingCalls,
- "outgoingcalls": datum.collectOutgoingCalls,
- "addimport": datum.collectAddImports,
- "selectionrange": datum.collectSelectionRanges,
+ "codelens": datum.collectCodeLens,
+ "diag": datum.collectDiagnostics,
+ "item": datum.collectCompletionItems,
+ "complete": datum.collectCompletions(CompletionDefault),
+ "unimported": datum.collectCompletions(CompletionUnimported),
+ "deep": datum.collectCompletions(CompletionDeep),
+ "fuzzy": datum.collectCompletions(CompletionFuzzy),
+ "casesensitive": datum.collectCompletions(CompletionCaseSensitive),
+ "rank": datum.collectCompletions(CompletionRank),
+ "snippet": datum.collectCompletionSnippets,
+ "fold": datum.collectFoldingRanges,
+ "format": datum.collectFormats,
+ "import": datum.collectImports,
+ "semantic": datum.collectSemanticTokens,
+ "godef": datum.collectDefinitions,
+ "typdef": datum.collectTypeDefinitions,
+ "hoverdef": datum.collectHoverDefinitions,
+ "highlight": datum.collectHighlights,
+ "inlayHint": datum.collectInlayHints,
+ "rename": datum.collectRenames,
+ "prepare": datum.collectPrepareRenames,
+ "symbol": datum.collectSymbols,
+ "signature": datum.collectSignatures,
+ "link": datum.collectLinks,
+ "suggestedfix": datum.collectSuggestedFixes,
+ "extractfunc": datum.collectFunctionExtractions,
+ "extractmethod": datum.collectMethodExtractions,
+ "incomingcalls": datum.collectIncomingCalls,
+ "outgoingcalls": datum.collectOutgoingCalls,
+ "addimport": datum.collectAddImports,
+ "selectionrange": datum.collectSelectionRanges,
}); err != nil {
t.Fatal(err)
}
@@ -752,16 +747,6 @@
}
})
- t.Run("Implementation", func(t *testing.T) {
- t.Helper()
- for spn, m := range data.Implementations {
- t.Run(SpanName(spn), func(t *testing.T) {
- t.Helper()
- tests.Implementation(t, spn, m)
- })
- }
- })
-
t.Run("Highlight", func(t *testing.T) {
t.Helper()
for pos, locations := range data.Highlights {
@@ -1010,7 +995,6 @@
fmt.Fprintf(buf, "WorkspaceSymbolsCount = %v\n", countWorkspaceSymbols(data.WorkspaceSymbols))
fmt.Fprintf(buf, "SignaturesCount = %v\n", len(data.Signatures))
fmt.Fprintf(buf, "LinksCount = %v\n", linksCount)
- fmt.Fprintf(buf, "ImplementationsCount = %v\n", len(data.Implementations))
fmt.Fprintf(buf, "SelectionRangesCount = %v\n", len(data.SelectionRanges))
want := string(data.Golden(t, "summary", summaryFile, func() ([]byte, error) {
@@ -1217,10 +1201,6 @@
data.SelectionRanges = append(data.SelectionRanges, spn)
}
-func (data *Data) collectImplementations(src span.Span, targets []span.Span) {
- data.Implementations[src] = targets
-}
-
func (data *Data) collectIncomingCalls(src span.Span, calls []span.Span) {
for _, call := range calls {
rng := data.mustRange(call)
diff --git a/gopls/internal/lsp/tests/util.go b/gopls/internal/lsp/tests/util.go
index fd65ecb..deadfa8 100644
--- a/gopls/internal/lsp/tests/util.go
+++ b/gopls/internal/lsp/tests/util.go
@@ -518,30 +518,3 @@
return source.SymbolCaseInsensitive
}
}
-
-// LocationsToSpans converts protocol location into span form for testing.
-func LocationsToSpans(data *Data, locs []protocol.Location) ([]span.Span, error) {
- spans := make([]span.Span, len(locs))
- for i, loc := range locs {
- m, err := data.Mapper(loc.URI.SpanURI())
- if err != nil {
- return nil, err
- }
- spn, err := m.LocationSpan(loc)
- if err != nil {
- return nil, fmt.Errorf("failed for %v: %w", loc, err)
- }
- spans[i] = spn
- }
- return spans, nil
-}
-
-// SortAndFormatSpans sorts and formats a list of spans for use in an assertion.
-func SortAndFormatSpans(spans []span.Span) string {
- span.SortSpans(spans)
- var buf strings.Builder
- for _, spn := range spans {
- fmt.Fprintf(&buf, "%v\n", spn)
- }
- return buf.String()
-}
diff --git a/gopls/internal/regtest/marker/testdata/implementation/basic.txt b/gopls/internal/regtest/marker/testdata/implementation/basic.txt
new file mode 100644
index 0000000..3f63a5d
--- /dev/null
+++ b/gopls/internal/regtest/marker/testdata/implementation/basic.txt
@@ -0,0 +1,73 @@
+Basic test of implementation query.
+
+-- go.mod --
+module example.com
+go 1.12
+
+-- implementation/implementation.go --
+package implementation
+
+import "example.com/other"
+
+type ImpP struct{} //@loc(ImpP, "ImpP"),implementation("ImpP", Laugher, OtherLaugher)
+
+func (*ImpP) Laugh() { //@loc(LaughP, "Laugh"),implementation("Laugh", Laugh, OtherLaugh)
+}
+
+type ImpS struct{} //@loc(ImpS, "ImpS"),implementation("ImpS", Laugher, OtherLaugher)
+
+func (ImpS) Laugh() { //@loc(LaughS, "Laugh"),implementation("Laugh", Laugh, OtherLaugh)
+}
+
+type Laugher interface { //@loc(Laugher, "Laugher"),implementation("Laugher", ImpP, OtherImpP, ImpS, OtherImpS, embedsImpP)
+ Laugh() //@loc(Laugh, "Laugh"),implementation("Laugh", LaughP, OtherLaughP, LaughS, OtherLaughS)
+}
+
+type Foo struct { //@implementation("Foo", Joker)
+ other.Foo
+}
+
+type Joker interface { //@loc(Joker, "Joker")
+ Joke() //@loc(Joke, "Joke"),implementation("Joke", ImpJoker)
+}
+
+type cryer int //@implementation("cryer", Cryer)
+
+func (cryer) Cry(other.CryType) {} //@loc(CryImpl, "Cry"),implementation("Cry", Cry)
+
+type Empty interface{} //@implementation("Empty")
+
+var _ interface{ Joke() } //@implementation("Joke", ImpJoker)
+
+type embedsImpP struct { //@loc(embedsImpP, "embedsImpP")
+ ImpP //@implementation("ImpP", Laugher, OtherLaugher)
+}
+
+-- other/other.go --
+package other
+
+type ImpP struct{} //@loc(OtherImpP, "ImpP")
+
+func (*ImpP) Laugh() { //@loc(OtherLaughP, "Laugh")
+}
+
+type ImpS struct{} //@loc(OtherImpS, "ImpS")
+
+func (ImpS) Laugh() { //@loc(OtherLaughS, "Laugh")
+}
+
+type ImpI interface { //@loc(OtherLaugher, "ImpI")
+ Laugh() //@loc(OtherLaugh, "Laugh")
+}
+
+type Foo struct { //@implementation("Foo", Joker)
+}
+
+func (Foo) Joke() { //@loc(ImpJoker, "Joke"),implementation("Joke", Joke)
+}
+
+type CryType int
+
+type Cryer interface { //@loc(Cryer, "Cryer")
+ Cry(CryType) //@loc(Cry, "Cry"),implementation("Cry", CryImpl)
+}
diff --git a/gopls/internal/regtest/marker/testdata/implementation/generics.txt b/gopls/internal/regtest/marker/testdata/implementation/generics.txt
new file mode 100644
index 0000000..2a9fcb8
--- /dev/null
+++ b/gopls/internal/regtest/marker/testdata/implementation/generics.txt
@@ -0,0 +1,34 @@
+Test of 'implementation' query on generic types.
+
+-- flags --
+-min_go=go1.18
+
+-- go.mod --
+module example.com
+go 1.18
+
+-- implementation/implementation.go --
+package implementation
+
+type GenIface[T any] interface { //@loc(GenIface, "GenIface"),implementation("GenIface", GC)
+ F(int, string, T) //@loc(GenIfaceF, "F"),implementation("F", GCF)
+}
+
+type GenConc[U any] int //@loc(GenConc, "GenConc"),implementation("GenConc", GI)
+
+func (GenConc[V]) F(int, string, V) {} //@loc(GenConcF, "F"),implementation("F", GIF)
+
+type GenConcString struct{ GenConc[string] } //@loc(GenConcString, "GenConcString"),implementation(GenConcString, GIString)
+
+-- other/other.go --
+package other
+
+type GI[T any] interface { //@loc(GI, "GI"),implementation("GI", GenConc)
+ F(int, string, T) //@loc(GIF, "F"),implementation("F", GenConcF)
+}
+
+type GIString GI[string] //@loc(GIString, "GIString"),implementation("GIString", GenConcString)
+
+type GC[U any] int //@loc(GC, "GC"),implementation("GC", GenIface)
+
+func (GC[V]) F(int, string, V) {} //@loc(GCF, "F"),implementation("F", GenIfaceF)