internal/lsp: use builtin package for signature help

This change uses the builtin package to derive the signature help for
builtin functions.

Updates golang/go#31696

Change-Id: I458b3a89bdf143e7018e8be7cb6a5e8c068a47c2
Reviewed-on: https://go-review.googlesource.com/c/tools/+/176922
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/cmd/cmd_test.go b/internal/lsp/cmd/cmd_test.go
index 5474238..316b46e 100644
--- a/internal/lsp/cmd/cmd_test.go
+++ b/internal/lsp/cmd/cmd_test.go
@@ -55,7 +55,7 @@
 	//TODO: add command line symbol tests when it works
 }
 
-func (r *runner) Signature(t *testing.T, data tests.Signatures) {
+func (r *runner) SignatureHelp(t *testing.T, data tests.Signatures) {
 	//TODO: add command line signature tests when it works
 }
 
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index 05bae65..7c4943b 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -471,7 +471,7 @@
 	return msg.String()
 }
 
-func (r *runner) Signature(t *testing.T, data tests.Signatures) {
+func (r *runner) SignatureHelp(t *testing.T, data tests.Signatures) {
 	for spn, expectedSignatures := range data {
 		m := r.mapper(spn.URI())
 		loc, err := m.Location(spn)
diff --git a/internal/lsp/source/completion_format.go b/internal/lsp/source/completion_format.go
index 6894254..da3774e 100644
--- a/internal/lsp/source/completion_format.go
+++ b/internal/lsp/source/completion_format.go
@@ -6,6 +6,7 @@
 
 import (
 	"bytes"
+	"context"
 	"fmt"
 	"go/ast"
 	"go/printer"
@@ -111,20 +112,12 @@
 		item.Kind = ConstantCompletionItem
 	case *types.Builtin:
 		item.Kind = FunctionCompletionItem
-		builtinPkg := c.view.BuiltinPackage()
-		if builtinPkg == nil || builtinPkg.Scope == nil {
+		decl := lookupBuiltin(c.view, obj.Name())
+		if decl == nil {
 			break
 		}
-		fn := builtinPkg.Scope.Lookup(obj.Name())
-		if fn == nil {
-			break
-		}
-		decl, ok := fn.Decl.(*ast.FuncDecl)
-		if !ok {
-			break
-		}
-		params, _ := c.formatFieldList(decl.Type.Params)
-		results, writeResultParens := c.formatFieldList(decl.Type.Results)
+		params, _ := formatFieldList(c.ctx, c.view, decl.Type.Params)
+		results, writeResultParens := formatFieldList(c.ctx, c.view, decl.Type.Results)
 		item.Label, item.Detail = formatFunction(obj.Name(), params, results, writeResultParens)
 		item.plainSnippet, item.placeholderSnippet = c.functionCallSnippets(obj.Name(), params)
 	case *types.TypeName:
@@ -139,13 +132,29 @@
 	return item
 }
 
+func lookupBuiltin(v View, name string) *ast.FuncDecl {
+	builtinPkg := v.BuiltinPackage()
+	if builtinPkg == nil || builtinPkg.Scope == nil {
+		return nil
+	}
+	fn := builtinPkg.Scope.Lookup(name)
+	if fn == nil {
+		return nil
+	}
+	decl, ok := fn.Decl.(*ast.FuncDecl)
+	if !ok {
+		return nil
+	}
+	return decl
+}
+
 var replacer = strings.NewReplacer(
 	`ComplexType`, `complex128`,
 	`FloatType`, `float64`,
 	`IntegerType`, `int`,
 )
 
-func (c *completer) formatFieldList(list *ast.FieldList) ([]string, bool) {
+func formatFieldList(ctx context.Context, v View, list *ast.FieldList) ([]string, bool) {
 	if list == nil {
 		return nil, false
 	}
@@ -158,8 +167,8 @@
 		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)
+		if err := cfg.Fprint(b, v.FileSet(), p.Type); err != nil {
+			v.Logger().Errorf(ctx, "unable to print type %v", p.Type)
 			continue
 		}
 		typ := replacer.Replace(b.String())
diff --git a/internal/lsp/source/signature_help.go b/internal/lsp/source/signature_help.go
index 1d9f81f..ea5a644 100644
--- a/internal/lsp/source/signature_help.go
+++ b/internal/lsp/source/signature_help.go
@@ -10,7 +10,6 @@
 	"go/ast"
 	"go/token"
 	"go/types"
-	"strings"
 
 	"golang.org/x/tools/go/ast/astutil"
 )
@@ -48,6 +47,22 @@
 		return nil, fmt.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 {
@@ -60,21 +75,60 @@
 	}
 
 	qf := qualifier(fAST, pkg.GetTypes(), pkg.GetTypesInfo())
-	var paramInfo []ParameterInformation
-	for i := 0; i < sig.Params().Len(); i++ {
-		param := sig.Params().At(i)
-		label := types.TypeString(param.Type(), qf)
-		if sig.Variadic() && i == sig.Params().Len()-1 {
-			label = strings.Replace(label, "[]", "...", 1)
-		}
-		if param.Name() != "" {
-			label = fmt.Sprintf("%s %s", param.Name(), label)
-		}
-		paramInfo = append(paramInfo, ParameterInformation{
-			Label: label,
-		})
-	}
+	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
+	if obj != nil {
+		name = obj.Name()
+	} else {
+		name = "func"
+	}
+	return signatureInformation(name, params, results, writeResultParens, activeParam), nil
+}
+
+func builtinSignature(ctx context.Context, v View, callExpr *ast.CallExpr, name string, pos token.Pos) (*SignatureInformation, error) {
+	decl := lookupBuiltin(v, name)
+	if decl == nil {
+		return nil, fmt.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, params, results, writeResultParens, activeParam), nil
+}
+
+func signatureInformation(name string, 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, detail := formatFunction(name, params, results, writeResultParens)
+	// Show return values of the function in the label.
+	if detail != "" {
+		label += " " + detail
+	}
+	return &SignatureInformation{
+		Label:           label,
+		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
@@ -88,38 +142,10 @@
 		}
 
 		// Don't advance the active parameter for the last parameter of a variadic function.
-		if !sig.Variadic() || activeParam < sig.Params().Len()-1 {
+		if !variadic || activeParam < numParams-1 {
 			activeParam++
 		}
 		start = expr.Pos() + 1 // to account for commas
 	}
-
-	// 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)
-	}
-
-	var name string
-	if obj != nil {
-		name = obj.Name()
-	} else {
-		name = "func"
-	}
-
-	results, writeResultParens := formatResults(sig.Results(), qf)
-	label, detail := formatFunction(name, formatParams(sig.Params(), sig.Variadic(), qf), results, writeResultParens)
-	if sig.Results().Len() > 0 {
-		label += " " + detail
-	}
-	return &SignatureInformation{
-		Label:           label,
-		Parameters:      paramInfo,
-		ActiveParameter: activeParam,
-	}, nil
+	return activeParam
 }
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index 7913b9c..7087430 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -420,7 +420,7 @@
 	return msg.String()
 }
 
-func (r *runner) Signature(t *testing.T, data tests.Signatures) {
+func (r *runner) SignatureHelp(t *testing.T, data tests.Signatures) {
 	ctx := context.Background()
 	for spn, expectedSignatures := range data {
 		f, err := r.view.GetFile(ctx, spn.URI())
diff --git a/internal/lsp/testdata/signature/signature.go b/internal/lsp/testdata/signature/signature.go
index 7e826a0..dd50035 100644
--- a/internal/lsp/testdata/signature/signature.go
+++ b/internal/lsp/testdata/signature/signature.go
@@ -53,10 +53,11 @@
 	var ms myStruct
 	ms.foo(nil) //@signature("nil", "foo(e *json.Decoder) (*big.Int, error)", 0)
 
-	_ = make([]int, 1, 2) //@signature("2", "make([]int, int, int) []int", 2)
+	_ = make([]int, 1, 2) //@signature("2", "make(t Type, size ...int) Type", 1)
 
 	Foo(myFunc(123), 456) //@signature("myFunc", "Foo(a string, b int) (c bool)", 0)
 	Foo(myFunc(123), 456) //@signature("123", "myFunc(foo int) string", 0)
 
-	panic("oops!") //@signature("oops", "panic(interface{})", 0)
+	panic("oops!")            //@signature("oops", "panic(v interface{})", 0)
+	println("hello", "world") //@signature("world", "println(args ...Type)", 0)
 }
diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go
index 5ffe3ac..a263160 100644
--- a/internal/lsp/tests/tests.go
+++ b/internal/lsp/tests/tests.go
@@ -35,7 +35,7 @@
 	ExpectedTypeDefinitionsCount   = 2
 	ExpectedHighlightsCount        = 2
 	ExpectedSymbolsCount           = 1
-	ExpectedSignaturesCount        = 19
+	ExpectedSignaturesCount        = 20
 	ExpectedCompletionSnippetCount = 11
 	ExpectedLinksCount             = 2
 )
@@ -89,7 +89,7 @@
 	Definition(*testing.T, Definitions)
 	Highlight(*testing.T, Highlights)
 	Symbol(*testing.T, Symbols)
-	Signature(*testing.T, Signatures)
+	SignatureHelp(*testing.T, Signatures)
 	Link(*testing.T, Links)
 }
 
@@ -291,12 +291,12 @@
 		tests.Symbol(t, data.Symbols)
 	})
 
-	t.Run("Signatures", func(t *testing.T) {
+	t.Run("SignatureHelp", func(t *testing.T) {
 		t.Helper()
 		if len(data.Signatures) != ExpectedSignaturesCount {
 			t.Errorf("got %v signatures expected %v", len(data.Signatures), ExpectedSignaturesCount)
 		}
-		tests.Signature(t, data.Signatures)
+		tests.SignatureHelp(t, data.Signatures)
 	})
 
 	t.Run("Links", func(t *testing.T) {