mirror of https://github.com/golang/go.git
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>
This commit is contained in:
parent
3286927895
commit
3ce772872c
|
|
@ -60,7 +60,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
|
|||
endPos = analysisinternal.TypeErrorEndPos(pass.Fset, buf.Bytes(), err.Pos)
|
||||
}
|
||||
path, _ := astutil.PathEnclosingInterval(file, err.Pos, endPos)
|
||||
si := GetStubInfo(pass.TypesInfo, path, pass.Pkg, err.Pos)
|
||||
si := GetStubInfo(pass.TypesInfo, path, err.Pos)
|
||||
if si == nil {
|
||||
continue
|
||||
}
|
||||
|
|
@ -91,20 +91,20 @@ type StubInfo struct {
|
|||
|
||||
// GetStubInfo determines whether the "missing method error"
|
||||
// can be used to deduced what the concrete and interface types are.
|
||||
func GetStubInfo(ti *types.Info, path []ast.Node, pkg *types.Package, pos token.Pos) *StubInfo {
|
||||
func GetStubInfo(ti *types.Info, path []ast.Node, pos token.Pos) *StubInfo {
|
||||
for _, n := range path {
|
||||
switch n := n.(type) {
|
||||
case *ast.ValueSpec:
|
||||
return fromValueSpec(ti, n, pkg, pos)
|
||||
return fromValueSpec(ti, n, pos)
|
||||
case *ast.ReturnStmt:
|
||||
// An error here may not indicate a real error the user should know about, but it may.
|
||||
// Therefore, it would be best to log it out for debugging/reporting purposes instead of ignoring
|
||||
// it. However, event.Log takes a context which is not passed via the analysis package.
|
||||
// TODO(marwan-at-work): properly log this error.
|
||||
si, _ := fromReturnStmt(ti, pos, path, n, pkg)
|
||||
si, _ := fromReturnStmt(ti, pos, path, n)
|
||||
return si
|
||||
case *ast.AssignStmt:
|
||||
return fromAssignStmt(ti, n, pkg, pos)
|
||||
return fromAssignStmt(ti, n, pos)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
@ -115,7 +115,7 @@ func GetStubInfo(ti *types.Info, path []ast.Node, pkg *types.Package, pos token.
|
|||
//
|
||||
// For example, func() io.Writer { return myType{} }
|
||||
// would return StubInfo with the interface being io.Writer and the concrete type being myType{}.
|
||||
func fromReturnStmt(ti *types.Info, pos token.Pos, path []ast.Node, rs *ast.ReturnStmt, pkg *types.Package) (*StubInfo, error) {
|
||||
func fromReturnStmt(ti *types.Info, pos token.Pos, path []ast.Node, rs *ast.ReturnStmt) (*StubInfo, error) {
|
||||
returnIdx := -1
|
||||
for i, r := range rs.Results {
|
||||
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
|
|||
|
||||
// fromValueSpec returns *StubInfo from a variable declaration such as
|
||||
// var x io.Writer = &T{}
|
||||
func fromValueSpec(ti *types.Info, vs *ast.ValueSpec, pkg *types.Package, pos token.Pos) *StubInfo {
|
||||
func fromValueSpec(ti *types.Info, vs *ast.ValueSpec, pos token.Pos) *StubInfo {
|
||||
var idx int
|
||||
for i, vs := range vs.Values {
|
||||
if pos >= vs.Pos() && pos <= vs.End() {
|
||||
|
|
@ -182,7 +182,7 @@ func fromValueSpec(ti *types.Info, vs *ast.ValueSpec, pkg *types.Package, pos to
|
|||
// fromAssignStmt returns *StubInfo from a variable re-assignment such as
|
||||
// var x io.Writer
|
||||
// x = &T{}
|
||||
func fromAssignStmt(ti *types.Info, as *ast.AssignStmt, pkg *types.Package, pos token.Pos) *StubInfo {
|
||||
func fromAssignStmt(ti *types.Info, as *ast.AssignStmt, pos token.Pos) *StubInfo {
|
||||
idx := -1
|
||||
var lhs, rhs ast.Expr
|
||||
// Given a re-assignment interface conversion error,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
package completion
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
|
|
@ -65,7 +64,7 @@ func (c *completer) item(ctx context.Context, cand candidate) (CompletionItem, e
|
|||
x := cand.obj.(*types.TypeName)
|
||||
if named, ok := x.Type().(*types.Named); ok {
|
||||
tp := typeparams.ForNamed(named)
|
||||
label += string(formatTypeParams(tp))
|
||||
label += source.FormatTypeParams(tp)
|
||||
insert = label // maintain invariant above (label == insert)
|
||||
}
|
||||
}
|
||||
|
|
@ -339,19 +338,3 @@ func (c *completer) wantTypeParams() bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func formatTypeParams(tp *typeparams.TypeParamList) []byte {
|
||||
var buf bytes.Buffer
|
||||
if tp == nil || tp.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
buf.WriteByte('[')
|
||||
for i := 0; i < tp.Len(); i++ {
|
||||
if i > 0 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
buf.WriteString(tp.At(i).Obj().Name())
|
||||
}
|
||||
buf.WriteByte(']')
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"golang.org/x/tools/internal/lsp/analysis/stubmethods"
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/span"
|
||||
"golang.org/x/tools/internal/typeparams"
|
||||
)
|
||||
|
||||
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
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("getNodes: %w", err)
|
||||
}
|
||||
si := stubmethods.GetStubInfo(pkg.GetTypesInfo(), nodes, pkg.GetTypes(), pos)
|
||||
si := stubmethods.GetStubInfo(pkg.GetTypesInfo(), nodes, pos)
|
||||
if si == nil {
|
||||
return nil, fmt.Errorf("nil interface request")
|
||||
}
|
||||
|
|
@ -134,7 +135,7 @@ func stubMethods(ctx context.Context, concreteFile *ast.File, si *stubmethods.St
|
|||
_, err = methodsBuffer.Write(printStubMethod(methodData{
|
||||
Method: m.Name(),
|
||||
Concrete: getStubReceiver(si),
|
||||
Interface: deduceIfaceName(si.Concrete.Obj().Pkg(), si.Interface.Pkg(), si.Concrete.Obj(), si.Interface),
|
||||
Interface: deduceIfaceName(si.Concrete.Obj().Pkg(), si.Interface.Pkg(), si.Interface),
|
||||
Signature: strings.TrimPrefix(sig, "func"),
|
||||
}))
|
||||
if err != nil {
|
||||
|
|
@ -159,13 +160,14 @@ func stubErr(ctx context.Context, concreteFile *ast.File, si *stubmethods.StubIn
|
|||
}
|
||||
|
||||
// getStubReceiver returns the concrete type's name as a method receiver.
|
||||
// TODO(marwan-at-work): add type parameters to the receiver when the concrete type
|
||||
// is a generic one.
|
||||
// It accounts for type parameters if they exist.
|
||||
func getStubReceiver(si *stubmethods.StubInfo) string {
|
||||
concrete := si.Concrete.Obj().Name()
|
||||
var concrete string
|
||||
if si.Pointer {
|
||||
concrete = "*" + concrete
|
||||
concrete += "*"
|
||||
}
|
||||
concrete += si.Concrete.Obj().Name()
|
||||
concrete += FormatTypeParams(typeparams.ForNamed(si.Concrete))
|
||||
return concrete
|
||||
}
|
||||
|
||||
|
|
@ -203,7 +205,7 @@ func deducePkgFromTypes(ctx context.Context, snapshot Snapshot, ifaceObj types.O
|
|||
return nil, fmt.Errorf("pkg %q not found", ifaceObj.Pkg().Path())
|
||||
}
|
||||
|
||||
func deduceIfaceName(concretePkg, ifacePkg *types.Package, concreteObj, ifaceObj types.Object) string {
|
||||
func deduceIfaceName(concretePkg, ifacePkg *types.Package, ifaceObj types.Object) string {
|
||||
if concretePkg.Path() == ifacePkg.Path() {
|
||||
return ifaceObj.Name()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -168,6 +168,25 @@ func formatFieldList(ctx context.Context, snapshot Snapshot, list *ast.FieldList
|
|||
return result, writeResultParens
|
||||
}
|
||||
|
||||
// FormatTypeParams turns TypeParamList into its Go representation, such as:
|
||||
// [T, Y]. Note that it does not print constraints as this is mainly used for
|
||||
// formatting type params in method receivers.
|
||||
func FormatTypeParams(tp *typeparams.TypeParamList) string {
|
||||
if tp == nil || tp.Len() == 0 {
|
||||
return ""
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
buf.WriteByte('[')
|
||||
for i := 0; i < tp.Len(); i++ {
|
||||
if i > 0 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
buf.WriteString(tp.At(i).Obj().Name())
|
||||
}
|
||||
buf.WriteByte(']')
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// NewSignature returns formatted signature for a types.Signature struct.
|
||||
func NewSignature(ctx context.Context, s Snapshot, pkg Package, sig *types.Signature, comment *ast.CommentGroup, qf types.Qualifier) *signature {
|
||||
params := make([]string, 0, sig.Params().Len())
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
package stub
|
||||
|
||||
import "io"
|
||||
|
||||
// This file tests that that the stub method generator accounts for concrete
|
||||
// types that have type parameters defined.
|
||||
var _ io.ReaderFrom = &genReader[string, int]{} //@suggestedfix("&genReader", "refactor.rewrite")
|
||||
|
||||
type genReader[T, Y any] struct {
|
||||
T T
|
||||
Y Y
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
-- suggestedfix_stub_generic_receiver_10_23 --
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
package stub
|
||||
|
||||
import "io"
|
||||
|
||||
// This file tests that that the stub method generator accounts for concrete
|
||||
// types that have type parameters defined.
|
||||
var _ io.ReaderFrom = &genReader[string, int]{} //@suggestedfix("&genReader", "refactor.rewrite")
|
||||
|
||||
type genReader[T, Y any] struct {
|
||||
T T
|
||||
Y Y
|
||||
}
|
||||
|
||||
// ReadFrom implements io.ReaderFrom
|
||||
func (*genReader[T, Y]) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ FoldingRangesCount = 2
|
|||
FormatCount = 6
|
||||
ImportCount = 8
|
||||
SemanticTokenCount = 3
|
||||
SuggestedFixCount = 61
|
||||
SuggestedFixCount = 62
|
||||
FunctionExtractionCount = 25
|
||||
MethodExtractionCount = 6
|
||||
DefinitionsCount = 108
|
||||
|
|
|
|||
Loading…
Reference in New Issue