Skip to content

Commit 3ce7728

Browse files
marwan-at-workfindleyr
authored andcommitted
internal/lsp/source: support stubbing concrete type params
This CL adds support for generating method stubs for named types that have type parameters and want to implement an interface. See internal/lsp/testdata/stub/stub_generic_receiver.go for an example. Note, this CL does not yet support type params on interface declarations. Updates golang/go#37537 Change-Id: I2a2a18d364b2b489e2fbd8a74dfed88ae32d83b5 Reviewed-on: https://go-review.googlesource.com/c/tools/+/389654 Trust: Robert Findley <rfindley@google.com> Run-TryBot: Robert Findley <rfindley@google.com> Reviewed-by: Robert Findley <rfindley@google.com> gopls-CI: kokoro <noreply+kokoro@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Trust: Suzy Mueller <suzmue@golang.org>
1 parent 3286927 commit 3ce7728

File tree

7 files changed

+75
-34
lines changed

7 files changed

+75
-34
lines changed

Diff for: internal/lsp/analysis/stubmethods/stubmethods.go

+8-8
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
6060
endPos = analysisinternal.TypeErrorEndPos(pass.Fset, buf.Bytes(), err.Pos)
6161
}
6262
path, _ := astutil.PathEnclosingInterval(file, err.Pos, endPos)
63-
si := GetStubInfo(pass.TypesInfo, path, pass.Pkg, err.Pos)
63+
si := GetStubInfo(pass.TypesInfo, path, err.Pos)
6464
if si == nil {
6565
continue
6666
}
@@ -91,20 +91,20 @@ type StubInfo struct {
9191

9292
// GetStubInfo determines whether the "missing method error"
9393
// can be used to deduced what the concrete and interface types are.
94-
func GetStubInfo(ti *types.Info, path []ast.Node, pkg *types.Package, pos token.Pos) *StubInfo {
94+
func GetStubInfo(ti *types.Info, path []ast.Node, pos token.Pos) *StubInfo {
9595
for _, n := range path {
9696
switch n := n.(type) {
9797
case *ast.ValueSpec:
98-
return fromValueSpec(ti, n, pkg, pos)
98+
return fromValueSpec(ti, n, pos)
9999
case *ast.ReturnStmt:
100100
// An error here may not indicate a real error the user should know about, but it may.
101101
// Therefore, it would be best to log it out for debugging/reporting purposes instead of ignoring
102102
// it. However, event.Log takes a context which is not passed via the analysis package.
103103
// TODO(marwan-at-work): properly log this error.
104-
si, _ := fromReturnStmt(ti, pos, path, n, pkg)
104+
si, _ := fromReturnStmt(ti, pos, path, n)
105105
return si
106106
case *ast.AssignStmt:
107-
return fromAssignStmt(ti, n, pkg, pos)
107+
return fromAssignStmt(ti, n, pos)
108108
}
109109
}
110110
return nil
@@ -115,7 +115,7 @@ func GetStubInfo(ti *types.Info, path []ast.Node, pkg *types.Package, pos token.
115115
//
116116
// For example, func() io.Writer { return myType{} }
117117
// would return StubInfo with the interface being io.Writer and the concrete type being myType{}.
118-
func fromReturnStmt(ti *types.Info, pos token.Pos, path []ast.Node, rs *ast.ReturnStmt, pkg *types.Package) (*StubInfo, error) {
118+
func fromReturnStmt(ti *types.Info, pos token.Pos, path []ast.Node, rs *ast.ReturnStmt) (*StubInfo, error) {
119119
returnIdx := -1
120120
for i, r := range rs.Results {
121121
if pos >= r.Pos() && pos <= r.End() {
@@ -146,7 +146,7 @@ func fromReturnStmt(ti *types.Info, pos token.Pos, path []ast.Node, rs *ast.Retu
146146

147147
// fromValueSpec returns *StubInfo from a variable declaration such as
148148
// var x io.Writer = &T{}
149-
func fromValueSpec(ti *types.Info, vs *ast.ValueSpec, pkg *types.Package, pos token.Pos) *StubInfo {
149+
func fromValueSpec(ti *types.Info, vs *ast.ValueSpec, pos token.Pos) *StubInfo {
150150
var idx int
151151
for i, vs := range vs.Values {
152152
if pos >= vs.Pos() && pos <= vs.End() {
@@ -182,7 +182,7 @@ func fromValueSpec(ti *types.Info, vs *ast.ValueSpec, pkg *types.Package, pos to
182182
// fromAssignStmt returns *StubInfo from a variable re-assignment such as
183183
// var x io.Writer
184184
// x = &T{}
185-
func fromAssignStmt(ti *types.Info, as *ast.AssignStmt, pkg *types.Package, pos token.Pos) *StubInfo {
185+
func fromAssignStmt(ti *types.Info, as *ast.AssignStmt, pos token.Pos) *StubInfo {
186186
idx := -1
187187
var lhs, rhs ast.Expr
188188
// Given a re-assignment interface conversion error,

Diff for: internal/lsp/source/completion/format.go

+1-18
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
package completion
66

77
import (
8-
"bytes"
98
"context"
109
"fmt"
1110
"go/ast"
@@ -65,7 +64,7 @@ func (c *completer) item(ctx context.Context, cand candidate) (CompletionItem, e
6564
x := cand.obj.(*types.TypeName)
6665
if named, ok := x.Type().(*types.Named); ok {
6766
tp := typeparams.ForNamed(named)
68-
label += string(formatTypeParams(tp))
67+
label += source.FormatTypeParams(tp)
6968
insert = label // maintain invariant above (label == insert)
7069
}
7170
}
@@ -339,19 +338,3 @@ func (c *completer) wantTypeParams() bool {
339338
}
340339
return false
341340
}
342-
343-
func formatTypeParams(tp *typeparams.TypeParamList) []byte {
344-
var buf bytes.Buffer
345-
if tp == nil || tp.Len() == 0 {
346-
return nil
347-
}
348-
buf.WriteByte('[')
349-
for i := 0; i < tp.Len(); i++ {
350-
if i > 0 {
351-
buf.WriteString(", ")
352-
}
353-
buf.WriteString(tp.At(i).Obj().Name())
354-
}
355-
buf.WriteByte(']')
356-
return buf.Bytes()
357-
}

Diff for: internal/lsp/source/stub.go

+9-7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"golang.org/x/tools/internal/lsp/analysis/stubmethods"
2121
"golang.org/x/tools/internal/lsp/protocol"
2222
"golang.org/x/tools/internal/span"
23+
"golang.org/x/tools/internal/typeparams"
2324
)
2425

2526
func stubSuggestedFixFunc(ctx context.Context, snapshot Snapshot, fh VersionedFileHandle, rng protocol.Range) (*analysis.SuggestedFix, error) {
@@ -31,7 +32,7 @@ func stubSuggestedFixFunc(ctx context.Context, snapshot Snapshot, fh VersionedFi
3132
if err != nil {
3233
return nil, fmt.Errorf("getNodes: %w", err)
3334
}
34-
si := stubmethods.GetStubInfo(pkg.GetTypesInfo(), nodes, pkg.GetTypes(), pos)
35+
si := stubmethods.GetStubInfo(pkg.GetTypesInfo(), nodes, pos)
3536
if si == nil {
3637
return nil, fmt.Errorf("nil interface request")
3738
}
@@ -134,7 +135,7 @@ func stubMethods(ctx context.Context, concreteFile *ast.File, si *stubmethods.St
134135
_, err = methodsBuffer.Write(printStubMethod(methodData{
135136
Method: m.Name(),
136137
Concrete: getStubReceiver(si),
137-
Interface: deduceIfaceName(si.Concrete.Obj().Pkg(), si.Interface.Pkg(), si.Concrete.Obj(), si.Interface),
138+
Interface: deduceIfaceName(si.Concrete.Obj().Pkg(), si.Interface.Pkg(), si.Interface),
138139
Signature: strings.TrimPrefix(sig, "func"),
139140
}))
140141
if err != nil {
@@ -159,13 +160,14 @@ func stubErr(ctx context.Context, concreteFile *ast.File, si *stubmethods.StubIn
159160
}
160161

161162
// getStubReceiver returns the concrete type's name as a method receiver.
162-
// TODO(marwan-at-work): add type parameters to the receiver when the concrete type
163-
// is a generic one.
163+
// It accounts for type parameters if they exist.
164164
func getStubReceiver(si *stubmethods.StubInfo) string {
165-
concrete := si.Concrete.Obj().Name()
165+
var concrete string
166166
if si.Pointer {
167-
concrete = "*" + concrete
167+
concrete += "*"
168168
}
169+
concrete += si.Concrete.Obj().Name()
170+
concrete += FormatTypeParams(typeparams.ForNamed(si.Concrete))
169171
return concrete
170172
}
171173

@@ -203,7 +205,7 @@ func deducePkgFromTypes(ctx context.Context, snapshot Snapshot, ifaceObj types.O
203205
return nil, fmt.Errorf("pkg %q not found", ifaceObj.Pkg().Path())
204206
}
205207

206-
func deduceIfaceName(concretePkg, ifacePkg *types.Package, concreteObj, ifaceObj types.Object) string {
208+
func deduceIfaceName(concretePkg, ifacePkg *types.Package, ifaceObj types.Object) string {
207209
if concretePkg.Path() == ifacePkg.Path() {
208210
return ifaceObj.Name()
209211
}

Diff for: internal/lsp/source/types_format.go

+19
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,25 @@ func formatFieldList(ctx context.Context, snapshot Snapshot, list *ast.FieldList
168168
return result, writeResultParens
169169
}
170170

171+
// FormatTypeParams turns TypeParamList into its Go representation, such as:
172+
// [T, Y]. Note that it does not print constraints as this is mainly used for
173+
// formatting type params in method receivers.
174+
func FormatTypeParams(tp *typeparams.TypeParamList) string {
175+
if tp == nil || tp.Len() == 0 {
176+
return ""
177+
}
178+
var buf bytes.Buffer
179+
buf.WriteByte('[')
180+
for i := 0; i < tp.Len(); i++ {
181+
if i > 0 {
182+
buf.WriteString(", ")
183+
}
184+
buf.WriteString(tp.At(i).Obj().Name())
185+
}
186+
buf.WriteByte(']')
187+
return buf.String()
188+
}
189+
171190
// NewSignature returns formatted signature for a types.Signature struct.
172191
func NewSignature(ctx context.Context, s Snapshot, pkg Package, sig *types.Signature, comment *ast.CommentGroup, qf types.Qualifier) *signature {
173192
params := make([]string, 0, sig.Params().Len())

Diff for: internal/lsp/testdata/stub/stub_generic_receiver.go

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//go:build go1.18
2+
// +build go1.18
3+
4+
package stub
5+
6+
import "io"
7+
8+
// This file tests that that the stub method generator accounts for concrete
9+
// types that have type parameters defined.
10+
var _ io.ReaderFrom = &genReader[string, int]{} //@suggestedfix("&genReader", "refactor.rewrite")
11+
12+
type genReader[T, Y any] struct {
13+
T T
14+
Y Y
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-- suggestedfix_stub_generic_receiver_10_23 --
2+
//go:build go1.18
3+
// +build go1.18
4+
5+
package stub
6+
7+
import "io"
8+
9+
// This file tests that that the stub method generator accounts for concrete
10+
// types that have type parameters defined.
11+
var _ io.ReaderFrom = &genReader[string, int]{} //@suggestedfix("&genReader", "refactor.rewrite")
12+
13+
type genReader[T, Y any] struct {
14+
T T
15+
Y Y
16+
}
17+
18+
// ReadFrom implements io.ReaderFrom
19+
func (*genReader[T, Y]) ReadFrom(r io.Reader) (n int64, err error) {
20+
panic("unimplemented")
21+
}
22+

Diff for: internal/lsp/testdata/summary_go1.18.txt.golden

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ FoldingRangesCount = 2
1313
FormatCount = 6
1414
ImportCount = 8
1515
SemanticTokenCount = 3
16-
SuggestedFixCount = 61
16+
SuggestedFixCount = 62
1717
FunctionExtractionCount = 25
1818
MethodExtractionCount = 6
1919
DefinitionsCount = 108

0 commit comments

Comments
 (0)