Skip to content

Commit f28cf99

Browse files
committed
gopls/internal/golang: Hover: fix crash on alias to built-in Named
parseFull had an unstated precondition that pos was valid, and this was violated for built-ins. The primary Hover logic rejects built-ins earlier, but the logic for aliases to built-in Named types did not. The fix is to make parseFull take an Object, not a Pos, and support built-in objects. Fixes golang/go#74351 Change-Id: I84cb3ac9f02236dff7bcaa7c4f78c761e6fc7aa5 Reviewed-on: https://go-review.googlesource.com/c/tools/+/683536 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent a0eca0c commit f28cf99

File tree

5 files changed

+63
-25
lines changed

5 files changed

+63
-25
lines changed

gopls/internal/golang/definition.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ func builtinDecl(ctx context.Context, snapshot *cache.Snapshot, obj types.Object
324324
return nil, nil, bug.Errorf("unknown built-in %v", obj)
325325
}
326326
}
327+
_ = ident.Name // ident != nil
327328

328329
return pgf, ident, nil
329330
}

gopls/internal/golang/hover.go

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -147,20 +147,19 @@ func findRhsTypeDecl(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.P
147147
// we choose Rhs instead of types.Unalias to make the connection between original alias
148148
// and the corresponding aliased type clearer.
149149
// types.Unalias brings confusion because it breaks the connection from A to C given
150-
// the alias chain like 'type ( A = B; B =C ; )' except we show all transitive alias
150+
// the alias chain like 'type ( A = B; B = C; )' except we show all transitive alias
151151
// from start to the end. As it's rare, we don't do so.
152-
t := alias.Rhs()
153-
switch o := t.(type) {
154-
case *types.Named:
155-
obj = o.Obj()
156-
declPGF1, declPos1, err := parseFull(ctx, snapshot, pkg.FileSet(), obj.Pos())
152+
if named, ok := alias.Rhs().(*types.Named); ok {
153+
obj = named.Obj()
154+
declPGF1, declPos1, err := parseFull(ctx, snapshot, pkg.FileSet(), obj)
157155
if err != nil {
158156
return "", err
159157
}
160-
realTypeDecl, _, err := typeDeclContent(declPGF1, declPos1, obj)
158+
realTypeDecl, _, err := typeDeclContent(declPGF1, declPos1, obj.Name())
161159
return realTypeDecl, err
162160
}
163161
}
162+
164163
return "", nil
165164
}
166165

@@ -337,7 +336,7 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
337336
// order to correctly compute their documentation, signature, and link.
338337
//
339338
// Beware: decl{PGF,Pos} are not necessarily associated with pkg.FileSet().
340-
declPGF, declPos, err := parseFull(ctx, snapshot, pkg.FileSet(), obj.Pos())
339+
declPGF, declPos, err := parseFull(ctx, snapshot, pkg.FileSet(), obj)
341340
if err != nil {
342341
return protocol.Range{}, nil, fmt.Errorf("re-parsing declaration of %s: %v", obj.Name(), err)
343342
}
@@ -468,7 +467,7 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
468467
_, isTypeParam := types.Unalias(obj.Type()).(*types.TypeParam)
469468
if isTypeName && !isTypeParam {
470469
var spec1 *ast.TypeSpec
471-
typeDecl, spec1, err = typeDeclContent(declPGF, declPos, obj)
470+
typeDecl, spec1, err = typeDeclContent(declPGF, declPos, obj.Name())
472471
if err != nil {
473472
return protocol.Range{}, nil, err
474473
}
@@ -684,7 +683,7 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
684683
}
685684

686685
// typeDeclContent returns a well formatted type definition.
687-
func typeDeclContent(declPGF *parsego.File, declPos token.Pos, obj types.Object) (string, *ast.TypeSpec, error) {
686+
func typeDeclContent(declPGF *parsego.File, declPos token.Pos, name string) (string, *ast.TypeSpec, error) {
688687
_, spec, _ := findDeclInfo([]*ast.File{declPGF.File}, declPos) // may be nil^3
689688
// Don't duplicate comments.
690689
spec1, ok := spec.(*ast.TypeSpec)
@@ -698,7 +697,7 @@ func typeDeclContent(declPGF *parsego.File, declPos token.Pos, obj types.Object)
698697
if !declPGF.Fixed() {
699698
errorf = bug.Errorf
700699
}
701-
return "", nil, errorf("type name %q without type spec", obj.Name())
700+
return "", nil, errorf("type name %q without type spec", name)
702701
}
703702
spec2 := *spec1
704703
spec2.Doc = nil
@@ -1228,7 +1227,7 @@ func HoverDocForObject(ctx context.Context, snapshot *cache.Snapshot, fset *toke
12281227
return nil, nil
12291228
}
12301229

1231-
pgf, pos, err := parseFull(ctx, snapshot, fset, obj.Pos())
1230+
pgf, pos, err := parseFull(ctx, snapshot, fset, obj)
12321231
if err != nil {
12331232
return nil, fmt.Errorf("re-parsing: %v", err)
12341233
}
@@ -1273,21 +1272,23 @@ func chooseDocComment(decl ast.Decl, spec ast.Spec, field *ast.Field) *ast.Comme
12731272
return nil
12741273
}
12751274

1276-
// parseFull fully parses the file corresponding to position pos (for
1277-
// which fset provides file/line information).
1278-
//
1279-
// It returns the resulting parsego.File as well as new pos contained
1280-
// in the parsed file.
1275+
// parseFull fully parses the file containing the declaration of obj
1276+
// (for which fset provides file/line information). It returns the
1277+
// parsego.File and the position of the declaration within it.
12811278
//
12821279
// BEWARE: the provided FileSet is used only to interpret the provided
12831280
// pos; the resulting File and Pos may belong to the same or a
12841281
// different FileSet, such as one synthesized by the parser cache, if
12851282
// parse-caching is enabled.
1286-
//
1287-
// TODO(adonovan): change this function to accept a filename and a
1288-
// byte offset, and eliminate the confusing (fset, pos) parameters.
1289-
// Then simplify stubmethods.StubInfo, which doesn't need a Fset.
1290-
func parseFull(ctx context.Context, snapshot *cache.Snapshot, fset *token.FileSet, pos token.Pos) (*parsego.File, token.Pos, error) {
1283+
func parseFull(ctx context.Context, snapshot *cache.Snapshot, fset *token.FileSet, obj types.Object) (*parsego.File, token.Pos, error) {
1284+
if isBuiltin(obj) {
1285+
pgf, id, err := builtinDecl(ctx, snapshot, obj)
1286+
if err != nil {
1287+
return nil, 0, err
1288+
}
1289+
return pgf, id.Pos(), err
1290+
}
1291+
pos := obj.Pos()
12911292
f := fset.File(pos)
12921293
if f == nil {
12931294
return nil, 0, bug.Errorf("internal error: no file for position %d", pos)
@@ -1304,11 +1305,11 @@ func parseFull(ctx context.Context, snapshot *cache.Snapshot, fset *token.FileSe
13041305
return nil, 0, err
13051306
}
13061307

1308+
// Translate pos from original file to new file.
13071309
offset, err := safetoken.Offset(f, pos)
13081310
if err != nil {
13091311
return nil, 0, bug.Errorf("offset out of bounds in %q", uri)
13101312
}
1311-
13121313
fullPos, err := safetoken.Pos(pgf.Tok, offset)
13131314
if err != nil {
13141315
return nil, 0, err

gopls/internal/golang/stub.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func insertDeclsAfter(ctx context.Context, snapshot *cache.Snapshot, mp *metadat
6666
// Parse the file declaring the sym.
6767
//
6868
// Beware: declPGF is not necessarily covered by pkg.FileSet() or si.Fset.
69-
declPGF, _, err := parseFull(ctx, snapshot, fset, sym.Pos())
69+
declPGF, _, err := parseFull(ctx, snapshot, fset, sym)
7070
if err != nil {
7171
return nil, nil, fmt.Errorf("failed to parse file %q declaring implementation symbol: %w", declPGF.URI, err)
7272
}

gopls/internal/golang/types_format.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ func FormatVarType(ctx context.Context, snapshot *cache.Snapshot, srcpkg *cache.
294294

295295
// TODO(rfindley): parsing to produce candidates can be costly; consider
296296
// using faster methods.
297-
targetpgf, pos, err := parseFull(ctx, snapshot, srcpkg.FileSet(), obj.Pos())
297+
targetpgf, pos, err := parseFull(ctx, snapshot, srcpkg.FileSet(), obj)
298298
if err != nil {
299299
return "", err // e.g. ctx cancelled
300300
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
Regression test for crash in hover on an alias to a built-in named type.
2+
3+
-- flags --
4+
-skip_goarch=386,arm
5+
6+
-- go.mod --
7+
module example.com
8+
9+
go 1.18
10+
11+
-- a/a.go --
12+
package a
13+
14+
type A = error //@hover("A", "A", out)
15+
16+
-- @out --
17+
```go
18+
type A = error // size=16 (0x10)
19+
20+
type error interface {
21+
Error() string
22+
}
23+
```
24+
25+
---
26+
27+
@hover("A", "A", out)
28+
29+
30+
```go
31+
func (error) Error() string
32+
```
33+
34+
---
35+
36+
[`a.A` on pkg.go.dev](https://pkg.go.dev/example.com/a#A)

0 commit comments

Comments
 (0)