diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go index 913cf6819f0..3b59ed5b3c9 100644 --- a/internal/lsp/cache/view.go +++ b/internal/lsp/cache/view.go @@ -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 } diff --git a/internal/lsp/diagnostics.go b/internal/lsp/diagnostics.go index 244dc40a06a..ee634a6570e 100644 --- a/internal/lsp/diagnostics.go +++ b/internal/lsp/diagnostics.go @@ -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 } diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go index ca05bbcaaf8..3761542d84f 100644 --- a/internal/lsp/source/completion.go +++ b/internal/lsp/source/completion.go @@ -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), diff --git a/internal/lsp/source/completion_format.go b/internal/lsp/source/completion_format.go index 1ab5a3bf484..046480fcc09 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 @@ 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{}", - }, -} diff --git a/internal/lsp/source/completion_snippet.go b/internal/lsp/source/completion_snippet.go index 7cd6db7946d..1f2c1533861 100644 --- a/internal/lsp/source/completion_snippet.go +++ b/internal/lsp/source/completion_snippet.go @@ -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(",") } diff --git a/internal/lsp/source/signature_help.go b/internal/lsp/source/signature_help.go index 54433f60593..44872fab11e 100644 --- a/internal/lsp/source/signature_help.go +++ b/internal/lsp/source/signature_help.go @@ -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, diff --git a/internal/lsp/source/util.go b/internal/lsp/source/util.go index a4d37453857..d53d7c12e80 100644 --- a/internal/lsp/source/util.go +++ b/internal/lsp/source/util.go @@ -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() +} diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go index 5975f222b21..b9ce7e74b78 100644 --- a/internal/lsp/source/view.go +++ b/internal/lsp/source/view.go @@ -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 } diff --git a/internal/lsp/testdata/builtins/builtins.go b/internal/lsp/testdata/builtins/builtins.go index c85a763aaba..ae7280e03a6 100644 --- a/internal/lsp/testdata/builtins/builtins.go +++ b/internal/lsp/testdata/builtins/builtins.go @@ -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")