Skip to content

Commit

Permalink
internal/lsp/source: add inferred types to generic function hover
Browse files Browse the repository at this point in the history
As an experiment, this CL introduces the first gopls feature that is
specific to generics: enriching function hover information with inferred
types. This is done with no additional gating on build constraints by
using the new internal/typeparams package.

The marker tests are updated to allow tests that rely on type parameters
being enabled.

Change-Id: Ic627d64b61a6211389196814edd0abe1484491eb
Reviewed-on: https://go-review.googlesource.com/c/tools/+/317452
Trust: Robert Findley <[email protected]>
Run-TryBot: Robert Findley <[email protected]>
gopls-CI: kokoro <[email protected]>
TryBot-Result: Go Bot <[email protected]>
Reviewed-by: Rebecca Stambler <[email protected]>
  • Loading branch information
findleyr committed Jun 22, 2021
1 parent d25f906 commit 4c651fc
Show file tree
Hide file tree
Showing 10 changed files with 239 additions and 95 deletions.
3 changes: 2 additions & 1 deletion internal/lsp/cache/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"golang.org/x/tools/internal/memoize"
"golang.org/x/tools/internal/packagesinternal"
"golang.org/x/tools/internal/span"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal"
errors "golang.org/x/xerrors"
)
Expand Down Expand Up @@ -343,7 +344,6 @@ func typeCheck(ctx context.Context, snapshot *snapshot, m *metadata, mode source
}
}
}

// If this is a replaced module in the workspace, the version is
// meaningless, and we don't want clients to access it.
if m.module != nil {
Expand Down Expand Up @@ -447,6 +447,7 @@ func doTypeCheck(ctx context.Context, snapshot *snapshot, m *metadata, mode sour
},
typesSizes: m.typesSizes,
}
typeparams.InitInferred(pkg.typesInfo)

for _, gf := range pkg.m.goFiles {
// In the presence of line directives, we may need to report errors in
Expand Down
21 changes: 18 additions & 3 deletions internal/lsp/source/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (

"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/typeparams"
errors "golang.org/x/xerrors"
)

Expand Down Expand Up @@ -125,10 +126,10 @@ func HoverIdentifier(ctx context.Context, i *IdentifierInfo) (*HoverInformation,
break
}
}
h.Signature = objectString(x, i.qf)
h.Signature = objectString(x, i.qf, i.Inferred)
}
if obj := i.Declaration.obj; obj != nil {
h.SingleLine = objectString(obj, i.qf)
h.SingleLine = objectString(obj, i.qf, nil)
}
obj := i.Declaration.obj
if obj == nil {
Expand Down Expand Up @@ -237,7 +238,21 @@ func moduleAtVersion(path string, i *IdentifierInfo) (string, string, bool) {

// objectString is a wrapper around the types.ObjectString function.
// It handles adding more information to the object string.
func objectString(obj types.Object, qf types.Qualifier) string {
func objectString(obj types.Object, qf types.Qualifier, inferred *types.Signature) string {
// If the signature type was inferred, prefer the preferred signature with a
// comment showing the generic signature.
if sig, _ := obj.Type().(*types.Signature); sig != nil && len(typeparams.ForSignature(sig)) > 0 && inferred != nil {
obj2 := types.NewFunc(obj.Pos(), obj.Pkg(), obj.Name(), inferred)
str := types.ObjectString(obj2, qf)
// Try to avoid overly long lines.
if len(str) > 60 {
str += "\n"
} else {
str += " "
}
str += "// " + types.TypeString(sig, qf)
return str
}
str := types.ObjectString(obj, qf)
switch obj := obj.(type) {
case *types.Const:
Expand Down
51 changes: 51 additions & 0 deletions internal/lsp/source/identifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/span"
"golang.org/x/tools/internal/typeparams"
errors "golang.org/x/xerrors"
)

Expand All @@ -32,6 +33,8 @@ type IdentifierInfo struct {
Object types.Object
}

Inferred *types.Signature

Declaration Declaration

ident *ast.Ident
Expand Down Expand Up @@ -292,6 +295,8 @@ func findIdentifier(ctx context.Context, snapshot Snapshot, pkg Package, pgf *Pa
return result, nil
}

result.Inferred = inferredSignature(pkg.GetTypesInfo(), path)

result.Type.Object = typeToObject(typ)
if result.Type.Object != nil {
// Identifiers with the type "error" are a special case with no position.
Expand Down Expand Up @@ -337,6 +342,52 @@ func fullSpec(snapshot Snapshot, obj types.Object, pkg Package) (ast.Spec, error
return nil, nil
}

// inferredSignature determines the resolved non-generic signature for an
// identifier with a generic signature that is the operand of an IndexExpr or
// CallExpr.
//
// If no such signature exists, it returns nil.
func inferredSignature(info *types.Info, path []ast.Node) *types.Signature {
if len(path) < 2 {
return nil
}
// There are four ways in which a signature may be resolved:
// 1. It has no explicit type arguments, but the CallExpr can be fully
// inferred from function arguments.
// 2. It has full type arguments, and the IndexExpr has a non-generic type.
// 3. For a partially instantiated IndexExpr representing a function-valued
// expression (i.e. not part of a CallExpr), type arguments may be
// inferred using constraint type inference.
// 4. For a partially instantiated IndexExpr that is part of a CallExpr,
// type arguments may be inferred using both constraint type inference
// and function argument inference.
//
// These branches are handled below.
switch n := path[1].(type) {
case *ast.CallExpr:
_, sig := typeparams.GetInferred(info, n)
return sig
case *ast.IndexExpr:
// If the IndexExpr is fully instantiated, we consider that 'inference' for
// gopls' purposes.
sig, _ := info.TypeOf(n).(*types.Signature)
if sig != nil && len(typeparams.ForSignature(sig)) == 0 {
return sig
}
_, sig = typeparams.GetInferred(info, n)
if sig != nil {
return sig
}
if len(path) >= 2 {
if call, _ := path[2].(*ast.CallExpr); call != nil {
_, sig := typeparams.GetInferred(info, call)
return sig
}
}
}
return nil
}

func searchForEnclosing(info *types.Info, path []ast.Node) types.Type {
for _, n := range path {
switch n := n.(type) {
Expand Down
72 changes: 36 additions & 36 deletions internal/lsp/testdata/godef/a/a.go.golden
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,42 @@ func Random2(y int) int
[`a.Random2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Random2)
-- aPackage-hover --
Package a is a package for testing go to definition\.
-- declBlockA-hover --
```go
type a struct {
x string
}
```

1st type declaration block
-- declBlockB-hover --
```go
type b struct{}
```

b has a comment
-- declBlockC-hover --
```go
type c struct {
f string
}
```

c is a struct
-- declBlockD-hover --
```go
type d string
```

3rd type declaration block
-- declBlockE-hover --
```go
type e struct {
f float64
}
```

e has a comment
-- err-definition --
godef/a/a.go:33:6-9: defined here as ```go
var err error
Expand Down Expand Up @@ -148,39 +184,3 @@ var z string
```

z is a variable too\.
-- declBlockA-hover --
```go
type a struct {
x string
}
```

1st type declaration block
-- declBlockB-hover --
```go
type b struct{}
```

b has a comment
-- declBlockC-hover --
```go
type c struct {
f string
}
```

c is a struct
-- declBlockD-hover --
```go
type d string
```

3rd type declaration block
-- declBlockE-hover --
```go
type e struct {
f float64
}
```

e has a comment
96 changes: 48 additions & 48 deletions internal/lsp/testdata/godef/a/h.go.golden
Original file line number Diff line number Diff line change
@@ -1,39 +1,3 @@
-- nestedNumber-hover --
```go
field number int64
```

nested number
-- nestedString-hover --
```go
field str string
```

nested string
-- nestedMap-hover --
```go
field m map[string]float64
```

nested map
-- structA-hover --
```go
field a int
```

a field
-- structB-hover --
```go
field b struct{c int}
```

b nested struct
-- structC-hover --
```go
field c int
```

c field of nested struct
-- arrD-hover --
```go
field d int
Expand Down Expand Up @@ -86,12 +50,60 @@ field x string
```

X value field
-- nestedMap-hover --
```go
field m map[string]float64
```

nested map
-- nestedNumber-hover --
```go
field number int64
```

nested number
-- nestedString-hover --
```go
field str string
```

nested string
-- openMethod-hover --
```go
func (interface).open() error
```

open method comment
-- returnX-hover --
```go
field x int
```

X coord
-- returnY-hover --
```go
field y int
```

Y coord
-- structA-hover --
```go
field a int
```

a field
-- structB-hover --
```go
field b struct{c int}
```

b nested struct
-- structC-hover --
```go
field c int
```

c field of nested struct
-- testDescription-hover --
```go
field desc string
Expand Down Expand Up @@ -122,15 +134,3 @@ field value int
```

expected test value
-- returnX-hover --
```go
field x int
```

X coord
-- returnY-hover --
```go
field y int
```

Y coord
12 changes: 6 additions & 6 deletions internal/lsp/testdata/godef/b/h.go.golden
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
-- AVariable-hover --
-- AStuff-hover --
```go
var _ A
func AStuff()
```

variable of type a\.A
-- AStuff-hover --
[`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#AStuff)
-- AVariable-hover --
```go
func AStuff()
var _ A
```

[`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#AStuff)
variable of type a\.A
12 changes: 12 additions & 0 deletions internal/lsp/testdata/godef/infer_generics/inferred.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package inferred

func app[S interface{ ~[]E }, E any](s S, e E) S {
return append(s, e)
}

func _() {
_ = app[[]int] //@mark(constrInfer, "app"),hover("app", constrInfer)
_ = app[[]int, int] //@mark(instance, "app"),hover("app", instance)
_ = app[[]int]([]int{}, 0) //@mark(partialInfer, "app"),hover("app", partialInfer)
_ = app([]int{}, 0) //@mark(argInfer, "app"),hover("app", argInfer)
}
Loading

0 comments on commit 4c651fc

Please sign in to comment.