Skip to content

Commit 5e6f314

Browse files
tttoadbsiegert
authored andcommitted
gopls/stub: support for method generation with same name but different receiver
Fixes golang/go#64114 Change-Id: I5581bdbe1cbaa08e1e5589a304da5e637c6a5228 Reviewed-on: https://go-review.googlesource.com/c/tools/+/544915 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Suzy Mueller <[email protected]> Reviewed-by: Benny Siegert <[email protected]>
1 parent d47b14c commit 5e6f314

File tree

5 files changed

+115
-12
lines changed

5 files changed

+115
-12
lines changed

gopls/internal/analysis/stubmethods/stubmethods.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
6666
// MatchesMessage reports whether msg matches the error message sought after by
6767
// the stubmethods fix.
6868
func MatchesMessage(msg string) bool {
69-
return strings.Contains(msg, "missing method") || strings.HasPrefix(msg, "cannot convert")
69+
return strings.Contains(msg, "missing method") || strings.HasPrefix(msg, "cannot convert") || strings.Contains(msg, "not implement")
7070
}
7171

7272
// DiagnosticForError computes a diagnostic suggesting to implement an
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package stubmethods_test
6+
7+
import (
8+
"testing"
9+
10+
"golang.org/x/tools/go/analysis/analysistest"
11+
"golang.org/x/tools/gopls/internal/analysis/stubmethods"
12+
)
13+
14+
func Test(t *testing.T) {
15+
testdata := analysistest.TestData()
16+
analysistest.Run(t, testdata, stubmethods.Analyzer, "a")
17+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package stubmethods
6+
7+
var _ I = Y{} // want "Implement I"
8+
9+
type I interface{ F() }
10+
11+
type X struct{}
12+
13+
func (X) F(string) {}
14+
15+
type Y struct{ X }

gopls/internal/lsp/source/stub.go

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,30 @@ func stubMethodsFixer(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.
7575
importEnv[importPath] = name // latest alias wins
7676
}
7777

78+
// Record all direct methods of the current object
79+
concreteFuncs := make(map[string]struct{})
80+
for i := 0; i < si.Concrete.NumMethods(); i++ {
81+
concreteFuncs[si.Concrete.Method(i).Name()] = struct{}{}
82+
}
83+
7884
// Find subset of interface methods that the concrete type lacks.
79-
var missing []*types.Func
8085
ifaceType := si.Interface.Type().Underlying().(*types.Interface)
86+
87+
type missingFn struct {
88+
fn *types.Func
89+
needSubtle string
90+
}
91+
92+
var (
93+
missing []missingFn
94+
concreteStruct, isStruct = si.Concrete.Origin().Underlying().(*types.Struct)
95+
)
96+
8197
for i := 0; i < ifaceType.NumMethods(); i++ {
8298
imethod := ifaceType.Method(i)
83-
cmethod, _, _ := types.LookupFieldOrMethod(si.Concrete, si.Pointer, imethod.Pkg(), imethod.Name())
99+
cmethod, index, _ := types.LookupFieldOrMethod(si.Concrete, si.Pointer, imethod.Pkg(), imethod.Name())
84100
if cmethod == nil {
85-
missing = append(missing, imethod)
101+
missing = append(missing, missingFn{fn: imethod})
86102
continue
87103
}
88104

@@ -92,10 +108,27 @@ func stubMethodsFixer(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.
92108
conc.Name(), imethod.Name())
93109
}
94110

95-
if !types.Identical(cmethod.Type(), imethod.Type()) {
96-
return nil, nil, fmt.Errorf("method %s.%s already exists but has the wrong type: got %s, want %s",
97-
conc.Name(), imethod.Name(), cmethod.Type(), imethod.Type())
111+
if _, exist := concreteFuncs[imethod.Name()]; exist {
112+
if !types.Identical(cmethod.Type(), imethod.Type()) {
113+
return nil, nil, fmt.Errorf("method %s.%s already exists but has the wrong type: got %s, want %s",
114+
conc.Name(), imethod.Name(), cmethod.Type(), imethod.Type())
115+
}
116+
continue
117+
}
118+
119+
mf := missingFn{fn: imethod}
120+
if isStruct && len(index) > 0 {
121+
field := concreteStruct.Field(index[0])
122+
123+
fn := field.Name()
124+
if _, ok := field.Type().(*types.Pointer); ok {
125+
fn = "*" + fn
126+
}
127+
128+
mf.needSubtle = fmt.Sprintf("// Subtle: this method shadows the method (%s).%s of %s.%s.\n", fn, imethod.Name(), si.Concrete.Obj().Name(), field.Name())
98129
}
130+
131+
missing = append(missing, mf)
99132
}
100133
if len(missing) == 0 {
101134
return nil, nil, fmt.Errorf("no missing methods found")
@@ -159,19 +192,20 @@ func stubMethodsFixer(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.
159192

160193
// Format the new methods.
161194
var newMethods bytes.Buffer
162-
for _, method := range missing {
195+
for index := range missing {
163196
fmt.Fprintf(&newMethods, `// %s implements %s.
164-
func (%s%s%s) %s%s {
197+
%sfunc (%s%s%s) %s%s {
165198
panic("unimplemented")
166199
}
167200
`,
168-
method.Name(),
201+
missing[index].fn.Name(),
169202
iface,
203+
missing[index].needSubtle,
170204
star,
171205
si.Concrete.Obj().Name(),
172206
FormatTypeParams(si.Concrete.TypeParams()),
173-
method.Name(),
174-
strings.TrimPrefix(types.TypeString(method.Type(), qual), "func"))
207+
missing[index].fn.Name(),
208+
strings.TrimPrefix(types.TypeString(missing[index].fn.Type(), qual), "func"))
175209
}
176210

177211
// Compute insertion point for new methods:
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
This test verifies that the embedded field has a method with the same name.
2+
3+
-- issue64114.go --
4+
package stub
5+
6+
// Regression test for issue #64114: code action "implement" is not listed.
7+
8+
var _ WriteTest = (*WriteStruct)(nil) //@suggestedfix("(", re"does not implement", issue64114)
9+
10+
type WriterTwoStruct struct{}
11+
12+
// Write implements io.ReadWriter.
13+
func (t *WriterTwoStruct) RRRR(str string) error {
14+
panic("unimplemented")
15+
}
16+
17+
type WriteTest interface {
18+
RRRR()
19+
WWWW()
20+
}
21+
22+
type WriteStruct struct {
23+
WriterTwoStruct
24+
}
25+
-- @issue64114/issue64114.go --
26+
@@ -22 +22,11 @@
27+
+
28+
+// RRRR implements WriteTest.
29+
+// Subtle: this method shadows the method (WriterTwoStruct).RRRR of WriteStruct.WriterTwoStruct.
30+
+func (*WriteStruct) RRRR() {
31+
+ panic("unimplemented")
32+
+}
33+
+
34+
+// WWWW implements WriteTest.
35+
+func (*WriteStruct) WWWW() {
36+
+ panic("unimplemented")
37+
+}

0 commit comments

Comments
 (0)