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>
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index 913cf68..3b59ed5 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -6,6 +6,8 @@
import (
"context"
+ "go/ast"
+ "go/parser"
"go/token"
"go/types"
"os"
@@ -63,6 +65,9 @@
// 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 @@
v := &View{
baseCtx: ctx,
backgroundCtx: backgroundCtx,
+ builtinPkg: builtinPkg(*config),
cancel: cancel,
log: log,
Config: *config,
@@ -118,6 +124,32 @@
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
}
diff --git a/internal/lsp/diagnostics.go b/internal/lsp/diagnostics.go
index 244dc40..ee634a6 100644
--- a/internal/lsp/diagnostics.go
+++ b/internal/lsp/diagnostics.go
@@ -14,24 +14,17 @@
)
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 @@
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 @@
// 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 @@
delete(s.undelivered, uri)
}
}()
-
- s.log.Debugf(ctx, "cacheAndDiagnose: returned from diagnostics for %s", uri)
return nil
}
diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go
index ca05bbc..3761542 100644
--- a/internal/lsp/source/completion.go
+++ b/internal/lsp/source/completion.go
@@ -93,7 +93,12 @@
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 @@
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),
diff --git a/internal/lsp/source/completion_format.go b/internal/lsp/source/completion_format.go
index 1ab5a3b..046480f 100644
--- a/internal/lsp/source/completion_format.go
+++ b/internal/lsp/source/completion_format.go
@@ -5,8 +5,10 @@
package source
import (
+ "bytes"
"fmt"
"go/ast"
+ "go/printer"
"go/types"
"strings"
@@ -15,6 +17,11 @@
// 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 @@
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 @@
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 @@
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 @@
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{}",
- },
-}
diff --git a/internal/lsp/source/completion_snippet.go b/internal/lsp/source/completion_snippet.go
index 7cd6db7..1f2c153 100644
--- a/internal/lsp/source/completion_snippet.go
+++ b/internal/lsp/source/completion_snippet.go
@@ -52,7 +52,7 @@
// 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(",")
}
diff --git a/internal/lsp/source/signature_help.go b/internal/lsp/source/signature_help.go
index 54433f6..44872fa 100644
--- a/internal/lsp/source/signature_help.go
+++ b/internal/lsp/source/signature_help.go
@@ -105,23 +105,18 @@
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,
diff --git a/internal/lsp/source/util.go b/internal/lsp/source/util.go
index a4d3745..d53d7c1 100644
--- a/internal/lsp/source/util.go
+++ b/internal/lsp/source/util.go
@@ -5,6 +5,7 @@
"go/ast"
"go/token"
"go/types"
+ "strings"
)
// indexExprAtPos returns the index of the expression containing pos.
@@ -109,3 +110,90 @@
}
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()
+}
diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go
index 5975f22..b9ce7e7 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -24,6 +24,7 @@
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
}
diff --git a/internal/lsp/testdata/builtins/builtins.go b/internal/lsp/testdata/builtins/builtins.go
index c85a763..ae7280e 100644
--- a/internal/lsp/testdata/builtins/builtins.go
+++ b/internal/lsp/testdata/builtins/builtins.go
@@ -5,35 +5,35 @@
}
/* 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")