Skip to content

Commit 4e0c888

Browse files
xieyuschengopherbot
authored andcommitted
gopls/internal/hover: show alias rhs type declaration on hover
This CL support to find the direct Rhs declaration for an alias type in hover. Fixes golang/go#71286 Change-Id: Ie43a70ec52fe41510e303bb538cc170ff59020c0 Reviewed-on: https://go-review.googlesource.com/c/tools/+/644495 Auto-Submit: Robert Findley <[email protected]> Reviewed-by: Robert Findley <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
1 parent 7347766 commit 4e0c888

File tree

3 files changed

+156
-38
lines changed

3 files changed

+156
-38
lines changed

gopls/internal/golang/hover.go

Lines changed: 73 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,28 @@ func Hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, positi
138138
}, nil
139139
}
140140

141+
// findRhsTypeDecl finds an alias's rhs type and returns its declaration.
142+
// The rhs of an alias might be an alias as well, but we feel this is a rare case.
143+
// It returns an empty string if the given obj is not an alias.
144+
func findRhsTypeDecl(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, obj types.Object) (string, error) {
145+
if alias, ok := obj.Type().(*types.Alias); ok {
146+
// we choose Rhs instead of types.Unalias to make the connection between original alias
147+
// and the corresponding aliased type clearer.
148+
// types.Unalias brings confusion because it breaks the connection from A to C given
149+
// the alias chain like 'type ( A = B; B =C ; )' except we show all transitive alias
150+
// from start to the end. As it's rare, we don't do so.
151+
t := alias.Rhs()
152+
switch o := t.(type) {
153+
case *types.Named:
154+
obj = o.Obj()
155+
declPGF1, declPos1, _ := parseFull(ctx, snapshot, pkg.FileSet(), obj.Pos())
156+
realTypeDecl, _, err := typeDeclContent(declPGF1, declPos1, obj)
157+
return realTypeDecl, err
158+
}
159+
}
160+
return "", nil
161+
}
162+
141163
// hover computes hover information at the given position. If we do not support
142164
// hovering at the position, it returns _, nil, nil: an error is only returned
143165
// if the position is valid but we fail to compute hover information.
@@ -404,46 +426,20 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
404426
_, isTypeName := obj.(*types.TypeName)
405427
_, isTypeParam := types.Unalias(obj.Type()).(*types.TypeParam)
406428
if isTypeName && !isTypeParam {
407-
spec, ok := spec.(*ast.TypeSpec)
408-
if !ok {
409-
// We cannot find a TypeSpec for this type or alias declaration
410-
// (that is not a type parameter or a built-in).
411-
// This should be impossible even for ill-formed trees;
412-
// we suspect that AST repair may be creating inconsistent
413-
// positions. Don't report a bug in that case. (#64241)
414-
errorf := fmt.Errorf
415-
if !declPGF.Fixed() {
416-
errorf = bug.Errorf
417-
}
418-
return protocol.Range{}, nil, errorf("type name %q without type spec", obj.Name())
429+
var spec1 *ast.TypeSpec
430+
typeDecl, spec1, err = typeDeclContent(declPGF, declPos, obj)
431+
if err != nil {
432+
return protocol.Range{}, nil, err
419433
}
420434

421-
// Format the type's declaration syntax.
422-
{
423-
// Don't duplicate comments.
424-
spec2 := *spec
425-
spec2.Doc = nil
426-
spec2.Comment = nil
427-
428-
var b strings.Builder
429-
b.WriteString("type ")
430-
fset := tokeninternal.FileSetFor(declPGF.Tok)
431-
// TODO(adonovan): use a smarter formatter that omits
432-
// inaccessible fields (non-exported ones from other packages).
433-
if err := format.Node(&b, fset, &spec2); err != nil {
434-
return protocol.Range{}, nil, err
435-
}
436-
typeDecl = b.String()
437-
438-
// Splice in size/offset at end of first line.
439-
// "type T struct { // size=..."
440-
if sizeOffset != "" {
441-
nl := strings.IndexByte(typeDecl, '\n')
442-
if nl < 0 {
443-
nl = len(typeDecl)
444-
}
445-
typeDecl = typeDecl[:nl] + " // " + sizeOffset + typeDecl[nl:]
435+
// Splice in size/offset at end of first line.
436+
// "type T struct { // size=..."
437+
if sizeOffset != "" {
438+
nl := strings.IndexByte(typeDecl, '\n')
439+
if nl < 0 {
440+
nl = len(typeDecl)
446441
}
442+
typeDecl = typeDecl[:nl] + " // " + sizeOffset + typeDecl[nl:]
447443
}
448444

449445
// Promoted fields
@@ -478,7 +474,7 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
478474
// already been displayed when the node was formatted
479475
// above. Don't list these again.
480476
var skip map[string]bool
481-
if iface, ok := spec.Type.(*ast.InterfaceType); ok {
477+
if iface, ok := spec1.Type.(*ast.InterfaceType); ok {
482478
if iface.Methods.List != nil {
483479
for _, m := range iface.Methods.List {
484480
if len(m.Names) == 1 {
@@ -520,6 +516,12 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
520516
}
521517
}
522518

519+
// realTypeDecl is defined to store the underlying definition of an alias.
520+
realTypeDecl, _ := findRhsTypeDecl(ctx, snapshot, pkg, obj) // tolerate the error
521+
if realTypeDecl != "" {
522+
typeDecl += fmt.Sprintf("\n\n%s", realTypeDecl)
523+
}
524+
523525
// Compute link data (on pkg.go.dev or other documentation host).
524526
//
525527
// If linkPath is empty, the symbol is not linkable.
@@ -640,6 +642,39 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
640642
}, nil
641643
}
642644

645+
// typeDeclContent returns a well formatted type definition.
646+
func typeDeclContent(declPGF *parsego.File, declPos token.Pos, obj types.Object) (string, *ast.TypeSpec, error) {
647+
_, spec, _ := findDeclInfo([]*ast.File{declPGF.File}, declPos) // may be nil^3
648+
// Don't duplicate comments.
649+
spec1, ok := spec.(*ast.TypeSpec)
650+
if !ok {
651+
// We cannot find a TypeSpec for this type or alias declaration
652+
// (that is not a type parameter or a built-in).
653+
// This should be impossible even for ill-formed trees;
654+
// we suspect that AST repair may be creating inconsistent
655+
// positions. Don't report a bug in that case. (#64241)
656+
errorf := fmt.Errorf
657+
if !declPGF.Fixed() {
658+
errorf = bug.Errorf
659+
}
660+
return "", nil, errorf("type name %q without type spec", obj.Name())
661+
}
662+
spec2 := *spec1
663+
spec2.Doc = nil
664+
spec2.Comment = nil
665+
666+
var b strings.Builder
667+
b.WriteString("type ")
668+
fset := tokeninternal.FileSetFor(declPGF.Tok)
669+
// TODO(adonovan): use a smarter formatter that omits
670+
// inaccessible fields (non-exported ones from other packages).
671+
if err := format.Node(&b, fset, &spec2); err != nil {
672+
return "", nil, err
673+
}
674+
typeDecl := b.String()
675+
return typeDecl, spec1, nil
676+
}
677+
643678
// hoverBuiltin computes hover information when hovering over a builtin
644679
// identifier.
645680
func hoverBuiltin(ctx context.Context, snapshot *cache.Snapshot, obj types.Object) (*hoverResult, error) {

gopls/internal/test/marker/testdata/definition/embed.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,8 @@ func (a.A) Hi()
322322
-- @aAlias --
323323
```go
324324
type aAlias = a.A // size=16 (0x10)
325+
326+
type A string
325327
```
326328

327329
---
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
This test checks gopls behavior when hovering over alias type.
2+
3+
-- flags --
4+
-skip_goarch=386,arm
5+
6+
-- go.mod --
7+
module mod.com
8+
9+
-- main.go --
10+
package main
11+
12+
import "mod.com/a"
13+
import "mod.com/b"
14+
15+
type ToTypeDecl = b.RealType //@hover("ToTypeDecl", "ToTypeDecl", ToTypeDecl)
16+
17+
type ToAlias = a.Alias //@hover("ToAlias", "ToAlias", ToAlias)
18+
19+
type ToAliasWithComment = a.AliasWithComment //@hover("ToAliasWithComment", "ToAliasWithComment", ToAliasWithComment)
20+
21+
-- a/a.go --
22+
package a
23+
import "mod.com/b"
24+
25+
type Alias = b.RealType
26+
27+
// AliasWithComment is a type alias with comments.
28+
type AliasWithComment = b.RealType
29+
30+
-- b/b.go --
31+
package b
32+
// RealType is a real type rather than an alias type.
33+
type RealType struct {
34+
Name string
35+
Age int
36+
}
37+
38+
-- @ToTypeDecl --
39+
```go
40+
type ToTypeDecl = b.RealType // size=24 (0x18)
41+
42+
type RealType struct {
43+
Name string
44+
Age int
45+
}
46+
```
47+
48+
---
49+
50+
@hover("ToTypeDecl", "ToTypeDecl", ToTypeDecl)
51+
52+
53+
---
54+
55+
[`main.ToTypeDecl` on pkg.go.dev](https://pkg.go.dev/mod.com#ToTypeDecl)
56+
-- @ToAlias --
57+
```go
58+
type ToAlias = a.Alias // size=24 (0x18)
59+
```
60+
61+
---
62+
63+
@hover("ToAlias", "ToAlias", ToAlias)
64+
65+
66+
---
67+
68+
[`main.ToAlias` on pkg.go.dev](https://pkg.go.dev/mod.com#ToAlias)
69+
-- @ToAliasWithComment --
70+
```go
71+
type ToAliasWithComment = a.AliasWithComment // size=24 (0x18)
72+
```
73+
74+
---
75+
76+
@hover("ToAliasWithComment", "ToAliasWithComment", ToAliasWithComment)
77+
78+
79+
---
80+
81+
[`main.ToAliasWithComment` on pkg.go.dev](https://pkg.go.dev/mod.com#ToAliasWithComment)

0 commit comments

Comments
 (0)