1
0
mirror of https://github.com/golang/go synced 2024-11-19 13:44:52 -07:00
go/internal/lsp/source/signature_help.go
Rebecca Stambler 00c44ba9c1 internal/lsp: add cache for type information
This change adds an additional cache for type information, which here is
just a *packages.Package for each package. The metadata cache maintains
the import graph, which allows us to easily determine when a package X
(and therefore any other package that imports X) should be invalidated.

Additionally, rather than performing content changes as they happen, we
queue up content changes and apply them the next time that any type
information is requested.

Updates golang/go#30309

Change-Id: Iaf569f641f84ce69b0c0d5bdabbaa85635eeb8bf
Reviewed-on: https://go-review.googlesource.com/c/tools/+/165438
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
2019-03-08 17:45:44 +00:00

113 lines
2.9 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"
"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)
// 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 {
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 corresponding to the call expression.
var obj types.Object
switch t := callExpr.Fun.(type) {
case *ast.Ident:
obj = pkg.TypesInfo.ObjectOf(t)
case *ast.SelectorExpr:
obj = pkg.TypesInfo.ObjectOf(t.Sel)
default:
return nil, fmt.Errorf("the enclosing function is malformed")
}
if obj == nil {
return nil, fmt.Errorf("cannot resolve %s", callExpr.Fun)
}
// Find the signature corresponding to the object.
var sig *types.Signature
switch obj.(type) {
case *types.Var:
if underlying, ok := obj.Type().Underlying().(*types.Signature); ok {
sig = underlying
}
case *types.Func:
sig = obj.Type().(*types.Signature)
}
if sig == nil {
return nil, fmt.Errorf("no function signatures found for %s", obj.Name())
}
pkgStringer := qualifier(fAST, pkg.Types, pkg.TypesInfo)
var paramInfo []ParameterInformation
for i := 0; i < sig.Params().Len(); i++ {
param := sig.Params().At(i)
label := types.TypeString(param.Type(), pkgStringer)
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 i, expr := range callExpr.Args {
if start == token.NoPos {
start = expr.Pos()
}
end = expr.End()
if i < len(callExpr.Args)-1 {
end = callExpr.Args[i+1].Pos() - 1 // comma
}
if start <= pos && pos <= end {
break
}
activeParam++
start = expr.Pos() + 1 // to account for commas
}
// Label for function, qualified by package name.
label := obj.Name()
if pkg := pkgStringer(obj.Pkg()); pkg != "" {
label = pkg + "." + label
}
return &SignatureInformation{
Label: label + formatParams(sig.Params(), sig.Variadic(), pkgStringer),
Parameters: paramInfo,
ActiveParameter: activeParam,
}, nil
}