1
0
mirror of https://github.com/golang/go synced 2024-11-18 16:54:43 -07:00

internal/lsp: support builtin types without hardcoding

This change uses an *ast.Package built from the file
go/src/builtin/builtin.go. Completion (and ultimately other features)
will be resolved using this AST instead of being hardcoded.

Change-Id: I3e34030b3236994faa484cf17cf75da511165133
Reviewed-on: https://go-review.googlesource.com/c/tools/+/174381
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
Rebecca Stambler 2019-04-29 21:08:16 -04:00
parent 2d28432af7
commit 2346320968
9 changed files with 227 additions and 181 deletions

View File

@ -6,6 +6,8 @@ package cache
import (
"context"
"go/ast"
"go/parser"
"go/token"
"go/types"
"os"
@ -63,6 +65,9 @@ type View struct {
// pcache caches type information for the packages of the opened files in a view.
pcache *packageCache
// builtinPkg is the AST package used to resolve builtin types.
builtinPkg *ast.Package
}
type metadataCache struct {
@ -93,6 +98,7 @@ func NewView(ctx context.Context, log xlog.Logger, name string, folder span.URI,
v := &View{
baseCtx: ctx,
backgroundCtx: backgroundCtx,
builtinPkg: builtinPkg(*config),
cancel: cancel,
log: log,
Config: *config,
@ -118,6 +124,32 @@ func (v *View) BackgroundContext() context.Context {
return v.backgroundCtx
}
func (v *View) BuiltinPackage() *ast.Package {
return v.builtinPkg
}
func builtinPkg(cfg packages.Config) *ast.Package {
var bpkg *ast.Package
cfg.Mode = packages.LoadFiles
pkgs, _ := packages.Load(&cfg, "builtin")
if len(pkgs) != 1 {
bpkg, _ = ast.NewPackage(cfg.Fset, nil, nil, nil)
return bpkg
}
pkg := pkgs[0]
files := make(map[string]*ast.File)
for _, filename := range pkg.GoFiles {
file, err := parser.ParseFile(cfg.Fset, filename, nil, parser.ParseComments)
if err != nil {
bpkg, _ = ast.NewPackage(cfg.Fset, nil, nil, nil)
return bpkg
}
files[filename] = file
}
bpkg, _ = ast.NewPackage(cfg.Fset, files, nil, nil)
return bpkg
}
func (v *View) FileSet() *token.FileSet {
return v.Config.Fset
}

View File

@ -14,24 +14,17 @@ import (
)
func (s *Server) cacheAndDiagnose(ctx context.Context, uri span.URI, content string) error {
s.log.Debugf(ctx, "cacheAndDiagnose: %s", uri)
view := s.findView(ctx, uri)
if err := view.SetContent(ctx, uri, []byte(content)); err != nil {
return err
}
s.log.Debugf(ctx, "cacheAndDiagnose: set content for %s", uri)
go func() {
ctx := view.BackgroundContext()
if ctx.Err() != nil {
s.log.Errorf(ctx, "canceling diagnostics for %s: %v", uri, ctx.Err())
return
}
s.log.Debugf(ctx, "cacheAndDiagnose: going to get diagnostics for %s", uri)
reports, err := source.Diagnostics(ctx, view, uri)
if err != nil {
s.log.Errorf(ctx, "failed to compute diagnostics for %s: %v", uri, err)
@ -41,8 +34,6 @@ func (s *Server) cacheAndDiagnose(ctx context.Context, uri span.URI, content str
s.undeliveredMu.Lock()
defer s.undeliveredMu.Unlock()
s.log.Debugf(ctx, "cacheAndDiagnose: publishing diagnostics")
for uri, diagnostics := range reports {
if err := s.publishDiagnostics(ctx, view, uri, diagnostics); err != nil {
if s.undelivered == nil {
@ -54,9 +45,6 @@ func (s *Server) cacheAndDiagnose(ctx context.Context, uri span.URI, content str
// In case we had old, undelivered diagnostics.
delete(s.undelivered, uri)
}
s.log.Debugf(ctx, "cacheAndDiagnose: publishing undelivered diagnostics")
// Anytime we compute diagnostics, make sure to also send along any
// undelivered ones (only for remaining URIs).
for uri, diagnostics := range s.undelivered {
@ -66,8 +54,6 @@ func (s *Server) cacheAndDiagnose(ctx context.Context, uri span.URI, content str
delete(s.undelivered, uri)
}
}()
s.log.Debugf(ctx, "cacheAndDiagnose: returned from diagnostics for %s", uri)
return nil
}

View File

@ -93,7 +93,12 @@ type completer struct {
types *types.Package
info *types.Info
qf types.Qualifier
fset *token.FileSet
// view is the View associated with this completion request.
view View
// ctx is the context associated with this completion request.
ctx context.Context
// pos is the position at which the request was triggered.
pos token.Pos
@ -186,7 +191,8 @@ func Completion(ctx context.Context, f File, pos token.Pos) ([]CompletionItem, s
types: pkg.GetTypes(),
info: pkg.GetTypesInfo(),
qf: qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo()),
fset: f.GetFileSet(ctx),
view: f.View(),
ctx: ctx,
path: path,
pos: pos,
seen: make(map[types.Object]bool),

View File

@ -5,8 +5,10 @@
package source
import (
"bytes"
"fmt"
"go/ast"
"go/printer"
"go/types"
"strings"
@ -15,6 +17,11 @@ import (
// formatCompletion creates a completion item for a given types.Object.
func (c *completer) item(obj types.Object, score float64) CompletionItem {
// Handle builtin types separately.
if obj.Parent() == types.Universe {
return c.formatBuiltin(obj, score)
}
var (
label = obj.Name()
detail = types.TypeString(obj.Type(), c.qf)
@ -27,9 +34,6 @@ func (c *completer) item(obj types.Object, score float64) CompletionItem {
switch o := obj.(type) {
case *types.TypeName:
detail, kind = formatType(o.Type(), c.qf)
if obj.Parent() == types.Universe {
detail = ""
}
case *types.Const:
if obj.Parent() == types.Universe {
detail = ""
@ -57,27 +61,17 @@ func (c *completer) item(obj types.Object, score float64) CompletionItem {
if !ok {
break
}
params := formatEachParam(sig, c.qf)
label += formatParamParts(params)
detail = strings.Trim(types.TypeString(sig.Results(), c.qf), "()")
params := formatParams(sig.Params(), sig.Variadic(), c.qf)
results, writeParens := formatResults(sig.Results(), c.qf)
label, detail = formatFunction(obj.Name(), params, results, writeParens)
plainSnippet, placeholderSnippet = c.functionCallSnippets(obj.Name(), params)
kind = FunctionCompletionItem
if sig.Recv() != nil {
kind = MethodCompletionItem
}
plainSnippet, placeholderSnippet = c.functionCallSnippets(obj.Name(), params)
case *types.Builtin:
item, ok := builtinDetails[obj.Name()]
if !ok {
break
}
label, detail = item.label, item.detail
kind = FunctionCompletionItem
case *types.PkgName:
kind = PackageCompletionItem
detail = fmt.Sprintf("\"%s\"", o.Imported().Path())
case *types.Nil:
kind = VariableCompletionItem
detail = ""
}
detail = strings.TrimPrefix(detail, "untyped ")
@ -106,71 +100,77 @@ func (c *completer) isParameter(v *types.Var) bool {
return false
}
// formatType returns the detail and kind for an object of type *types.TypeName.
func formatType(typ types.Type, qf types.Qualifier) (detail string, kind CompletionItemKind) {
if types.IsInterface(typ) {
detail = "interface{...}"
kind = InterfaceCompletionItem
} else if _, ok := typ.(*types.Struct); ok {
detail = "struct{...}"
kind = StructCompletionItem
} else if typ != typ.Underlying() {
detail, kind = formatType(typ.Underlying(), qf)
} else {
detail = types.TypeString(typ, qf)
kind = TypeCompletionItem
func (c *completer) formatBuiltin(obj types.Object, score float64) CompletionItem {
item := CompletionItem{
Label: obj.Name(),
InsertText: obj.Name(),
Score: score,
}
return detail, kind
}
// formatParams correctly formats the parameters of a function.
func formatParams(sig *types.Signature, qualifier types.Qualifier) string {
return formatParamParts(formatEachParam(sig, qualifier))
}
func formatParamParts(params []string) string {
totalLen := 2 // parens
// length of each param itself
for _, p := range params {
totalLen += len(p)
}
// length of ", " separator
if len(params) > 1 {
totalLen += 2 * (len(params) - 1)
}
var b strings.Builder
b.Grow(totalLen)
b.WriteByte('(')
for i, p := range params {
if i > 0 {
b.WriteString(", ")
switch obj.(type) {
case *types.Const:
item.Kind = ConstantCompletionItem
case *types.Builtin:
fn := c.view.BuiltinPackage().Scope.Lookup(obj.Name())
decl, ok := fn.Decl.(*ast.FuncDecl)
if !ok {
break
}
b.WriteString(p)
}
b.WriteByte(')')
return b.String()
}
func formatEachParam(sig *types.Signature, qualifier types.Qualifier) []string {
params := make([]string, 0, sig.Params().Len())
for i := 0; i < sig.Params().Len(); i++ {
el := sig.Params().At(i)
typ := types.TypeString(el.Type(), qualifier)
// Handle a variadic parameter (can only be the final parameter).
if sig.Variadic() && i == sig.Params().Len()-1 {
typ = strings.Replace(typ, "[]", "...", 1)
}
if el.Name() == "" {
params = append(params, typ)
params, _ := c.formatFieldList(decl.Type.Params)
results, writeResultParens := c.formatFieldList(decl.Type.Results)
item.Label, item.Detail = formatFunction(obj.Name(), params, results, writeResultParens)
item.Snippet, item.PlaceholderSnippet = c.functionCallSnippets(obj.Name(), params)
item.Kind = FunctionCompletionItem
case *types.TypeName:
if types.IsInterface(obj.Type()) {
item.Kind = InterfaceCompletionItem
} else {
params = append(params, el.Name()+" "+typ)
item.Kind = TypeCompletionItem
}
case *types.Nil:
item.Kind = VariableCompletionItem
}
return item
}
var replacer = strings.NewReplacer(
`ComplexType`, `complex128`,
`FloatType`, `float64`,
`IntegerType`, `int`,
)
func (c *completer) formatFieldList(list *ast.FieldList) ([]string, bool) {
if list == nil {
return nil, false
}
var writeResultParens bool
var result []string
for i := 0; i < len(list.List); i++ {
if i >= 1 {
writeResultParens = true
}
p := list.List[i]
cfg := printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 4}
b := &bytes.Buffer{}
if err := cfg.Fprint(b, c.view.FileSet(), p.Type); err != nil {
c.view.Logger().Errorf(c.ctx, "unable to print type %v", p.Type)
continue
}
typ := replacer.Replace(b.String())
if len(p.Names) == 0 {
result = append(result, fmt.Sprintf("%s", typ))
}
for _, name := range p.Names {
if name.Name != "" {
if i == 0 {
writeResultParens = true
}
result = append(result, fmt.Sprintf("%s %s", name.Name, typ))
} else {
result = append(result, fmt.Sprintf("%s", typ))
}
}
}
return params
return result, writeResultParens
}
// qualifier returns a function that appropriately formats a types.PkgName
@ -200,65 +200,3 @@ func qualifier(f *ast.File, pkg *types.Package, info *types.Info) types.Qualifie
return p.Name()
}
}
type itemDetails struct {
label, detail string
}
var builtinDetails = map[string]itemDetails{
"append": { // append(slice []T, elems ...T)
label: "append(slice []T, elems ...T)",
detail: "[]T",
},
"cap": { // cap(v []T) int
label: "cap(v []T)",
detail: "int",
},
"close": { // close(c chan<- T)
label: "close(c chan<- T)",
},
"complex": { // complex(r, i float64) complex128
label: "complex(real float64, imag float64)",
detail: "complex128",
},
"copy": { // copy(dst, src []T) int
label: "copy(dst []T, src []T)",
detail: "int",
},
"delete": { // delete(m map[T]T1, key T)
label: "delete(m map[K]V, key K)",
},
"imag": { // imag(c complex128) float64
label: "imag(complex128)",
detail: "float64",
},
"len": { // len(v T) int
label: "len(T)",
detail: "int",
},
"make": { // make(t T, size ...int) T
label: "make(t T, size ...int)",
detail: "T",
},
"new": { // new(T) *T
label: "new(T)",
detail: "*T",
},
"panic": { // panic(v interface{})
label: "panic(interface{})",
},
"print": { // print(args ...T)
label: "print(args ...T)",
},
"println": { // println(args ...T)
label: "println(args ...T)",
},
"real": { // real(c complex128) float64
label: "real(complex128)",
detail: "float64",
},
"recover": { // recover() interface{}
label: "recover()",
detail: "interface{}",
},
}

View File

@ -52,7 +52,7 @@ func (c *completer) structFieldSnippets(label, detail string) (*snippet.Builder,
// If the cursor position is on a different line from the literal's opening brace,
// we are in a multiline literal.
if c.fset.Position(c.pos).Line != c.fset.Position(lit.Lbrace).Line {
if c.view.FileSet().Position(c.pos).Line != c.view.FileSet().Position(lit.Lbrace).Line {
plain.WriteText(",")
placeholder.WriteText(",")
}

View File

@ -105,23 +105,18 @@ func SignatureHelp(ctx context.Context, f File, pos token.Pos) (*SignatureInform
obj = pkg.GetTypesInfo().ObjectOf(t.Sel)
}
var label string
var name string
if obj != nil {
label = obj.Name()
name = obj.Name()
} else {
label = "func"
name = "func"
}
label += formatParams(sig, qf)
results, writeResultParens := formatResults(sig.Results(), qf)
label, detail := formatFunction(name, formatParams(sig.Params(), sig.Variadic(), qf), results, writeResultParens)
if sig.Results().Len() > 0 {
results := types.TypeString(sig.Results(), qf)
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
label += " " + detail
}
return &SignatureInformation{
Label: label,
Parameters: paramInfo,

View File

@ -5,6 +5,7 @@ import (
"go/ast"
"go/token"
"go/types"
"strings"
)
// indexExprAtPos returns the index of the expression containing pos.
@ -109,3 +110,90 @@ func deref(typ types.Type) types.Type {
}
return typ
}
func formatParams(tup *types.Tuple, variadic bool, qf types.Qualifier) []string {
params := make([]string, 0, tup.Len())
for i := 0; i < tup.Len(); i++ {
el := tup.At(i)
typ := types.TypeString(el.Type(), qf)
// Handle a variadic parameter (can only be the final parameter).
if variadic && i == tup.Len()-1 {
typ = strings.Replace(typ, "[]", "...", 1)
}
if el.Name() == "" {
params = append(params, typ)
} else {
params = append(params, el.Name()+" "+typ)
}
}
return params
}
func formatResults(tup *types.Tuple, qf types.Qualifier) ([]string, bool) {
var writeResultParens bool
results := make([]string, 0, tup.Len())
for i := 0; i < tup.Len(); i++ {
if i >= 1 {
writeResultParens = true
}
el := tup.At(i)
typ := types.TypeString(el.Type(), qf)
if el.Name() == "" {
results = append(results, typ)
} else {
if i == 0 {
writeResultParens = true
}
results = append(results, el.Name()+" "+typ)
}
}
return results, writeResultParens
}
// formatType returns the detail and kind for an object of type *types.TypeName.
func formatType(typ types.Type, qf types.Qualifier) (detail string, kind CompletionItemKind) {
if types.IsInterface(typ) {
detail = "interface{...}"
kind = InterfaceCompletionItem
} else if _, ok := typ.(*types.Struct); ok {
detail = "struct{...}"
kind = StructCompletionItem
} else if typ != typ.Underlying() {
detail, kind = formatType(typ.Underlying(), qf)
} else {
detail = types.TypeString(typ, qf)
kind = TypeCompletionItem
}
return detail, kind
}
func formatFunction(name string, params []string, results []string, writeResultParens bool) (string, string) {
var label, detail strings.Builder
label.WriteString(name)
label.WriteByte('(')
for i, p := range params {
if i > 0 {
label.WriteString(", ")
}
label.WriteString(p)
}
label.WriteByte(')')
if writeResultParens {
detail.WriteByte('(')
}
for i, p := range results {
if i > 0 {
detail.WriteString(", ")
}
detail.WriteString(p)
}
if writeResultParens {
detail.WriteByte(')')
}
return label.String(), detail.String()
}

View File

@ -24,6 +24,7 @@ import (
type View interface {
Logger() xlog.Logger
FileSet() *token.FileSet
BuiltinPackage() *ast.Package
GetFile(ctx context.Context, uri span.URI) (File, error)
SetContent(ctx context.Context, uri span.URI, content []byte) error
}

View File

@ -5,35 +5,35 @@ func _() {
}
/* Create markers for builtin types. Only for use by this test.
/* append(slice []T, elems ...T) []T */ //@item(append, "append(slice []T, elems ...T)", "[]T", "func")
/* append(slice []Type, elems ...Type) []Type */ //@item(append, "append(slice []Type, elems ...Type)", "[]Type", "func")
/* bool */ //@item(bool, "bool", "", "type")
/* byte */ //@item(byte, "byte", "", "type")
/* cap(v []T) int */ //@item(cap, "cap(v []T)", "int", "func")
/* close(c chan<- T) */ //@item(close, "close(c chan<- T)", "", "func")
/* complex(real float64, imag float64) */ //@item(complex, "complex(real float64, imag float64)", "complex128", "func")
/* cap(v Type) int */ //@item(cap, "cap(v Type)", "int", "func")
/* close(c chan<- Type) */ //@item(close, "close(c chan<- Type)", "", "func")
/* complex(r float64, i float64) */ //@item(complex, "complex(r float64, i float64)", "complex128", "func")
/* complex128 */ //@item(complex128, "complex128", "", "type")
/* complex64 */ //@item(complex64, "complex64", "", "type")
/* copy(dst []T, src []T) int */ //@item(copy, "copy(dst []T, src []T)", "int", "func")
/* delete(m map[K]V, key K) */ //@item(delete, "delete(m map[K]V, key K)", "", "func")
/* copy(dst []Type, src []Type) int */ //@item(copy, "copy(dst []Type, src []Type)", "int", "func")
/* delete(m map[Type]Type1, key Type) */ //@item(delete, "delete(m map[Type]Type1, key Type)", "", "func")
/* error */ //@item(error, "error", "", "interface")
/* false */ //@item(_false, "false", "", "const")
/* float32 */ //@item(float32, "float32", "", "type")
/* float64 */ //@item(float64, "float64", "", "type")
/* imag(complex128) float64 */ //@item(imag, "imag(complex128)", "float64", "func")
/* imag(c complex128) float64 */ //@item(imag, "imag(c complex128)", "float64", "func")
/* int */ //@item(int, "int", "", "type")
/* int16 */ //@item(int16, "int16", "", "type")
/* int32 */ //@item(int32, "int32", "", "type")
/* int64 */ //@item(int64, "int64", "", "type")
/* int8 */ //@item(int8, "int8", "", "type")
/* iota */ //@item(iota, "iota", "", "const")
/* len(T) int */ //@item(len, "len(T)", "int", "func")
/* make(t T, size ...int) T */ //@item(make, "make(t T, size ...int)", "T", "func")
/* new(T) *T */ //@item(new, "new(T)", "*T", "func")
/* len(v Type) int */ //@item(len, "len(v Type)", "int", "func")
/* make(t Type, size ...int) Type */ //@item(make, "make(t Type, size ...int)", "Type", "func")
/* new(Type) *Type */ //@item(new, "new(Type)", "*Type", "func")
/* nil */ //@item(_nil, "nil", "", "var")
/* panic(interface{}) */ //@item(panic, "panic(interface{})", "", "func")
/* print(args ...T) */ //@item(print, "print(args ...T)", "", "func")
/* println(args ...T) */ //@item(println, "println(args ...T)", "", "func")
/* real(complex128) float64 */ //@item(real, "real(complex128)", "float64", "func")
/* panic(v interface{}) */ //@item(panic, "panic(v interface{})", "", "func")
/* print(args ...Type) */ //@item(print, "print(args ...Type)", "", "func")
/* println(args ...Type) */ //@item(println, "println(args ...Type)", "", "func")
/* real(c complex128) float64 */ //@item(real, "real(c complex128)", "float64", "func")
/* recover() interface{} */ //@item(recover, "recover()", "interface{}", "func")
/* rune */ //@item(rune, "rune", "", "type")
/* string */ //@item(string, "string", "", "type")