Skip to content

Commit 7c33a56

Browse files
ShoshinNikitafindleyr
authored andcommitted
gopls/internal/lsp/source: show both the original declaration and the value of constants in hover
This change improves the hover information for constants by showing both the original declaration and the value. The value is displayed as an inline comment. If the original declaration and the value are the same, there will be no inline comment. Examples: ```go const duration time.Duration = 15*time.Minute + 10*time.Second // 15m10s const octal untyped int = 0o777 // 511 const expr untyped int = 2 << (0b111&0b101 - 2) // 16 const boolean untyped bool = (55 - 3) == (26 * 2) // true const dec untyped int = 500 ``` Other changes: - Calls to `objectString` that format methods or variables have been replaced with `types.ObjectString`. - The logic of inferred signature formatting has been extracted from `objectString` to a new function `inferredSignatureString`. - Remove unused function `extractFieldList`. - Port `gopls/internal/lsp/testdata/godef/a/g.go` to the new marker tests. - Fix `-min_go` constraint for marker tests by replacing hardcoded `1.18` with an actual flag value. Fixes golang/go#47453 Change-Id: Ib9012dae8554a3b646c3059d2f8967e425974fbf GitHub-Last-Rev: 8cc75a7 GitHub-Pull-Request: #432 Reviewed-on: https://go-review.googlesource.com/c/tools/+/480695 Run-TryBot: Robert Findley <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Michael Knyszek <[email protected]> gopls-CI: kokoro <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent 4d205d8 commit 7c33a56

File tree

8 files changed

+209
-60
lines changed

8 files changed

+209
-60
lines changed

gopls/internal/lsp/regtest/marker.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ func RunMarkerTests(t *testing.T, dir string) {
308308
if _, err := fmt.Sscanf(test.minGoVersion, "go1.%d", &go1point); err != nil {
309309
t.Fatalf("parsing -min_go version: %v", err)
310310
}
311-
testenv.NeedsGo1Point(t, 18)
311+
testenv.NeedsGo1Point(t, go1point)
312312
}
313313
config := fake.EditorConfig{
314314
Settings: test.settings,

gopls/internal/lsp/source/hover.go

+65-42
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ func hover(ctx context.Context, snapshot Snapshot, fh FileHandle, pp protocol.Po
143143
// There's not much useful information to provide.
144144
if selectedType != nil {
145145
fakeObj := types.NewVar(obj.Pos(), obj.Pkg(), obj.Name(), selectedType)
146-
signature := objectString(fakeObj, qf, nil)
146+
signature := types.ObjectString(fakeObj, qf)
147147
return rng, &HoverJSON{
148148
Signature: signature,
149149
SingleLine: signature,
@@ -168,10 +168,14 @@ func hover(ctx context.Context, snapshot Snapshot, fh FileHandle, pp protocol.Po
168168
docText := comment.Text()
169169

170170
// By default, types.ObjectString provides a reasonable signature.
171-
signature := objectString(obj, qf, nil)
171+
signature := objectString(obj, qf, declPos, declPGF.Tok, spec)
172+
singleLineSignature := signature
173+
172174
// TODO(rfindley): we could do much better for inferred signatures.
173175
if inferred := inferredSignature(pkg.GetTypesInfo(), ident); inferred != nil {
174-
signature = objectString(obj, qf, inferred)
176+
if s := inferredSignatureString(obj, qf, inferred); s != "" {
177+
signature = s
178+
}
175179
}
176180

177181
// For "objects defined by a type spec", the signature produced by
@@ -214,7 +218,7 @@ func hover(ctx context.Context, snapshot Snapshot, fh FileHandle, pp protocol.Po
214218
if (m.Obj().Exported() || m.Obj().Pkg() == pkg.GetTypes()) && len(m.Index()) == 1 {
215219
b.WriteString(sep)
216220
sep = "\n"
217-
b.WriteString(objectString(m.Obj(), qf, nil))
221+
b.WriteString(types.ObjectString(m.Obj(), qf))
218222
}
219223
}
220224
}
@@ -321,7 +325,7 @@ func hover(ctx context.Context, snapshot Snapshot, fh FileHandle, pp protocol.Po
321325
return rng, &HoverJSON{
322326
Synopsis: doc.Synopsis(docText),
323327
FullDocumentation: docText,
324-
SingleLine: objectString(obj, qf, nil),
328+
SingleLine: singleLineSignature,
325329
SymbolName: linkName,
326330
Signature: signature,
327331
LinkPath: linkPath,
@@ -577,12 +581,10 @@ func hoverLit(pgf *ParsedGoFile, lit *ast.BasicLit, pos token.Pos) (protocol.Ran
577581
}, nil
578582
}
579583

580-
// objectString is a wrapper around the types.ObjectString function.
581-
// It handles adding more information to the object string.
582-
//
583-
// TODO(rfindley): this function does too much. We should lift the special
584-
// handling to callsites.
585-
func objectString(obj types.Object, qf types.Qualifier, inferred *types.Signature) string {
584+
// inferredSignatureString is a wrapper around the types.ObjectString function
585+
// that adds more information to inferred signatures. It will return an empty string
586+
// if the passed types.Object is not a signature.
587+
func inferredSignatureString(obj types.Object, qf types.Qualifier, inferred *types.Signature) string {
586588
// If the signature type was inferred, prefer the inferred signature with a
587589
// comment showing the generic signature.
588590
if sig, _ := obj.Type().(*types.Signature); sig != nil && typeparams.ForSignature(sig).Len() > 0 && inferred != nil {
@@ -597,22 +599,65 @@ func objectString(obj types.Object, qf types.Qualifier, inferred *types.Signatur
597599
str += "// " + types.TypeString(sig, qf)
598600
return str
599601
}
602+
return ""
603+
}
604+
605+
// objectString is a wrapper around the types.ObjectString function.
606+
// It handles adding more information to the object string.
607+
// If spec is non-nil, it may be used to format additional declaration
608+
// syntax, and file must be the token.File describing its positions.
609+
func objectString(obj types.Object, qf types.Qualifier, declPos token.Pos, file *token.File, spec ast.Spec) string {
600610
str := types.ObjectString(obj, qf)
611+
601612
switch obj := obj.(type) {
602613
case *types.Const:
603-
str = fmt.Sprintf("%s = %s", str, obj.Val())
614+
var (
615+
declaration = obj.Val().String() // default formatted declaration
616+
comment = "" // if non-empty, a clarifying comment
617+
)
604618

605-
// Try to add a formatted duration as an inline comment
606-
typ, ok := obj.Type().(*types.Named)
607-
if !ok {
608-
break
619+
// Try to use the original declaration.
620+
switch obj.Val().Kind() {
621+
case constant.String:
622+
// Usually the original declaration of a string doesn't carry much information.
623+
// Also strings can be very long. So, just use the constant's value.
624+
625+
default:
626+
if spec, _ := spec.(*ast.ValueSpec); spec != nil {
627+
for i, name := range spec.Names {
628+
if declPos == name.Pos() {
629+
if i < len(spec.Values) {
630+
originalDeclaration := FormatNodeFile(file, spec.Values[i])
631+
if originalDeclaration != declaration {
632+
comment = declaration
633+
declaration = originalDeclaration
634+
}
635+
}
636+
break
637+
}
638+
}
639+
}
609640
}
610-
pkg := typ.Obj().Pkg()
611-
if pkg.Path() == "time" && typ.Obj().Name() == "Duration" {
612-
if d, ok := constant.Int64Val(obj.Val()); ok {
613-
str += " // " + time.Duration(d).String()
641+
642+
// Special formatting cases.
643+
switch typ := obj.Type().(type) {
644+
case *types.Named:
645+
// Try to add a formatted duration as an inline comment.
646+
pkg := typ.Obj().Pkg()
647+
if pkg.Path() == "time" && typ.Obj().Name() == "Duration" {
648+
if d, ok := constant.Int64Val(obj.Val()); ok {
649+
comment = time.Duration(d).String()
650+
}
614651
}
615652
}
653+
if comment == declaration {
654+
comment = ""
655+
}
656+
657+
str += " = " + declaration
658+
if comment != "" {
659+
str += " // " + comment
660+
}
616661
}
617662
return str
618663
}
@@ -708,28 +753,6 @@ func parseFull(ctx context.Context, snapshot Snapshot, fset *token.FileSet, pos
708753
return pgf, fullPos, nil
709754
}
710755

711-
// extractFieldList recursively tries to extract a field list.
712-
// If it is not found, nil is returned.
713-
func extractFieldList(specType ast.Expr) *ast.FieldList {
714-
switch t := specType.(type) {
715-
case *ast.StructType:
716-
return t.Fields
717-
case *ast.InterfaceType:
718-
return t.Methods
719-
case *ast.ArrayType:
720-
return extractFieldList(t.Elt)
721-
case *ast.MapType:
722-
// Map value has a greater chance to be a struct
723-
if fields := extractFieldList(t.Value); fields != nil {
724-
return fields
725-
}
726-
return extractFieldList(t.Key)
727-
case *ast.ChanType:
728-
return extractFieldList(t.Value)
729-
}
730-
return nil
731-
}
732-
733756
func formatHover(h *HoverJSON, options *Options) (string, error) {
734757
signature := formatSignature(h, options)
735758

gopls/internal/lsp/testdata/godef/a/g.go

-6
This file was deleted.

gopls/internal/lsp/testdata/godef/a/g.go.golden

-7
This file was deleted.

gopls/internal/lsp/testdata/summary.txt.golden

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ SemanticTokenCount = 3
1616
SuggestedFixCount = 65
1717
FunctionExtractionCount = 27
1818
MethodExtractionCount = 6
19-
DefinitionsCount = 47
19+
DefinitionsCount = 46
2020
TypeDefinitionsCount = 18
2121
HighlightsCount = 69
2222
InlayHintsCount = 4

gopls/internal/lsp/testdata/summary_go1.18.txt.golden

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ SemanticTokenCount = 3
1616
SuggestedFixCount = 71
1717
FunctionExtractionCount = 27
1818
MethodExtractionCount = 6
19-
DefinitionsCount = 47
19+
DefinitionsCount = 46
2020
TypeDefinitionsCount = 18
2121
HighlightsCount = 69
2222
InlayHintsCount = 5

gopls/internal/regtest/marker/testdata/hover/const.txt

+140-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,86 @@
11
This test checks hovering over constants.
2+
3+
-- flags --
4+
-min_go=go1.17
5+
26
-- go.mod --
37
module mod.com
48

5-
go 1.18
9+
go 1.17
10+
611
-- c.go --
712
package c
813

14+
import (
15+
"math"
16+
"time"
17+
)
18+
919
const X = 0 //@hover("X", "X", bX)
20+
21+
// dur is a constant of type time.Duration.
22+
const dur = 15*time.Minute + 10*time.Second + 350*time.Millisecond //@hover("dur", "dur", dur)
23+
24+
// MaxFloat32 is used in another package.
25+
const MaxFloat32 = 0x1p127 * (1 + (1 - 0x1p-23))
26+
27+
// Numbers.
28+
func _() {
29+
const hex, bin = 0xe34e, 0b1001001
30+
31+
const (
32+
// no inline comment
33+
decimal = 153
34+
35+
numberWithUnderscore int64 = 10_000_000_000
36+
octal = 0o777
37+
expr = 2 << (0b111&0b101 - 2)
38+
boolean = (55 - 3) == (26 * 2)
39+
)
40+
41+
_ = decimal //@hover("decimal", "decimal", decimalConst)
42+
_ = hex //@hover("hex", "hex", hexConst)
43+
_ = bin //@hover("bin", "bin", binConst)
44+
_ = numberWithUnderscore //@hover("numberWithUnderscore", "numberWithUnderscore", numberWithUnderscoreConst)
45+
_ = octal //@hover("octal", "octal", octalConst)
46+
_ = expr //@hover("expr", "expr", exprConst)
47+
_ = boolean //@hover("boolean", "boolean", boolConst)
48+
49+
const ln10 = 2.30258509299404568401799145468436420760110148862877297603332790
50+
51+
_ = ln10 //@hover("ln10", "ln10", ln10Const)
52+
}
53+
54+
// Iota.
55+
func _() {
56+
const (
57+
a = 1 << iota
58+
b
59+
)
60+
61+
_ = a //@hover("a", "a", aIota)
62+
_ = b //@hover("b", "b", bIota)
63+
}
64+
65+
// Strings.
66+
func _() {
67+
const (
68+
str = "hello" + " " + "world"
69+
longStr = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur eget ipsum non nunc
70+
molestie mattis id quis augue. Mauris dictum tincidunt ipsum, in auctor arcu congue eu.
71+
Morbi hendrerit fringilla libero commodo varius. Vestibulum in enim rutrum, rutrum tellus
72+
aliquet, luctus enim. Nunc sem ex, consectetur id porta nec, placerat vel urna.`
73+
)
74+
75+
_ = str //@hover("str", "str", strConst)
76+
_ = longStr //@hover("longStr", "longStr", longStrConst)
77+
}
78+
79+
// Constants from other packages.
80+
func _() {
81+
_ = math.Log2E //@hover("Log2E", "Log2E", log2eConst)
82+
}
83+
1084
-- @bX/hover.md --
1185
```go
1286
const X untyped int = 0
@@ -16,3 +90,68 @@ const X untyped int = 0
1690

1791

1892
[`c.X` on pkg.go.dev](https://pkg.go.dev/mod.com#X)
93+
-- @dur/hover.md --
94+
```go
95+
const dur time.Duration = 15*time.Minute + 10*time.Second + 350*time.Millisecond // 15m10.35s
96+
```
97+
98+
dur is a constant of type time.Duration.
99+
-- @decimalConst/hover.md --
100+
```go
101+
const decimal untyped int = 153
102+
```
103+
104+
no inline comment
105+
-- @hexConst/hover.md --
106+
```go
107+
const hex untyped int = 0xe34e // 58190
108+
```
109+
-- @binConst/hover.md --
110+
```go
111+
const bin untyped int = 0b1001001 // 73
112+
```
113+
-- @numberWithUnderscoreConst/hover.md --
114+
```go
115+
const numberWithUnderscore int64 = 10_000_000_000 // 10000000000
116+
```
117+
-- @octalConst/hover.md --
118+
```go
119+
const octal untyped int = 0o777 // 511
120+
```
121+
-- @exprConst/hover.md --
122+
```go
123+
const expr untyped int = 2 << (0b111&0b101 - 2) // 16
124+
```
125+
-- @boolConst/hover.md --
126+
```go
127+
const boolean untyped bool = (55 - 3) == (26 * 2) // true
128+
```
129+
-- @ln10Const/hover.md --
130+
```go
131+
const ln10 untyped float = 2.30258509299404568401799145468436420760110148862877297603332790 // 2.30259
132+
```
133+
-- @aIota/hover.md --
134+
```go
135+
const a untyped int = 1 << iota // 1
136+
```
137+
-- @bIota/hover.md --
138+
```go
139+
const b untyped int = 2
140+
```
141+
-- @strConst/hover.md --
142+
```go
143+
const str untyped string = "hello world"
144+
```
145+
-- @longStrConst/hover.md --
146+
```go
147+
const longStr untyped string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur e...
148+
```
149+
-- @log2eConst/hover.md --
150+
```go
151+
const math.Log2E untyped float = 1 / Ln2 // 1.4427
152+
```
153+
154+
Mathematical constants.
155+
156+
157+
[`math.Log2E` on pkg.go.dev](https://pkg.go.dev/math#Log2E)

gopls/internal/regtest/marker/testdata/hover/hover.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ func _() {
1515
}
1616
-- @abc/hover.md --
1717
```go
18-
const abc untyped int = 42
18+
const abc untyped int = 0x2a // 42
1919
```
2020

2121
@hover("b", "abc", abc),hover(" =", "abc", abc)

0 commit comments

Comments
 (0)