mirror of
https://github.com/golang/go
synced 2024-11-19 02:24:41 -07:00
7e40e1726e
- show signature for function calls whose function expression is not an object (e.g. the second call in foo()()). since the function name is not available, we use the generic "func" - only provide signature help when the position is on or within the call expression parens. this is consistent with the one other lsp server i tried (java). this improves the gopls experience in emacs where lsp-mode is constantly calling "hover" and "signatureHelp" ("hover" should be preferred unless you are inside the function params list) - use the entire signature type string as the label since that includes the return values, which are useful to see - don't qualify the function name with its package. it looks funny to see "bytes.Cap()" as the help when you are in a call to (*bytes.Buffer).Cap(). it could be useful to include invocant type info, but leave it out for now since signature help is meant to focus on the function parameters. - don't turn variadic args "foo ...int" into "foo []int" for the parameter information (i.e. maintain it as "foo ...int") - when determining active parameter, count the space before a parameter name as being part of that parameter (e.g. the space before "b" in "func(a int, b int)") - handle variadic params when determining the active param (i.e. highlight "foo(a int, *b ...string*)" on signature help for final param in `foo(123, "a", "b", "c")` - don't generate an extra space in formatParams() for unnamed arguments I also tweaked the signatureHelp server log message to include the error message itself, and populated the server's logger in lsp_test.go to aid in development. Fixes golang/go#31448 Change-Id: Iefe0e1e3c531d17197c0fa997b949174475a276c GitHub-Last-Rev: 5c0b8ebd87a8c05d5d8f519ea096f94e89c77e2c GitHub-Pull-Request: golang/tools#82 Reviewed-on: https://go-review.googlesource.com/c/tools/+/172439 Run-TryBot: Ian Cottrell <iancottrell@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
131 lines
3.4 KiB
Go
131 lines
3.4 KiB
Go
// Copyright 2018 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package source
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/token"
|
|
"go/types"
|
|
"strings"
|
|
|
|
"golang.org/x/tools/go/ast/astutil"
|
|
)
|
|
|
|
type SignatureInformation struct {
|
|
Label string
|
|
Parameters []ParameterInformation
|
|
ActiveParameter int
|
|
}
|
|
|
|
type ParameterInformation struct {
|
|
Label string
|
|
}
|
|
|
|
func SignatureHelp(ctx context.Context, f File, pos token.Pos) (*SignatureInformation, error) {
|
|
fAST := f.GetAST(ctx)
|
|
pkg := f.GetPackage(ctx)
|
|
if pkg.IsIllTyped() {
|
|
return nil, fmt.Errorf("package for %s is ill typed", f.URI())
|
|
}
|
|
|
|
// Find a call expression surrounding the query position.
|
|
var callExpr *ast.CallExpr
|
|
path, _ := astutil.PathEnclosingInterval(fAST, pos, pos)
|
|
if path == nil {
|
|
return nil, fmt.Errorf("cannot find node enclosing position")
|
|
}
|
|
for _, node := range path {
|
|
if c, ok := node.(*ast.CallExpr); ok && pos >= c.Lparen && pos <= c.Rparen {
|
|
callExpr = c
|
|
break
|
|
}
|
|
}
|
|
if callExpr == nil || callExpr.Fun == nil {
|
|
return nil, fmt.Errorf("cannot find an enclosing function")
|
|
}
|
|
|
|
// Get the type information for the function being called.
|
|
sigType := pkg.GetTypesInfo().TypeOf(callExpr.Fun)
|
|
if sigType == nil {
|
|
return nil, fmt.Errorf("cannot get type for Fun %[1]T (%[1]v)", callExpr.Fun)
|
|
}
|
|
|
|
sig, _ := sigType.Underlying().(*types.Signature)
|
|
if sig == nil {
|
|
return nil, fmt.Errorf("cannot find signature for Fun %[1]T (%[1]v)", callExpr.Fun)
|
|
}
|
|
|
|
pkgStringer := qualifier(fAST, pkg.GetTypes(), pkg.GetTypesInfo())
|
|
var paramInfo []ParameterInformation
|
|
for i := 0; i < sig.Params().Len(); i++ {
|
|
param := sig.Params().At(i)
|
|
label := types.TypeString(param.Type(), pkgStringer)
|
|
if sig.Variadic() && i == sig.Params().Len()-1 {
|
|
label = strings.Replace(label, "[]", "...", 1)
|
|
}
|
|
if param.Name() != "" {
|
|
label = fmt.Sprintf("%s %s", param.Name(), label)
|
|
}
|
|
paramInfo = append(paramInfo, ParameterInformation{
|
|
Label: label,
|
|
})
|
|
}
|
|
|
|
// Determine the query position relative to the number of parameters in the function.
|
|
var activeParam int
|
|
var start, end token.Pos
|
|
for _, expr := range callExpr.Args {
|
|
if start == token.NoPos {
|
|
start = expr.Pos()
|
|
}
|
|
end = expr.End()
|
|
if start <= pos && pos <= end {
|
|
break
|
|
}
|
|
|
|
// Don't advance the active parameter for the last parameter of a variadic function.
|
|
if !sig.Variadic() || activeParam < sig.Params().Len()-1 {
|
|
activeParam++
|
|
}
|
|
start = expr.Pos() + 1 // to account for commas
|
|
}
|
|
|
|
// Get the object representing the function, if available.
|
|
// 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) {
|
|
case *ast.Ident:
|
|
obj = pkg.GetTypesInfo().ObjectOf(t)
|
|
case *ast.SelectorExpr:
|
|
obj = pkg.GetTypesInfo().ObjectOf(t.Sel)
|
|
}
|
|
|
|
var label string
|
|
if obj != nil {
|
|
label = obj.Name()
|
|
} else {
|
|
label = "func"
|
|
}
|
|
|
|
label += formatParams(sig.Params(), sig.Variadic(), pkgStringer)
|
|
if sig.Results().Len() > 0 {
|
|
results := types.TypeString(sig.Results(), pkgStringer)
|
|
if sig.Results().Len() == 1 && sig.Results().At(0).Name() == "" {
|
|
// Trim off leading/trailing parens to avoid results like "foo(a int) (int)".
|
|
results = strings.Trim(results, "()")
|
|
}
|
|
label += " " + results
|
|
}
|
|
|
|
return &SignatureInformation{
|
|
Label: label,
|
|
Parameters: paramInfo,
|
|
ActiveParameter: activeParam,
|
|
}, nil
|
|
}
|