Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SignatureHelp: report signature of Ident if no enclosing CallExpr #510

Closed
wants to merge 14 commits into from
Closed
4 changes: 4 additions & 0 deletions gopls/doc/features/passive.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ function call.

<img src='../assets/signature-help.png'>

Call parens are not necessary if the cursor is within an identifier
that denotes a function or method. For example, Signature Help at
`once.Do(initialize‸)` will describe `initialize`, not `once.Do`.

Client support:
- **VS Code**: enabled by default.
Also known as "[parameter hints](https://code.visualstudio.com/api/references/vscode-api#SignatureHelpProvider)" in the [IntelliSense settings](https://code.visualstudio.com/docs/editor/intellisense#_settings).
Expand Down
4 changes: 4 additions & 0 deletions gopls/doc/release/v0.17.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@ constructor of the type of each symbol:
`interface`, `struct`, `signature`, `pointer`, `array`, `map`, `slice`, `chan`, `string`, `number`, `bool`, and `invalid`.
Editors may use this for syntax coloring.

## SignatureHelp for ident and values.

Now, function signature help can be used on any identifier with a function
signature, not just within the parentheses of a function being called.
45 changes: 34 additions & 11 deletions gopls/internal/golang/signature_help.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,32 @@ func SignatureHelp(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle
if path == nil {
return nil, 0, fmt.Errorf("cannot find node enclosing position")
}
FindCall:
for _, node := range path {
info := pkg.TypesInfo()
var fnval ast.Expr
loop:
for i, node := range path {
switch node := node.(type) {
case *ast.Ident:
// If the selected text is a function/method Ident orSelectorExpr,
// even one not in function call position,
// show help for its signature. Example:
// once.Do(initialize⁁)
// should show help for initialize, not once.Do.
if t := info.TypeOf(node); t != nil &&
info.Defs[node] == nil &&
is[*types.Signature](t.Underlying()) {
if sel, ok := path[i+1].(*ast.SelectorExpr); ok && sel.Sel == node {
fnval = sel // e.g. fmt.Println⁁
} else {
fnval = node
}
break loop
}
case *ast.CallExpr:
if pos >= node.Lparen && pos <= node.Rparen {
callExpr = node
break FindCall
fnval = callExpr.Fun
break loop
}
case *ast.FuncLit, *ast.FuncType:
// The user is within an anonymous function,
Expand All @@ -70,20 +89,19 @@ FindCall:
}

}
if callExpr == nil || callExpr.Fun == nil {

if fnval == nil {
return nil, 0, nil
}

info := pkg.TypesInfo()

// Get the type information for the function being called.
var sig *types.Signature
if tv, ok := info.Types[callExpr.Fun]; !ok {
return nil, 0, fmt.Errorf("cannot get type for Fun %[1]T (%[1]v)", callExpr.Fun)
if tv, ok := info.Types[fnval]; !ok {
return nil, 0, fmt.Errorf("cannot get type for Fun %[1]T (%[1]v)", fnval)
} else if tv.IsType() {
return nil, 0, nil // a conversion, not a call
} else if sig, ok = tv.Type.Underlying().(*types.Signature); !ok {
return nil, 0, fmt.Errorf("call operand is not a func or type: %[1]T (%[1]v)", callExpr.Fun)
return nil, 0, fmt.Errorf("call operand is not a func or type: %[1]T (%[1]v)", fnval)
}
// Inv: sig != nil

Expand All @@ -93,7 +111,7 @@ FindCall:
// There is no object in certain cases such as calling a function returned by
// a function (e.g. "foo()()").
var obj types.Object
switch t := callExpr.Fun.(type) {
switch t := fnval.(type) {
case *ast.Ident:
obj = info.ObjectOf(t)
case *ast.SelectorExpr:
Expand All @@ -116,7 +134,12 @@ FindCall:
return nil, 0, bug.Errorf("call to unexpected built-in %v (%T)", obj, obj)
}

activeParam := activeParameter(callExpr, sig.Params().Len(), sig.Variadic(), pos)
activeParam := 0
if callExpr != nil {
// only return activeParam when CallExpr
// because we don't modify arguments when get function signature only
activeParam = activeParameter(callExpr, sig.Params().Len(), sig.Variadic(), pos)
}

var (
name string
Expand Down
27 changes: 26 additions & 1 deletion gopls/internal/test/marker/testdata/signature/signature.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"bytes"
"encoding/json"
"math/big"
"fmt"
)

func Foo(a string, b int) (c bool) {
Expand Down Expand Up @@ -49,6 +50,9 @@ func Qux() {
Foo("foo", 123) //@signature(",", "Foo(a string, b int) (c bool)", 0)
Foo("foo", 123) //@signature(" 1", "Foo(a string, b int) (c bool)", 1)
Foo("foo", 123) //@signature(")", "Foo(a string, b int) (c bool)", 1)
Foo("foo", 123) //@signature("o", "Foo(a string, b int) (c bool)", 0)
_ = Foo //@signature("o", "Foo(a string, b int) (c bool)", 0)
Foo //@signature("o", "Foo(a string, b int) (c bool)", 0)

Bar(13.37, 0x13) //@signature("13.37", "Bar(float64, ...byte)", 0)
Bar(13.37, 0x37) //@signature("0x37", "Bar(float64, ...byte)", 1)
Expand Down Expand Up @@ -78,9 +82,28 @@ func Qux() {

_ = make([]int, 1, 2) //@signature("2", "make(t Type, size ...int) Type", 1)

Foo(myFunc(123), 456) //@signature("myFunc", "Foo(a string, b int) (c bool)", 0)
Foo(myFunc(123), 456) //@signature("o(", "Foo(a string, b int) (c bool)", 0)
Foo(myFunc(123), 456) //@signature("(m", "Foo(a string, b int) (c bool)", 0)
Foo( myFunc(123), 456) //@signature(" m", "Foo(a string, b int) (c bool)", 0)
Foo(myFunc(123), 456) //@signature(", ", "Foo(a string, b int) (c bool)", 0)
Foo(myFunc(123), 456) //@signature("456", "Foo(a string, b int) (c bool)", 1)
Foo(myFunc) //@signature(")", "Foo(a string, b int) (c bool)", 0)
Foo(myFunc(123), 456) //@signature("(1", "myFunc(foo int) string", 0)
Foo(myFunc(123), 456) //@signature("123", "myFunc(foo int) string", 0)

fmt.Println //@signature("ln", "Println(a ...any) (n int, err error)", 0)
fmt.Println(myFunc) //@signature("ln", "Println(a ...any) (n int, err error)", 0)
fmt.Println(myFunc) //@signature("Func", "myFunc(foo int) string", 0)

var hi string = "hello"
var wl string = " world: %s"
fmt.Println(fmt.Sprintf(wl, myFunc)) //@signature("Func", "myFunc(foo int) string", 0)
fmt.Println(fmt.Sprintf(wl, myFunc)) //@signature("wl", "Sprintf(format string, a ...any) string", 0)
fmt.Println(fmt.Sprintf(wl, myFunc)) //@signature(" m", "Sprintf(format string, a ...any) string", 1)
fmt.Println(hi, fmt.Sprintf(wl, myFunc)) //@signature("Sprint", "Sprintf(format string, a ...any) string", 0)
fmt.Println(hi, fmt.Sprintf(wl, myFunc)) //@signature(" fmt", "Println(a ...any) (n int, err error)", 0)
fmt.Println(hi, fmt.Sprintf(wl, myFunc)) //@signature("hi", "Println(a ...any) (n int, err error)", 0)

panic("oops!") //@signature(")", "panic(v any)", 0)
println("hello", "world") //@signature(",", "println(args ...Type)", 0)

Expand All @@ -100,6 +123,8 @@ package signature

func _() {
Foo(//@signature("//", "Foo(a string, b int) (c bool)", 0)
Foo.//@signature("//", "Foo(a string, b int) (c bool)", 0)
Foo.//@signature("oo", "Foo(a string, b int) (c bool)", 0)
}

-- signature/signature3.go --
Expand Down