diff --git a/gopls/doc/features/passive.md b/gopls/doc/features/passive.md index 92ae929ad5e..23a1938cbeb 100644 --- a/gopls/doc/features/passive.md +++ b/gopls/doc/features/passive.md @@ -107,6 +107,10 @@ function call. +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). diff --git a/gopls/doc/release/v0.17.0.md b/gopls/doc/release/v0.17.0.md index dba85fef46c..12f04dadf2d 100644 --- a/gopls/doc/release/v0.17.0.md +++ b/gopls/doc/release/v0.17.0.md @@ -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. diff --git a/gopls/internal/golang/signature_help.go b/gopls/internal/golang/signature_help.go index a91be296cbd..26cb92c643b 100644 --- a/gopls/internal/golang/signature_help.go +++ b/gopls/internal/golang/signature_help.go @@ -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, @@ -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 @@ -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: @@ -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 diff --git a/gopls/internal/test/marker/testdata/signature/signature.txt b/gopls/internal/test/marker/testdata/signature/signature.txt index 7bdd6341818..1da4eb5843e 100644 --- a/gopls/internal/test/marker/testdata/signature/signature.txt +++ b/gopls/internal/test/marker/testdata/signature/signature.txt @@ -16,6 +16,7 @@ import ( "bytes" "encoding/json" "math/big" + "fmt" ) func Foo(a string, b int) (c bool) { @@ -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) @@ -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) @@ -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 --