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")