1
0
mirror of https://github.com/golang/go synced 2024-10-01 10:38:33 -06:00
go/internal/lsp/source/signature_help.go
Rebecca Stambler 62ee03427e internal/lsp: use memoize package to cache source.Packages
This change eliminates the need for the package cache map, and instead
stores package type information in the store. We still have to maintain
invalidation logic because the key is not computed correctly.

Change-Id: I1c2a7502b99491ef0ff68d68c9f439503d531ff1
Reviewed-on: https://go-review.googlesource.com/c/tools/+/185438
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
2019-08-13 21:41:43 +00:00

190 lines
5.3 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"
"go/ast"
"go/doc"
"go/token"
"go/types"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/internal/lsp/telemetry/trace"
errors "golang.org/x/xerrors"
)
type SignatureInformation struct {
Label, Documentation string
Parameters []ParameterInformation
ActiveParameter int
}
type ParameterInformation struct {
Label string
}
func SignatureHelp(ctx context.Context, f GoFile, pos token.Pos) (*SignatureInformation, error) {
ctx, done := trace.StartSpan(ctx, "source.SignatureHelp")
defer done()
file, err := f.GetAST(ctx, ParseFull)
if file == nil {
return nil, err
}
pkg, err := f.GetPackage(ctx)
if err != nil {
return nil, err
}
// Find a call expression surrounding the query position.
var callExpr *ast.CallExpr
path, _ := astutil.PathEnclosingInterval(file, pos, pos)
if path == nil {
return nil, errors.Errorf("cannot find node enclosing position")
}
FindCall:
for _, node := range path {
switch node := node.(type) {
case *ast.CallExpr:
if pos >= node.Lparen && pos <= node.Rparen {
callExpr = node
break FindCall
}
case *ast.FuncLit, *ast.FuncType:
// The user is within an anonymous function,
// which may be the parameter to the *ast.CallExpr.
// Don't show signature help in this case.
return nil, errors.Errorf("no signature help within a function declaration")
}
}
if callExpr == nil || callExpr.Fun == nil {
return nil, errors.Errorf("cannot find an enclosing function")
}
// 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)
}
// Handle builtin functions separately.
if obj, ok := obj.(*types.Builtin); ok {
return builtinSignature(ctx, f.View(), callExpr, obj.Name(), pos)
}
// Get the type information for the function being called.
sigType := pkg.GetTypesInfo().TypeOf(callExpr.Fun)
if sigType == nil {
return nil, errors.Errorf("cannot get type for Fun %[1]T (%[1]v)", callExpr.Fun)
}
sig, _ := sigType.Underlying().(*types.Signature)
if sig == nil {
return nil, errors.Errorf("cannot find signature for Fun %[1]T (%[1]v)", callExpr.Fun)
}
qf := qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo())
params := formatParams(sig.Params(), sig.Variadic(), qf)
results, writeResultParens := formatResults(sig.Results(), qf)
activeParam := activeParameter(callExpr, sig.Params().Len(), sig.Variadic(), pos)
var (
name string
comment *ast.CommentGroup
)
if obj != nil {
rng, err := objToRange(ctx, f.FileSet(), obj)
if err != nil {
return nil, err
}
node, err := objToNode(ctx, f.View(), pkg.GetTypes(), obj, rng)
if err != nil {
return nil, err
}
decl := &declaration{
obj: obj,
rng: rng,
node: node,
}
d, err := decl.hover(ctx)
if err != nil {
return nil, err
}
name = obj.Name()
comment = d.comment
} else {
name = "func"
}
return signatureInformation(name, comment, params, results, writeResultParens, activeParam), nil
}
func builtinSignature(ctx context.Context, v View, callExpr *ast.CallExpr, name string, pos token.Pos) (*SignatureInformation, error) {
decl, ok := lookupBuiltinDecl(v, name).(*ast.FuncDecl)
if !ok {
return nil, errors.Errorf("no function declaration for builtin: %s", name)
}
params, _ := formatFieldList(ctx, v, decl.Type.Params)
results, writeResultParens := formatFieldList(ctx, v, decl.Type.Results)
var (
numParams int
variadic bool
)
if decl.Type.Params.List != nil {
numParams = len(decl.Type.Params.List)
lastParam := decl.Type.Params.List[numParams-1]
if _, ok := lastParam.Type.(*ast.Ellipsis); ok {
variadic = true
}
}
activeParam := activeParameter(callExpr, numParams, variadic, pos)
return signatureInformation(name, nil, params, results, writeResultParens, activeParam), nil
}
func signatureInformation(name string, comment *ast.CommentGroup, params, results []string, writeResultParens bool, activeParam int) *SignatureInformation {
paramInfo := make([]ParameterInformation, 0, len(params))
for _, p := range params {
paramInfo = append(paramInfo, ParameterInformation{Label: p})
}
label := name + formatFunction(params, results, writeResultParens)
var c string
if comment != nil {
c = doc.Synopsis(comment.Text())
}
return &SignatureInformation{
Label: label,
Documentation: c,
Parameters: paramInfo,
ActiveParameter: activeParam,
}
}
func activeParameter(callExpr *ast.CallExpr, numParams int, variadic bool, pos token.Pos) int {
// 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 !variadic || activeParam < numParams-1 {
activeParam++
}
start = expr.Pos() + 1 // to account for commas
}
return activeParam
}