Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0bd0228

Browse files
committedFeb 14, 2023
gopls/internal/lsp/source: don't rely on global FileSet when stubbing
Update the method stubbing logic not to rely on a global FileSet. For golang/go#57987 Change-Id: Ia30067dd69ba88d0433482aaee61e35a79bf6a46 Reviewed-on: https://go-review.googlesource.com/c/tools/+/467798 Run-TryBot: Robert Findley <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Alan Donovan <[email protected]> gopls-CI: kokoro <[email protected]>
1 parent c3550e9 commit 0bd0228

File tree

2 files changed

+41
-24
lines changed

2 files changed

+41
-24
lines changed
 

‎gopls/internal/lsp/analysis/stubmethods/stubmethods.go

Lines changed: 17 additions & 10 deletions
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, err.Pos)
63+
si := GetStubInfo(pass.Fset, pass.TypesInfo, path, err.Pos)
6464
if si == nil {
6565
continue
6666
}
@@ -84,33 +84,34 @@ type StubInfo struct {
8484
// in the case where the concrete type file requires a new import that happens to be renamed
8585
// in the interface file.
8686
// TODO(marwan-at-work): implement interface literals.
87+
Fset *token.FileSet // the FileSet used to type-check the types below
8788
Interface *types.TypeName
8889
Concrete *types.Named
8990
Pointer bool
9091
}
9192

9293
// GetStubInfo determines whether the "missing method error"
9394
// can be used to deduced what the concrete and interface types are.
94-
func GetStubInfo(ti *types.Info, path []ast.Node, pos token.Pos) *StubInfo {
95+
func GetStubInfo(fset *token.FileSet, ti *types.Info, path []ast.Node, pos token.Pos) *StubInfo {
9596
for _, n := range path {
9697
switch n := n.(type) {
9798
case *ast.ValueSpec:
98-
return fromValueSpec(ti, n, pos)
99+
return fromValueSpec(fset, ti, n, pos)
99100
case *ast.ReturnStmt:
100101
// An error here may not indicate a real error the user should know about, but it may.
101102
// Therefore, it would be best to log it out for debugging/reporting purposes instead of ignoring
102103
// it. However, event.Log takes a context which is not passed via the analysis package.
103104
// TODO(marwan-at-work): properly log this error.
104-
si, _ := fromReturnStmt(ti, pos, path, n)
105+
si, _ := fromReturnStmt(fset, ti, pos, path, n)
105106
return si
106107
case *ast.AssignStmt:
107-
return fromAssignStmt(ti, n, pos)
108+
return fromAssignStmt(fset, ti, n, pos)
108109
case *ast.CallExpr:
109110
// Note that some call expressions don't carry the interface type
110111
// because they don't point to a function or method declaration elsewhere.
111112
// For eaxmple, "var Interface = (*Concrete)(nil)". In that case, continue
112113
// this loop to encounter other possibilities such as *ast.ValueSpec or others.
113-
si := fromCallExpr(ti, pos, n)
114+
si := fromCallExpr(fset, ti, pos, n)
114115
if si != nil {
115116
return si
116117
}
@@ -122,7 +123,7 @@ func GetStubInfo(ti *types.Info, path []ast.Node, pos token.Pos) *StubInfo {
122123
// fromCallExpr tries to find an *ast.CallExpr's function declaration and
123124
// analyzes a function call's signature against the passed in parameter to deduce
124125
// the concrete and interface types.
125-
func fromCallExpr(ti *types.Info, pos token.Pos, ce *ast.CallExpr) *StubInfo {
126+
func fromCallExpr(fset *token.FileSet, ti *types.Info, pos token.Pos, ce *ast.CallExpr) *StubInfo {
126127
paramIdx := -1
127128
for i, p := range ce.Args {
128129
if pos >= p.Pos() && pos <= p.End() {
@@ -152,6 +153,7 @@ func fromCallExpr(ti *types.Info, pos token.Pos, ce *ast.CallExpr) *StubInfo {
152153
return nil
153154
}
154155
return &StubInfo{
156+
Fset: fset,
155157
Concrete: concObj,
156158
Pointer: pointer,
157159
Interface: iface,
@@ -163,7 +165,7 @@ func fromCallExpr(ti *types.Info, pos token.Pos, ce *ast.CallExpr) *StubInfo {
163165
//
164166
// For example, func() io.Writer { return myType{} }
165167
// would return StubInfo with the interface being io.Writer and the concrete type being myType{}.
166-
func fromReturnStmt(ti *types.Info, pos token.Pos, path []ast.Node, rs *ast.ReturnStmt) (*StubInfo, error) {
168+
func fromReturnStmt(fset *token.FileSet, ti *types.Info, pos token.Pos, path []ast.Node, rs *ast.ReturnStmt) (*StubInfo, error) {
167169
returnIdx := -1
168170
for i, r := range rs.Results {
169171
if pos >= r.Pos() && pos <= r.End() {
@@ -186,6 +188,7 @@ func fromReturnStmt(ti *types.Info, pos token.Pos, path []ast.Node, rs *ast.Retu
186188
return nil, nil
187189
}
188190
return &StubInfo{
191+
Fset: fset,
189192
Concrete: concObj,
190193
Pointer: pointer,
191194
Interface: iface,
@@ -194,7 +197,7 @@ func fromReturnStmt(ti *types.Info, pos token.Pos, path []ast.Node, rs *ast.Retu
194197

195198
// fromValueSpec returns *StubInfo from a variable declaration such as
196199
// var x io.Writer = &T{}
197-
func fromValueSpec(ti *types.Info, vs *ast.ValueSpec, pos token.Pos) *StubInfo {
200+
func fromValueSpec(fset *token.FileSet, ti *types.Info, vs *ast.ValueSpec, pos token.Pos) *StubInfo {
198201
var idx int
199202
for i, vs := range vs.Values {
200203
if pos >= vs.Pos() && pos <= vs.End() {
@@ -221,6 +224,7 @@ func fromValueSpec(ti *types.Info, vs *ast.ValueSpec, pos token.Pos) *StubInfo {
221224
return nil
222225
}
223226
return &StubInfo{
227+
Fset: fset,
224228
Concrete: concObj,
225229
Interface: ifaceObj,
226230
Pointer: pointer,
@@ -230,7 +234,7 @@ func fromValueSpec(ti *types.Info, vs *ast.ValueSpec, pos token.Pos) *StubInfo {
230234
// fromAssignStmt returns *StubInfo from a variable re-assignment such as
231235
// var x io.Writer
232236
// x = &T{}
233-
func fromAssignStmt(ti *types.Info, as *ast.AssignStmt, pos token.Pos) *StubInfo {
237+
func fromAssignStmt(fset *token.FileSet, ti *types.Info, as *ast.AssignStmt, pos token.Pos) *StubInfo {
234238
idx := -1
235239
var lhs, rhs ast.Expr
236240
// Given a re-assignment interface conversion error,
@@ -263,6 +267,7 @@ func fromAssignStmt(ti *types.Info, as *ast.AssignStmt, pos token.Pos) *StubInfo
263267
return nil
264268
}
265269
return &StubInfo{
270+
Fset: fset,
266271
Concrete: concType,
267272
Interface: ifaceObj,
268273
Pointer: pointer,
@@ -283,6 +288,8 @@ func fromAssignStmt(ti *types.Info, as *ast.AssignStmt, pos token.Pos) *StubInfo
283288
// is presented with a package that is not imported. This is useful so that as types.TypeString is
284289
// formatting a function signature, it is identifying packages that will need to be imported when
285290
// stubbing an interface.
291+
//
292+
// TODO(rfindley): investigate if this can be merged with source.Qualifier.
286293
func RelativeToFiles(concPkg *types.Package, concFile *ast.File, ifaceImports []*ast.ImportSpec, missingImport func(name, path string)) types.Qualifier {
287294
return func(other *types.Package) string {
288295
if other == concPkg {

‎gopls/internal/lsp/source/stub.go

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"golang.org/x/tools/gopls/internal/lsp/protocol"
2323
"golang.org/x/tools/gopls/internal/lsp/safetoken"
2424
"golang.org/x/tools/gopls/internal/span"
25+
"golang.org/x/tools/internal/bug"
2526
"golang.org/x/tools/internal/typeparams"
2627
)
2728

@@ -34,7 +35,7 @@ func stubSuggestedFixFunc(ctx context.Context, snapshot Snapshot, fh FileHandle,
3435
if err != nil {
3536
return nil, nil, fmt.Errorf("getNodes: %w", err)
3637
}
37-
si := stubmethods.GetStubInfo(pkg.GetTypesInfo(), nodes, pos)
38+
si := stubmethods.GetStubInfo(pkg.FileSet(), pkg.GetTypesInfo(), nodes, pos)
3839
if si == nil {
3940
return nil, nil, fmt.Errorf("nil interface request")
4041
}
@@ -47,14 +48,14 @@ func stubSuggestedFixFunc(ctx context.Context, snapshot Snapshot, fh FileHandle,
4748
}
4849

4950
// Parse the file defining the concrete type.
50-
concreteFilename := safetoken.StartPosition(snapshot.FileSet(), si.Concrete.Obj().Pos()).Filename
51+
concreteFilename := safetoken.StartPosition(si.Fset, si.Concrete.Obj().Pos()).Filename
5152
concreteFH, err := snapshot.GetFile(ctx, span.URIFromPath(concreteFilename))
5253
if err != nil {
5354
return nil, nil, err
5455
}
5556
parsedConcreteFile, err := snapshot.ParseGo(ctx, concreteFH, ParseFull)
5657
if err != nil {
57-
return nil, nil, fmt.Errorf("failed to parse file declaring implementation type: %w", err)
58+
return nil, nil, fmt.Errorf("failed to parse file %q declaring implementation type: %w", concreteFH.URI(), err)
5859
}
5960
var (
6061
methodsSrc []byte
@@ -72,21 +73,28 @@ func stubSuggestedFixFunc(ctx context.Context, snapshot Snapshot, fh FileHandle,
7273
// Splice the methods into the file.
7374
// The insertion point is after the top-level declaration
7475
// enclosing the (package-level) type object.
75-
insertPos := parsedConcreteFile.File.End()
76+
insertOffset, err := safetoken.Offset(parsedConcreteFile.Tok, parsedConcreteFile.File.End())
77+
if err != nil {
78+
return nil, nil, bug.Errorf("internal error: end position outside file bounds: %v", err)
79+
}
80+
concOffset, err := safetoken.Offset(pkg.FileSet().File(conc.Pos()), conc.Pos())
81+
if err != nil {
82+
return nil, nil, bug.Errorf("internal error: finding type decl offset: %v", err)
83+
}
7684
for _, decl := range parsedConcreteFile.File.Decls {
77-
if decl.End() > conc.Pos() {
78-
insertPos = decl.End()
85+
declEndOffset, err := safetoken.Offset(parsedConcreteFile.Tok, decl.End())
86+
if err != nil {
87+
return nil, nil, bug.Errorf("internal error: finding decl offset: %v", err)
88+
}
89+
if declEndOffset > concOffset {
90+
insertOffset = declEndOffset
7991
break
8092
}
8193
}
8294
concreteSrc, err := concreteFH.Read()
8395
if err != nil {
8496
return nil, nil, fmt.Errorf("error reading concrete file source: %w", err)
8597
}
86-
insertOffset, err := safetoken.Offset(parsedConcreteFile.Tok, insertPos)
87-
if err != nil || insertOffset >= len(concreteSrc) {
88-
return nil, nil, fmt.Errorf("insertion position is past the end of the file")
89-
}
9098
var buf bytes.Buffer
9199
buf.Write(concreteSrc[:insertOffset])
92100
buf.WriteByte('\n')
@@ -126,7 +134,7 @@ func stubSuggestedFixFunc(ctx context.Context, snapshot Snapshot, fh FileHandle,
126134
// that implement the given interface
127135
func stubMethods(ctx context.Context, concreteFile *ast.File, si *stubmethods.StubInfo, snapshot Snapshot) ([]byte, []*stubImport, error) {
128136
concMS := types.NewMethodSet(types.NewPointer(si.Concrete.Obj().Type()))
129-
missing, err := missingMethods(ctx, snapshot, concMS, si.Concrete.Obj().Pkg(), si.Interface, map[string]struct{}{})
137+
missing, err := missingMethods(ctx, si.Fset, snapshot, concMS, si.Concrete.Obj().Pkg(), si.Interface, map[string]struct{}{})
130138
if err != nil {
131139
return nil, nil, fmt.Errorf("missingMethods: %w", err)
132140
}
@@ -249,8 +257,10 @@ returns
249257
missing: []*types.Func{Hello}
250258
},
251259
}
260+
261+
The provided FileSet must be the FileSet used when type-checking concPkg.
252262
*/
253-
func missingMethods(ctx context.Context, snapshot Snapshot, concMS *types.MethodSet, concPkg *types.Package, ifaceObj *types.TypeName, visited map[string]struct{}) ([]*missingInterface, error) {
263+
func missingMethods(ctx context.Context, fset *token.FileSet, snapshot Snapshot, concMS *types.MethodSet, concPkg *types.Package, ifaceObj *types.TypeName, visited map[string]struct{}) ([]*missingInterface, error) {
254264
iface, ok := ifaceObj.Type().Underlying().(*types.Interface)
255265
if !ok {
256266
return nil, fmt.Errorf("expected %v to be an interface but got %T", iface, ifaceObj.Type().Underlying())
@@ -270,7 +280,7 @@ func missingMethods(ctx context.Context, snapshot Snapshot, concMS *types.Method
270280
}
271281

272282
// Parse the imports from the file that declares the interface.
273-
ifaceFilename := safetoken.StartPosition(snapshot.FileSet(), ifaceObj.Pos()).Filename
283+
ifaceFilename := safetoken.StartPosition(fset, ifaceObj.Pos()).Filename
274284
ifaceFH, err := snapshot.GetFile(ctx, span.URIFromPath(ifaceFilename))
275285
if err != nil {
276286
return nil, err
@@ -311,7 +321,7 @@ func missingMethods(ctx context.Context, snapshot Snapshot, concMS *types.Method
311321
var missingInterfaces []*missingInterface
312322
for i := 0; i < iface.NumEmbeddeds(); i++ {
313323
eiface := iface.Embedded(i).Obj()
314-
em, err := missingMethods(ctx, snapshot, concMS, concPkg, eiface, visited)
324+
em, err := missingMethods(ctx, fset, snapshot, concMS, concPkg, eiface, visited)
315325
if err != nil {
316326
return nil, err
317327
}

0 commit comments

Comments
 (0)
Please sign in to comment.