diff options
Diffstat (limited to 'gopls/internal/lsp/source/completion')
19 files changed, 7288 insertions, 0 deletions
diff --git a/gopls/internal/lsp/source/completion/builtin.go b/gopls/internal/lsp/source/completion/builtin.go new file mode 100644 index 000000000..39732d864 --- /dev/null +++ b/gopls/internal/lsp/source/completion/builtin.go @@ -0,0 +1,147 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package completion + +import ( + "context" + "go/ast" + "go/types" +) + +// builtinArgKind determines the expected object kind for a builtin +// argument. It attempts to use the AST hints from builtin.go where +// possible. +func (c *completer) builtinArgKind(ctx context.Context, obj types.Object, call *ast.CallExpr) objKind { + builtin, err := c.snapshot.BuiltinFile(ctx) + if err != nil { + return 0 + } + exprIdx := exprAtPos(c.pos, call.Args) + + builtinObj := builtin.File.Scope.Lookup(obj.Name()) + if builtinObj == nil { + return 0 + } + decl, ok := builtinObj.Decl.(*ast.FuncDecl) + if !ok || exprIdx >= len(decl.Type.Params.List) { + return 0 + } + + switch ptyp := decl.Type.Params.List[exprIdx].Type.(type) { + case *ast.ChanType: + return kindChan + case *ast.ArrayType: + return kindSlice + case *ast.MapType: + return kindMap + case *ast.Ident: + switch ptyp.Name { + case "Type": + switch obj.Name() { + case "make": + return kindChan | kindSlice | kindMap + case "len": + return kindSlice | kindMap | kindArray | kindString | kindChan + case "cap": + return kindSlice | kindArray | kindChan + } + } + } + + return 0 +} + +// builtinArgType infers the type of an argument to a builtin +// function. parentInf is the inferred type info for the builtin +// call's parent node. +func (c *completer) builtinArgType(obj types.Object, call *ast.CallExpr, parentInf candidateInference) candidateInference { + var ( + exprIdx = exprAtPos(c.pos, call.Args) + + // Propagate certain properties from our parent's inference. + inf = candidateInference{ + typeName: parentInf.typeName, + modifiers: parentInf.modifiers, + } + ) + + switch obj.Name() { + case "append": + if exprIdx <= 0 { + // Infer first append() arg type as apparent return type of + // append(). + inf.objType = parentInf.objType + if parentInf.variadic { + inf.objType = types.NewSlice(inf.objType) + } + break + } + + // For non-initial append() args, infer slice type from the first + // append() arg, or from parent context. + if len(call.Args) > 0 { + inf.objType = c.pkg.GetTypesInfo().TypeOf(call.Args[0]) + } + if inf.objType == nil { + inf.objType = parentInf.objType + } + if inf.objType == nil { + break + } + + inf.objType = deslice(inf.objType) + + // Check if we are completing the variadic append() param. + inf.variadic = exprIdx == 1 && len(call.Args) <= 2 + + // Penalize the first append() argument as a candidate. You + // don't normally append a slice to itself. + if sliceChain := objChain(c.pkg.GetTypesInfo(), call.Args[0]); len(sliceChain) > 0 { + inf.penalized = append(inf.penalized, penalizedObj{objChain: sliceChain, penalty: 0.9}) + } + case "delete": + if exprIdx > 0 && len(call.Args) > 0 { + // Try to fill in expected type of map key. + firstArgType := c.pkg.GetTypesInfo().TypeOf(call.Args[0]) + if firstArgType != nil { + if mt, ok := firstArgType.Underlying().(*types.Map); ok { + inf.objType = mt.Key() + } + } + } + case "copy": + var t1, t2 types.Type + if len(call.Args) > 0 { + t1 = c.pkg.GetTypesInfo().TypeOf(call.Args[0]) + if len(call.Args) > 1 { + t2 = c.pkg.GetTypesInfo().TypeOf(call.Args[1]) + } + } + + // Fill in expected type of either arg if the other is already present. + if exprIdx == 1 && t1 != nil { + inf.objType = t1 + } else if exprIdx == 0 && t2 != nil { + inf.objType = t2 + } + case "new": + inf.typeName.wantTypeName = true + if parentInf.objType != nil { + // Expected type for "new" is the de-pointered parent type. + if ptr, ok := parentInf.objType.Underlying().(*types.Pointer); ok { + inf.objType = ptr.Elem() + } + } + case "make": + if exprIdx == 0 { + inf.typeName.wantTypeName = true + inf.objType = parentInf.objType + } else { + inf.objType = types.Typ[types.UntypedInt] + } + } + + return inf +} diff --git a/gopls/internal/lsp/source/completion/completion.go b/gopls/internal/lsp/source/completion/completion.go new file mode 100644 index 000000000..f8c7654f6 --- /dev/null +++ b/gopls/internal/lsp/source/completion/completion.go @@ -0,0 +1,3252 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package completion provides core functionality for code completion in Go +// editors and tools. +package completion + +import ( + "bytes" + "context" + "fmt" + "go/ast" + "go/constant" + "go/parser" + "go/scanner" + "go/token" + "go/types" + "math" + "sort" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + "unicode" + + "golang.org/x/sync/errgroup" + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/gopls/internal/lsp/protocol" + "golang.org/x/tools/gopls/internal/lsp/safetoken" + "golang.org/x/tools/gopls/internal/lsp/snippet" + "golang.org/x/tools/gopls/internal/lsp/source" + "golang.org/x/tools/gopls/internal/span" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/fuzzy" + "golang.org/x/tools/internal/imports" + "golang.org/x/tools/internal/typeparams" +) + +// A CompletionItem represents a possible completion suggested by the algorithm. +type CompletionItem struct { + + // Invariant: CompletionItem does not refer to syntax or types. + + // Label is the primary text the user sees for this completion item. + Label string + + // Detail is supplemental information to present to the user. + // This often contains the type or return type of the completion item. + Detail string + + // InsertText is the text to insert if this item is selected. + // Any of the prefix that has already been typed is not trimmed. + // The insert text does not contain snippets. + InsertText string + + Kind protocol.CompletionItemKind + Tags []protocol.CompletionItemTag + Deprecated bool // Deprecated, prefer Tags if available + + // An optional array of additional TextEdits that are applied when + // selecting this completion. + // + // Additional text edits should be used to change text unrelated to the current cursor position + // (for example adding an import statement at the top of the file if the completion item will + // insert an unqualified type). + AdditionalTextEdits []protocol.TextEdit + + // Depth is how many levels were searched to find this completion. + // For example when completing "foo<>", "fooBar" is depth 0, and + // "fooBar.Baz" is depth 1. + Depth int + + // Score is the internal relevance score. + // A higher score indicates that this completion item is more relevant. + Score float64 + + // snippet is the LSP snippet for the completion item. The LSP + // specification contains details about LSP snippets. For example, a + // snippet for a function with the following signature: + // + // func foo(a, b, c int) + // + // would be: + // + // foo(${1:a int}, ${2: b int}, ${3: c int}) + // + // If Placeholders is false in the CompletionOptions, the above + // snippet would instead be: + // + // foo(${1:}) + snippet *snippet.Builder + + // Documentation is the documentation for the completion item. + Documentation string + + // isSlice reports whether the underlying type of the object + // from which this candidate was derived is a slice. + // (Used to complete append() calls.) + isSlice bool +} + +// completionOptions holds completion specific configuration. +type completionOptions struct { + unimported bool + documentation bool + fullDocumentation bool + placeholders bool + literal bool + snippets bool + postfix bool + matcher source.Matcher + budget time.Duration +} + +// Snippet is a convenience returns the snippet if available, otherwise +// the InsertText. +// used for an item, depending on if the callee wants placeholders or not. +func (i *CompletionItem) Snippet() string { + if i.snippet != nil { + return i.snippet.String() + } + return i.InsertText +} + +// Scoring constants are used for weighting the relevance of different candidates. +const ( + // stdScore is the base score for all completion items. + stdScore float64 = 1.0 + + // highScore indicates a very relevant completion item. + highScore float64 = 10.0 + + // lowScore indicates an irrelevant or not useful completion item. + lowScore float64 = 0.01 +) + +// matcher matches a candidate's label against the user input. The +// returned score reflects the quality of the match. A score of zero +// indicates no match, and a score of one means a perfect match. +type matcher interface { + Score(candidateLabel string) (score float32) +} + +// prefixMatcher implements case sensitive prefix matching. +type prefixMatcher string + +func (pm prefixMatcher) Score(candidateLabel string) float32 { + if strings.HasPrefix(candidateLabel, string(pm)) { + return 1 + } + return -1 +} + +// insensitivePrefixMatcher implements case insensitive prefix matching. +type insensitivePrefixMatcher string + +func (ipm insensitivePrefixMatcher) Score(candidateLabel string) float32 { + if strings.HasPrefix(strings.ToLower(candidateLabel), string(ipm)) { + return 1 + } + return -1 +} + +// completer contains the necessary information for a single completion request. +type completer struct { + snapshot source.Snapshot + pkg source.Package + qf types.Qualifier // for qualifying typed expressions + mq source.MetadataQualifier // for syntactic qualifying + opts *completionOptions + + // completionContext contains information about the trigger for this + // completion request. + completionContext completionContext + + // fh is a handle to the file associated with this completion request. + fh source.FileHandle + + // filename is the name of the file associated with this completion request. + filename string + + // file is the AST of the file associated with this completion request. + file *ast.File + + // (tokFile, pos) is the position at which the request was triggered. + tokFile *token.File + pos token.Pos + + // path is the path of AST nodes enclosing the position. + path []ast.Node + + // seen is the map that ensures we do not return duplicate results. + seen map[types.Object]bool + + // items is the list of completion items returned. + items []CompletionItem + + // completionCallbacks is a list of callbacks to collect completions that + // require expensive operations. This includes operations where we search + // through the entire module cache. + completionCallbacks []func(opts *imports.Options) error + + // surrounding describes the identifier surrounding the position. + surrounding *Selection + + // inference contains information we've inferred about ideal + // candidates such as the candidate's type. + inference candidateInference + + // enclosingFunc contains information about the function enclosing + // the position. + enclosingFunc *funcInfo + + // enclosingCompositeLiteral contains information about the composite literal + // enclosing the position. + enclosingCompositeLiteral *compLitInfo + + // deepState contains the current state of our deep completion search. + deepState deepCompletionState + + // matcher matches the candidates against the surrounding prefix. + matcher matcher + + // methodSetCache caches the types.NewMethodSet call, which is relatively + // expensive and can be called many times for the same type while searching + // for deep completions. + methodSetCache map[methodSetKey]*types.MethodSet + + // mapper converts the positions in the file from which the completion originated. + mapper *protocol.Mapper + + // startTime is when we started processing this completion request. It does + // not include any time the request spent in the queue. + startTime time.Time + + // scopes contains all scopes defined by nodes in our path, + // including nil values for nodes that don't defined a scope. It + // also includes our package scope and the universal scope at the + // end. + scopes []*types.Scope +} + +// funcInfo holds info about a function object. +type funcInfo struct { + // sig is the function declaration enclosing the position. + sig *types.Signature + + // body is the function's body. + body *ast.BlockStmt +} + +type compLitInfo struct { + // cl is the *ast.CompositeLit enclosing the position. + cl *ast.CompositeLit + + // clType is the type of cl. + clType types.Type + + // kv is the *ast.KeyValueExpr enclosing the position, if any. + kv *ast.KeyValueExpr + + // inKey is true if we are certain the position is in the key side + // of a key-value pair. + inKey bool + + // maybeInFieldName is true if inKey is false and it is possible + // we are completing a struct field name. For example, + // "SomeStruct{<>}" will be inKey=false, but maybeInFieldName=true + // because we _could_ be completing a field name. + maybeInFieldName bool +} + +type importInfo struct { + importPath string + name string +} + +type methodSetKey struct { + typ types.Type + addressable bool +} + +type completionContext struct { + // triggerCharacter is the character used to trigger completion at current + // position, if any. + triggerCharacter string + + // triggerKind is information about how a completion was triggered. + triggerKind protocol.CompletionTriggerKind + + // commentCompletion is true if we are completing a comment. + commentCompletion bool + + // packageCompletion is true if we are completing a package name. + packageCompletion bool +} + +// A Selection represents the cursor position and surrounding identifier. +type Selection struct { + content string + tokFile *token.File + start, end, cursor token.Pos // relative to rng.TokFile + mapper *protocol.Mapper +} + +func (p Selection) Content() string { + return p.content +} + +func (p Selection) Range() (protocol.Range, error) { + return p.mapper.PosRange(p.tokFile, p.start, p.end) +} + +func (p Selection) Prefix() string { + return p.content[:p.cursor-p.start] +} + +func (p Selection) Suffix() string { + return p.content[p.cursor-p.start:] +} + +func (c *completer) setSurrounding(ident *ast.Ident) { + if c.surrounding != nil { + return + } + if !(ident.Pos() <= c.pos && c.pos <= ident.End()) { + return + } + + c.surrounding = &Selection{ + content: ident.Name, + cursor: c.pos, + // Overwrite the prefix only. + tokFile: c.tokFile, + start: ident.Pos(), + end: ident.End(), + mapper: c.mapper, + } + + c.setMatcherFromPrefix(c.surrounding.Prefix()) +} + +func (c *completer) setMatcherFromPrefix(prefix string) { + switch c.opts.matcher { + case source.Fuzzy: + c.matcher = fuzzy.NewMatcher(prefix) + case source.CaseSensitive: + c.matcher = prefixMatcher(prefix) + default: + c.matcher = insensitivePrefixMatcher(strings.ToLower(prefix)) + } +} + +func (c *completer) getSurrounding() *Selection { + if c.surrounding == nil { + c.surrounding = &Selection{ + content: "", + cursor: c.pos, + tokFile: c.tokFile, + start: c.pos, + end: c.pos, + mapper: c.mapper, + } + } + return c.surrounding +} + +// candidate represents a completion candidate. +type candidate struct { + // obj is the types.Object to complete to. + // TODO(adonovan): eliminate dependence on go/types throughout this struct. + obj types.Object + + // score is used to rank candidates. + score float64 + + // name is the deep object name path, e.g. "foo.bar" + name string + + // detail is additional information about this item. If not specified, + // defaults to type string for the object. + detail string + + // path holds the path from the search root (excluding the candidate + // itself) for a deep candidate. + path []types.Object + + // pathInvokeMask is a bit mask tracking whether each entry in path + // should be formatted with "()" (i.e. whether it is a function + // invocation). + pathInvokeMask uint16 + + // mods contains modifications that should be applied to the + // candidate when inserted. For example, "foo" may be inserted as + // "*foo" or "foo()". + mods []typeModKind + + // addressable is true if a pointer can be taken to the candidate. + addressable bool + + // convertTo is a type that this candidate should be cast to. For + // example, if convertTo is float64, "foo" should be formatted as + // "float64(foo)". + convertTo types.Type + + // imp is the import that needs to be added to this package in order + // for this candidate to be valid. nil if no import needed. + imp *importInfo +} + +func (c candidate) hasMod(mod typeModKind) bool { + for _, m := range c.mods { + if m == mod { + return true + } + } + return false +} + +// ErrIsDefinition is an error that informs the user they got no +// completions because they tried to complete the name of a new object +// being defined. +type ErrIsDefinition struct { + objStr string +} + +func (e ErrIsDefinition) Error() string { + msg := "this is a definition" + if e.objStr != "" { + msg += " of " + e.objStr + } + return msg +} + +// Completion returns a list of possible candidates for completion, given a +// a file and a position. +// +// The selection is computed based on the preceding identifier and can be used by +// the client to score the quality of the completion. For instance, some clients +// may tolerate imperfect matches as valid completion results, since users may make typos. +func Completion(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, protoPos protocol.Position, protoContext protocol.CompletionContext) ([]CompletionItem, *Selection, error) { + ctx, done := event.Start(ctx, "completion.Completion") + defer done() + + startTime := time.Now() + + pkg, pgf, err := source.PackageForFile(ctx, snapshot, fh.URI(), source.NarrowestPackage) + if err != nil || pgf.File.Package == token.NoPos { + // If we can't parse this file or find position for the package + // keyword, it may be missing a package declaration. Try offering + // suggestions for the package declaration. + // Note that this would be the case even if the keyword 'package' is + // present but no package name exists. + items, surrounding, innerErr := packageClauseCompletions(ctx, snapshot, fh, protoPos) + if innerErr != nil { + // return the error for GetParsedFile since it's more relevant in this situation. + return nil, nil, fmt.Errorf("getting file %s for Completion: %w (package completions: %v)", fh.URI(), err, innerErr) + } + return items, surrounding, nil + } + pos, err := pgf.PositionPos(protoPos) + if err != nil { + return nil, nil, err + } + // Completion is based on what precedes the cursor. + // Find the path to the position before pos. + path, _ := astutil.PathEnclosingInterval(pgf.File, pos-1, pos-1) + if path == nil { + return nil, nil, fmt.Errorf("cannot find node enclosing position") + } + + // Check if completion at this position is valid. If not, return early. + switch n := path[0].(type) { + case *ast.BasicLit: + // Skip completion inside literals except for ImportSpec + if len(path) > 1 { + if _, ok := path[1].(*ast.ImportSpec); ok { + break + } + } + return nil, nil, nil + case *ast.CallExpr: + if n.Ellipsis.IsValid() && pos > n.Ellipsis && pos <= n.Ellipsis+token.Pos(len("...")) { + // Don't offer completions inside or directly after "...". For + // example, don't offer completions at "<>" in "foo(bar...<>"). + return nil, nil, nil + } + case *ast.Ident: + // reject defining identifiers + if obj, ok := pkg.GetTypesInfo().Defs[n]; ok { + if v, ok := obj.(*types.Var); ok && v.IsField() && v.Embedded() { + // An anonymous field is also a reference to a type. + } else if pgf.File.Name == n { + // Don't skip completions if Ident is for package name. + break + } else { + objStr := "" + if obj != nil { + qual := types.RelativeTo(pkg.GetTypes()) + objStr = types.ObjectString(obj, qual) + } + ans, sel := definition(path, obj, pgf) + if ans != nil { + sort.Slice(ans, func(i, j int) bool { + return ans[i].Score > ans[j].Score + }) + return ans, sel, nil + } + return nil, nil, ErrIsDefinition{objStr: objStr} + } + } + } + + // Collect all surrounding scopes, innermost first. + scopes := source.CollectScopes(pkg.GetTypesInfo(), path, pos) + scopes = append(scopes, pkg.GetTypes().Scope(), types.Universe) + + opts := snapshot.View().Options() + c := &completer{ + pkg: pkg, + snapshot: snapshot, + qf: source.Qualifier(pgf.File, pkg.GetTypes(), pkg.GetTypesInfo()), + mq: source.MetadataQualifierForFile(snapshot, pgf.File, pkg.Metadata()), + completionContext: completionContext{ + triggerCharacter: protoContext.TriggerCharacter, + triggerKind: protoContext.TriggerKind, + }, + fh: fh, + filename: fh.URI().Filename(), + tokFile: pgf.Tok, + file: pgf.File, + path: path, + pos: pos, + seen: make(map[types.Object]bool), + enclosingFunc: enclosingFunction(path, pkg.GetTypesInfo()), + enclosingCompositeLiteral: enclosingCompositeLiteral(path, pos, pkg.GetTypesInfo()), + deepState: deepCompletionState{ + enabled: opts.DeepCompletion, + }, + opts: &completionOptions{ + matcher: opts.Matcher, + unimported: opts.CompleteUnimported, + documentation: opts.CompletionDocumentation && opts.HoverKind != source.NoDocumentation, + fullDocumentation: opts.HoverKind == source.FullDocumentation, + placeholders: opts.UsePlaceholders, + literal: opts.LiteralCompletions && opts.InsertTextFormat == protocol.SnippetTextFormat, + budget: opts.CompletionBudget, + snippets: opts.InsertTextFormat == protocol.SnippetTextFormat, + postfix: opts.ExperimentalPostfixCompletions, + }, + // default to a matcher that always matches + matcher: prefixMatcher(""), + methodSetCache: make(map[methodSetKey]*types.MethodSet), + mapper: pgf.Mapper, + startTime: startTime, + scopes: scopes, + } + + var cancel context.CancelFunc + if c.opts.budget == 0 { + ctx, cancel = context.WithCancel(ctx) + } else { + // timeoutDuration is the completion budget remaining. If less than + // 10ms, set to 10ms + timeoutDuration := time.Until(c.startTime.Add(c.opts.budget)) + if timeoutDuration < 10*time.Millisecond { + timeoutDuration = 10 * time.Millisecond + } + ctx, cancel = context.WithTimeout(ctx, timeoutDuration) + } + defer cancel() + + if surrounding := c.containingIdent(pgf.Src); surrounding != nil { + c.setSurrounding(surrounding) + } + + c.inference = expectedCandidate(ctx, c) + + err = c.collectCompletions(ctx) + if err != nil { + return nil, nil, err + } + + // Deep search collected candidates and their members for more candidates. + c.deepSearch(ctx) + + for _, callback := range c.completionCallbacks { + if err := c.snapshot.RunProcessEnvFunc(ctx, callback); err != nil { + return nil, nil, err + } + } + + // Search candidates populated by expensive operations like + // unimportedMembers etc. for more completion items. + c.deepSearch(ctx) + + // Statement candidates offer an entire statement in certain contexts, as + // opposed to a single object. Add statement candidates last because they + // depend on other candidates having already been collected. + c.addStatementCandidates() + + c.sortItems() + return c.items, c.getSurrounding(), nil +} + +// collectCompletions adds possible completion candidates to either the deep +// search queue or completion items directly for different completion contexts. +func (c *completer) collectCompletions(ctx context.Context) error { + // Inside import blocks, return completions for unimported packages. + for _, importSpec := range c.file.Imports { + if !(importSpec.Path.Pos() <= c.pos && c.pos <= importSpec.Path.End()) { + continue + } + return c.populateImportCompletions(ctx, importSpec) + } + + // Inside comments, offer completions for the name of the relevant symbol. + for _, comment := range c.file.Comments { + if comment.Pos() < c.pos && c.pos <= comment.End() { + c.populateCommentCompletions(ctx, comment) + return nil + } + } + + // Struct literals are handled entirely separately. + if c.wantStructFieldCompletions() { + // If we are definitely completing a struct field name, deep completions + // don't make sense. + if c.enclosingCompositeLiteral.inKey { + c.deepState.enabled = false + } + return c.structLiteralFieldName(ctx) + } + + if lt := c.wantLabelCompletion(); lt != labelNone { + c.labels(lt) + return nil + } + + if c.emptySwitchStmt() { + // Empty switch statements only admit "default" and "case" keywords. + c.addKeywordItems(map[string]bool{}, highScore, CASE, DEFAULT) + return nil + } + + switch n := c.path[0].(type) { + case *ast.Ident: + if c.file.Name == n { + return c.packageNameCompletions(ctx, c.fh.URI(), n) + } else if sel, ok := c.path[1].(*ast.SelectorExpr); ok && sel.Sel == n { + // Is this the Sel part of a selector? + return c.selector(ctx, sel) + } + return c.lexical(ctx) + // The function name hasn't been typed yet, but the parens are there: + // recv.‸(arg) + case *ast.TypeAssertExpr: + // Create a fake selector expression. + return c.selector(ctx, &ast.SelectorExpr{X: n.X}) + case *ast.SelectorExpr: + return c.selector(ctx, n) + // At the file scope, only keywords are allowed. + case *ast.BadDecl, *ast.File: + c.addKeywordCompletions() + default: + // fallback to lexical completions + return c.lexical(ctx) + } + + return nil +} + +// containingIdent returns the *ast.Ident containing pos, if any. It +// synthesizes an *ast.Ident to allow completion in the face of +// certain syntax errors. +func (c *completer) containingIdent(src []byte) *ast.Ident { + // In the normal case, our leaf AST node is the identifier being completed. + if ident, ok := c.path[0].(*ast.Ident); ok { + return ident + } + + pos, tkn, lit := c.scanToken(src) + if !pos.IsValid() { + return nil + } + + fakeIdent := &ast.Ident{Name: lit, NamePos: pos} + + if _, isBadDecl := c.path[0].(*ast.BadDecl); isBadDecl { + // You don't get *ast.Idents at the file level, so look for bad + // decls and use the manually extracted token. + return fakeIdent + } else if c.emptySwitchStmt() { + // Only keywords are allowed in empty switch statements. + // *ast.Idents are not parsed, so we must use the manually + // extracted token. + return fakeIdent + } else if tkn.IsKeyword() { + // Otherwise, manually extract the prefix if our containing token + // is a keyword. This improves completion after an "accidental + // keyword", e.g. completing to "variance" in "someFunc(var<>)". + return fakeIdent + } + + return nil +} + +// scanToken scans pgh's contents for the token containing pos. +func (c *completer) scanToken(contents []byte) (token.Pos, token.Token, string) { + tok := c.pkg.FileSet().File(c.pos) + + var s scanner.Scanner + s.Init(tok, contents, nil, 0) + for { + tknPos, tkn, lit := s.Scan() + if tkn == token.EOF || tknPos >= c.pos { + return token.NoPos, token.ILLEGAL, "" + } + + if len(lit) > 0 && tknPos <= c.pos && c.pos <= tknPos+token.Pos(len(lit)) { + return tknPos, tkn, lit + } + } +} + +func (c *completer) sortItems() { + sort.SliceStable(c.items, func(i, j int) bool { + // Sort by score first. + if c.items[i].Score != c.items[j].Score { + return c.items[i].Score > c.items[j].Score + } + + // Then sort by label so order stays consistent. This also has the + // effect of preferring shorter candidates. + return c.items[i].Label < c.items[j].Label + }) +} + +// emptySwitchStmt reports whether pos is in an empty switch or select +// statement. +func (c *completer) emptySwitchStmt() bool { + block, ok := c.path[0].(*ast.BlockStmt) + if !ok || len(block.List) > 0 || len(c.path) == 1 { + return false + } + + switch c.path[1].(type) { + case *ast.SwitchStmt, *ast.TypeSwitchStmt, *ast.SelectStmt: + return true + default: + return false + } +} + +// populateImportCompletions yields completions for an import path around the cursor. +// +// Completions are suggested at the directory depth of the given import path so +// that we don't overwhelm the user with a large list of possibilities. As an +// example, a completion for the prefix "golang" results in "golang.org/". +// Completions for "golang.org/" yield its subdirectories +// (i.e. "golang.org/x/"). The user is meant to accept completion suggestions +// until they reach a complete import path. +func (c *completer) populateImportCompletions(ctx context.Context, searchImport *ast.ImportSpec) error { + if !strings.HasPrefix(searchImport.Path.Value, `"`) { + return nil + } + + // deepSearch is not valuable for import completions. + c.deepState.enabled = false + + importPath := searchImport.Path.Value + + // Extract the text between the quotes (if any) in an import spec. + // prefix is the part of import path before the cursor. + prefixEnd := c.pos - searchImport.Path.Pos() + prefix := strings.Trim(importPath[:prefixEnd], `"`) + + // The number of directories in the import path gives us the depth at + // which to search. + depth := len(strings.Split(prefix, "/")) - 1 + + content := importPath + start, end := searchImport.Path.Pos(), searchImport.Path.End() + namePrefix, nameSuffix := `"`, `"` + // If a starting quote is present, adjust surrounding to either after the + // cursor or after the first slash (/), except if cursor is at the starting + // quote. Otherwise we provide a completion including the starting quote. + if strings.HasPrefix(importPath, `"`) && c.pos > searchImport.Path.Pos() { + content = content[1:] + start++ + if depth > 0 { + // Adjust textEdit start to replacement range. For ex: if current + // path was "golang.or/x/to<>ols/internal/", where <> is the cursor + // position, start of the replacement range would be after + // "golang.org/x/". + path := strings.SplitAfter(prefix, "/") + numChars := len(strings.Join(path[:len(path)-1], "")) + content = content[numChars:] + start += token.Pos(numChars) + } + namePrefix = "" + } + + // We won't provide an ending quote if one is already present, except if + // cursor is after the ending quote but still in import spec. This is + // because cursor has to be in our textEdit range. + if strings.HasSuffix(importPath, `"`) && c.pos < searchImport.Path.End() { + end-- + content = content[:len(content)-1] + nameSuffix = "" + } + + c.surrounding = &Selection{ + content: content, + cursor: c.pos, + tokFile: c.tokFile, + start: start, + end: end, + mapper: c.mapper, + } + + seenImports := make(map[string]struct{}) + for _, importSpec := range c.file.Imports { + if importSpec.Path.Value == importPath { + continue + } + seenImportPath, err := strconv.Unquote(importSpec.Path.Value) + if err != nil { + return err + } + seenImports[seenImportPath] = struct{}{} + } + + var mu sync.Mutex // guard c.items locally, since searchImports is called in parallel + seen := make(map[string]struct{}) + searchImports := func(pkg imports.ImportFix) { + path := pkg.StmtInfo.ImportPath + if _, ok := seenImports[path]; ok { + return + } + + // Any package path containing fewer directories than the search + // prefix is not a match. + pkgDirList := strings.Split(path, "/") + if len(pkgDirList) < depth+1 { + return + } + pkgToConsider := strings.Join(pkgDirList[:depth+1], "/") + + name := pkgDirList[depth] + // if we're adding an opening quote to completion too, set name to full + // package path since we'll need to overwrite that range. + if namePrefix == `"` { + name = pkgToConsider + } + + score := pkg.Relevance + if len(pkgDirList)-1 == depth { + score *= highScore + } else { + // For incomplete package paths, add a terminal slash to indicate that the + // user should keep triggering completions. + name += "/" + pkgToConsider += "/" + } + + if _, ok := seen[pkgToConsider]; ok { + return + } + seen[pkgToConsider] = struct{}{} + + mu.Lock() + defer mu.Unlock() + + name = namePrefix + name + nameSuffix + obj := types.NewPkgName(0, nil, name, types.NewPackage(pkgToConsider, name)) + c.deepState.enqueue(candidate{ + obj: obj, + detail: fmt.Sprintf("%q", pkgToConsider), + score: score, + }) + } + + c.completionCallbacks = append(c.completionCallbacks, func(opts *imports.Options) error { + return imports.GetImportPaths(ctx, searchImports, prefix, c.filename, c.pkg.GetTypes().Name(), opts.Env) + }) + return nil +} + +// populateCommentCompletions yields completions for comments preceding or in declarations. +func (c *completer) populateCommentCompletions(ctx context.Context, comment *ast.CommentGroup) { + // If the completion was triggered by a period, ignore it. These types of + // completions will not be useful in comments. + if c.completionContext.triggerCharacter == "." { + return + } + + // Using the comment position find the line after + file := c.pkg.FileSet().File(comment.End()) + if file == nil { + return + } + + // Deep completion doesn't work properly in comments since we don't + // have a type object to complete further. + c.deepState.enabled = false + c.completionContext.commentCompletion = true + + // Documentation isn't useful in comments, since it might end up being the + // comment itself. + c.opts.documentation = false + + commentLine := file.Line(comment.End()) + + // comment is valid, set surrounding as word boundaries around cursor + c.setSurroundingForComment(comment) + + // Using the next line pos, grab and parse the exported symbol on that line + for _, n := range c.file.Decls { + declLine := file.Line(n.Pos()) + // if the comment is not in, directly above or on the same line as a declaration + if declLine != commentLine && declLine != commentLine+1 && + !(n.Pos() <= comment.Pos() && comment.End() <= n.End()) { + continue + } + switch node := n.(type) { + // handle const, vars, and types + case *ast.GenDecl: + for _, spec := range node.Specs { + switch spec := spec.(type) { + case *ast.ValueSpec: + for _, name := range spec.Names { + if name.String() == "_" { + continue + } + obj := c.pkg.GetTypesInfo().ObjectOf(name) + c.deepState.enqueue(candidate{obj: obj, score: stdScore}) + } + case *ast.TypeSpec: + // add TypeSpec fields to completion + switch typeNode := spec.Type.(type) { + case *ast.StructType: + c.addFieldItems(ctx, typeNode.Fields) + case *ast.FuncType: + c.addFieldItems(ctx, typeNode.Params) + c.addFieldItems(ctx, typeNode.Results) + case *ast.InterfaceType: + c.addFieldItems(ctx, typeNode.Methods) + } + + if spec.Name.String() == "_" { + continue + } + + obj := c.pkg.GetTypesInfo().ObjectOf(spec.Name) + // Type name should get a higher score than fields but not highScore by default + // since field near a comment cursor gets a highScore + score := stdScore * 1.1 + // If type declaration is on the line after comment, give it a highScore. + if declLine == commentLine+1 { + score = highScore + } + + c.deepState.enqueue(candidate{obj: obj, score: score}) + } + } + // handle functions + case *ast.FuncDecl: + c.addFieldItems(ctx, node.Recv) + c.addFieldItems(ctx, node.Type.Params) + c.addFieldItems(ctx, node.Type.Results) + + // collect receiver struct fields + if node.Recv != nil { + for _, fields := range node.Recv.List { + for _, name := range fields.Names { + obj := c.pkg.GetTypesInfo().ObjectOf(name) + if obj == nil { + continue + } + + recvType := obj.Type().Underlying() + if ptr, ok := recvType.(*types.Pointer); ok { + recvType = ptr.Elem() + } + recvStruct, ok := recvType.Underlying().(*types.Struct) + if !ok { + continue + } + for i := 0; i < recvStruct.NumFields(); i++ { + field := recvStruct.Field(i) + c.deepState.enqueue(candidate{obj: field, score: lowScore}) + } + } + } + } + + if node.Name.String() == "_" { + continue + } + + obj := c.pkg.GetTypesInfo().ObjectOf(node.Name) + if obj == nil || obj.Pkg() != nil && obj.Pkg() != c.pkg.GetTypes() { + continue + } + + c.deepState.enqueue(candidate{obj: obj, score: highScore}) + } + } +} + +// sets word boundaries surrounding a cursor for a comment +func (c *completer) setSurroundingForComment(comments *ast.CommentGroup) { + var cursorComment *ast.Comment + for _, comment := range comments.List { + if c.pos >= comment.Pos() && c.pos <= comment.End() { + cursorComment = comment + break + } + } + // if cursor isn't in the comment + if cursorComment == nil { + return + } + + // index of cursor in comment text + cursorOffset := int(c.pos - cursorComment.Pos()) + start, end := cursorOffset, cursorOffset + for start > 0 && isValidIdentifierChar(cursorComment.Text[start-1]) { + start-- + } + for end < len(cursorComment.Text) && isValidIdentifierChar(cursorComment.Text[end]) { + end++ + } + + c.surrounding = &Selection{ + content: cursorComment.Text[start:end], + cursor: c.pos, + tokFile: c.tokFile, + start: token.Pos(int(cursorComment.Slash) + start), + end: token.Pos(int(cursorComment.Slash) + end), + mapper: c.mapper, + } + c.setMatcherFromPrefix(c.surrounding.Prefix()) +} + +// isValidIdentifierChar returns true if a byte is a valid go identifier +// character, i.e. unicode letter or digit or underscore. +func isValidIdentifierChar(char byte) bool { + charRune := rune(char) + return unicode.In(charRune, unicode.Letter, unicode.Digit) || char == '_' +} + +// adds struct fields, interface methods, function declaration fields to completion +func (c *completer) addFieldItems(ctx context.Context, fields *ast.FieldList) { + if fields == nil { + return + } + + cursor := c.surrounding.cursor + for _, field := range fields.List { + for _, name := range field.Names { + if name.String() == "_" { + continue + } + obj := c.pkg.GetTypesInfo().ObjectOf(name) + if obj == nil { + continue + } + + // if we're in a field comment/doc, score that field as more relevant + score := stdScore + if field.Comment != nil && field.Comment.Pos() <= cursor && cursor <= field.Comment.End() { + score = highScore + } else if field.Doc != nil && field.Doc.Pos() <= cursor && cursor <= field.Doc.End() { + score = highScore + } + + c.deepState.enqueue(candidate{obj: obj, score: score}) + } + } +} + +func (c *completer) wantStructFieldCompletions() bool { + clInfo := c.enclosingCompositeLiteral + if clInfo == nil { + return false + } + + return clInfo.isStruct() && (clInfo.inKey || clInfo.maybeInFieldName) +} + +func (c *completer) wantTypeName() bool { + return !c.completionContext.commentCompletion && c.inference.typeName.wantTypeName +} + +// See https://golang.org/issue/36001. Unimported completions are expensive. +const ( + maxUnimportedPackageNames = 5 + unimportedMemberTarget = 100 +) + +// selector finds completions for the specified selector expression. +func (c *completer) selector(ctx context.Context, sel *ast.SelectorExpr) error { + c.inference.objChain = objChain(c.pkg.GetTypesInfo(), sel.X) + + // True selector? + if tv, ok := c.pkg.GetTypesInfo().Types[sel.X]; ok { + c.methodsAndFields(tv.Type, tv.Addressable(), nil, c.deepState.enqueue) + c.addPostfixSnippetCandidates(ctx, sel) + return nil + } + + id, ok := sel.X.(*ast.Ident) + if !ok { + return nil + } + + // Treat sel as a qualified identifier. + var filter func(*source.Metadata) bool + needImport := false + if pkgName, ok := c.pkg.GetTypesInfo().Uses[id].(*types.PkgName); ok { + // Qualified identifier with import declaration. + imp := pkgName.Imported() + + // Known direct dependency? Expand using type information. + if _, ok := c.pkg.Metadata().DepsByPkgPath[source.PackagePath(imp.Path())]; ok { + c.packageMembers(imp, stdScore, nil, c.deepState.enqueue) + return nil + } + + // Imported declaration with missing type information. + // Fall through to shallow completion of unimported package members. + // Match candidate packages by path. + // TODO(adonovan): simplify by merging with else case and matching on name only? + filter = func(m *source.Metadata) bool { + return strings.TrimPrefix(string(m.PkgPath), "vendor/") == imp.Path() + } + } else { + // Qualified identifier without import declaration. + // Match candidate packages by name. + filter = func(m *source.Metadata) bool { + return string(m.Name) == id.Name + } + needImport = true + } + + // Search unimported packages. + if !c.opts.unimported { + return nil // feature disabled + } + + // The deep completion algorithm is exceedingly complex and + // deeply coupled to the now obsolete notions that all + // token.Pos values can be interpreted by as a single FileSet + // belonging to the Snapshot and that all types.Object values + // are canonicalized by a single types.Importer mapping. + // These invariants are no longer true now that gopls uses + // an incremental approach, parsing and type-checking each + // package separately. + // + // Consequently, completion of symbols defined in packages that + // are not currently imported by the query file cannot use the + // deep completion machinery which is based on type information. + // Instead it must use only syntax information from a quick + // parse of top-level declarations (but not function bodies). + // + // TODO(adonovan): rewrite the deep completion machinery to + // not assume global Pos/Object realms and then use export + // data instead of the quick parse approach taken here. + + // First, we search among packages in the workspace. + // We'll use a fast parse to extract package members + // from those that match the name/path criterion. + all, err := c.snapshot.AllMetadata(ctx) + if err != nil { + return err + } + var paths []string + known := make(map[source.PackagePath][]*source.Metadata) // may include test variant + for _, m := range all { + if m.IsIntermediateTestVariant() || m.Name == "main" || !filter(m) { + continue + } + known[m.PkgPath] = append(known[m.PkgPath], m) + paths = append(paths, string(m.PkgPath)) + } + + // Rank import paths as goimports would. + var relevances map[string]float64 + if len(paths) > 0 { + if err := c.snapshot.RunProcessEnvFunc(ctx, func(opts *imports.Options) error { + var err error + relevances, err = imports.ScoreImportPaths(ctx, opts.Env, paths) + return err + }); err != nil { + return err + } + sort.Slice(paths, func(i, j int) bool { + return relevances[paths[i]] > relevances[paths[j]] + }) + } + + // quickParse does a quick parse of a single file of package m, + // extracts exported package members and adds candidates to c.items. + var itemsMu sync.Mutex // guards c.items + var enough int32 // atomic bool + quickParse := func(uri span.URI, m *source.Metadata) error { + if atomic.LoadInt32(&enough) != 0 { + return nil + } + + fh, err := c.snapshot.GetFile(ctx, uri) + if err != nil { + return err + } + content, err := fh.Read() + if err != nil { + return err + } + path := string(m.PkgPath) + forEachPackageMember(content, func(tok token.Token, id *ast.Ident, fn *ast.FuncDecl) { + if atomic.LoadInt32(&enough) != 0 { + return + } + + if !id.IsExported() || + sel.Sel.Name != "_" && !strings.HasPrefix(id.Name, sel.Sel.Name) { + return // not a match + } + + // The only detail is the kind and package: `var (from "example.com/foo")` + // TODO(adonovan): pretty-print FuncDecl.FuncType or TypeSpec.Type? + item := CompletionItem{ + Label: id.Name, + Detail: fmt.Sprintf("%s (from %q)", strings.ToLower(tok.String()), m.PkgPath), + InsertText: id.Name, + Score: unimportedScore(relevances[path]), + } + switch tok { + case token.FUNC: + item.Kind = protocol.FunctionCompletion + case token.VAR: + item.Kind = protocol.VariableCompletion + case token.CONST: + item.Kind = protocol.ConstantCompletion + case token.TYPE: + // Without types, we can't distinguish Class from Interface. + item.Kind = protocol.ClassCompletion + } + + if needImport { + imp := &importInfo{importPath: path} + if imports.ImportPathToAssumedName(path) != string(m.Name) { + imp.name = string(m.Name) + } + item.AdditionalTextEdits, _ = c.importEdits(imp) + } + + // For functions, add a parameter snippet. + if fn != nil { + var sn snippet.Builder + sn.WriteText(id.Name) + sn.WriteText("(") + var nparams int + for _, field := range fn.Type.Params.List { + if field.Names != nil { + nparams += len(field.Names) + } else { + nparams++ + } + } + for i := 0; i < nparams; i++ { + if i > 0 { + sn.WriteText(", ") + } + sn.WritePlaceholder(nil) + } + sn.WriteText(")") + item.snippet = &sn + } + + itemsMu.Lock() + c.items = append(c.items, item) + if len(c.items) >= unimportedMemberTarget { + atomic.StoreInt32(&enough, 1) + } + itemsMu.Unlock() + }) + return nil + } + + // Extract the package-level candidates using a quick parse. + var g errgroup.Group + for _, path := range paths { + for _, m := range known[source.PackagePath(path)] { + m := m + for _, uri := range m.CompiledGoFiles { + uri := uri + g.Go(func() error { + return quickParse(uri, m) + }) + } + } + } + if err := g.Wait(); err != nil { + return err + } + + // In addition, we search in the module cache using goimports. + ctx, cancel := context.WithCancel(ctx) + var mu sync.Mutex + add := func(pkgExport imports.PackageExport) { + mu.Lock() + defer mu.Unlock() + // TODO(adonovan): what if the actual package has a vendor/ prefix? + if _, ok := known[source.PackagePath(pkgExport.Fix.StmtInfo.ImportPath)]; ok { + return // We got this one above. + } + + // Continue with untyped proposals. + pkg := types.NewPackage(pkgExport.Fix.StmtInfo.ImportPath, pkgExport.Fix.IdentName) + for _, export := range pkgExport.Exports { + score := unimportedScore(pkgExport.Fix.Relevance) + c.deepState.enqueue(candidate{ + obj: types.NewVar(0, pkg, export, nil), + score: score, + imp: &importInfo{ + importPath: pkgExport.Fix.StmtInfo.ImportPath, + name: pkgExport.Fix.StmtInfo.Name, + }, + }) + } + if len(c.items) >= unimportedMemberTarget { + cancel() + } + } + + c.completionCallbacks = append(c.completionCallbacks, func(opts *imports.Options) error { + defer cancel() + return imports.GetPackageExports(ctx, add, id.Name, c.filename, c.pkg.GetTypes().Name(), opts.Env) + }) + return nil +} + +// unimportedScore returns a score for an unimported package that is generally +// lower than other candidates. +func unimportedScore(relevance float64) float64 { + return (stdScore + .1*relevance) / 2 +} + +func (c *completer) packageMembers(pkg *types.Package, score float64, imp *importInfo, cb func(candidate)) { + scope := pkg.Scope() + for _, name := range scope.Names() { + obj := scope.Lookup(name) + cb(candidate{ + obj: obj, + score: score, + imp: imp, + addressable: isVar(obj), + }) + } +} + +func (c *completer) methodsAndFields(typ types.Type, addressable bool, imp *importInfo, cb func(candidate)) { + mset := c.methodSetCache[methodSetKey{typ, addressable}] + if mset == nil { + if addressable && !types.IsInterface(typ) && !isPointer(typ) { + // Add methods of *T, which includes methods with receiver T. + mset = types.NewMethodSet(types.NewPointer(typ)) + } else { + // Add methods of T. + mset = types.NewMethodSet(typ) + } + c.methodSetCache[methodSetKey{typ, addressable}] = mset + } + + if isStarTestingDotF(typ) && addressable { + // is that a sufficient test? (or is more care needed?) + if c.fuzz(typ, mset, imp, cb, c.pkg.FileSet()) { + return + } + } + + for i := 0; i < mset.Len(); i++ { + cb(candidate{ + obj: mset.At(i).Obj(), + score: stdScore, + imp: imp, + addressable: addressable || isPointer(typ), + }) + } + + // Add fields of T. + eachField(typ, func(v *types.Var) { + cb(candidate{ + obj: v, + score: stdScore - 0.01, + imp: imp, + addressable: addressable || isPointer(typ), + }) + }) +} + +// isStarTestingDotF reports whether typ is *testing.F. +func isStarTestingDotF(typ types.Type) bool { + ptr, _ := typ.(*types.Pointer) + if ptr == nil { + return false + } + named, _ := ptr.Elem().(*types.Named) + if named == nil { + return false + } + obj := named.Obj() + // obj.Pkg is nil for the error type. + return obj != nil && obj.Pkg() != nil && obj.Pkg().Path() == "testing" && obj.Name() == "F" +} + +// lexical finds completions in the lexical environment. +func (c *completer) lexical(ctx context.Context) error { + var ( + builtinIota = types.Universe.Lookup("iota") + builtinNil = types.Universe.Lookup("nil") + + // TODO(rfindley): only allow "comparable" where it is valid (in constraint + // position or embedded in interface declarations). + // builtinComparable = types.Universe.Lookup("comparable") + ) + + // Track seen variables to avoid showing completions for shadowed variables. + // This works since we look at scopes from innermost to outermost. + seen := make(map[string]struct{}) + + // Process scopes innermost first. + for i, scope := range c.scopes { + if scope == nil { + continue + } + + Names: + for _, name := range scope.Names() { + declScope, obj := scope.LookupParent(name, c.pos) + if declScope != scope { + continue // Name was declared in some enclosing scope, or not at all. + } + + // If obj's type is invalid, find the AST node that defines the lexical block + // containing the declaration of obj. Don't resolve types for packages. + if !isPkgName(obj) && !typeIsValid(obj.Type()) { + // Match the scope to its ast.Node. If the scope is the package scope, + // use the *ast.File as the starting node. + var node ast.Node + if i < len(c.path) { + node = c.path[i] + } else if i == len(c.path) { // use the *ast.File for package scope + node = c.path[i-1] + } + if node != nil { + if resolved := resolveInvalid(c.pkg.FileSet(), obj, node, c.pkg.GetTypesInfo()); resolved != nil { + obj = resolved + } + } + } + + // Don't use LHS of decl in RHS. + for _, ident := range enclosingDeclLHS(c.path) { + if obj.Pos() == ident.Pos() { + continue Names + } + } + + // Don't suggest "iota" outside of const decls. + if obj == builtinIota && !c.inConstDecl() { + continue + } + + // Rank outer scopes lower than inner. + score := stdScore * math.Pow(.99, float64(i)) + + // Dowrank "nil" a bit so it is ranked below more interesting candidates. + if obj == builtinNil { + score /= 2 + } + + // If we haven't already added a candidate for an object with this name. + if _, ok := seen[obj.Name()]; !ok { + seen[obj.Name()] = struct{}{} + c.deepState.enqueue(candidate{ + obj: obj, + score: score, + addressable: isVar(obj), + }) + } + } + } + + if c.inference.objType != nil { + if named, _ := source.Deref(c.inference.objType).(*types.Named); named != nil { + // If we expected a named type, check the type's package for + // completion items. This is useful when the current file hasn't + // imported the type's package yet. + + if named.Obj() != nil && named.Obj().Pkg() != nil { + pkg := named.Obj().Pkg() + + // Make sure the package name isn't already in use by another + // object, and that this file doesn't import the package yet. + // TODO(adonovan): what if pkg.Path has vendor/ prefix? + if _, ok := seen[pkg.Name()]; !ok && pkg != c.pkg.GetTypes() && !alreadyImports(c.file, source.ImportPath(pkg.Path())) { + seen[pkg.Name()] = struct{}{} + obj := types.NewPkgName(0, nil, pkg.Name(), pkg) + imp := &importInfo{ + importPath: pkg.Path(), + } + if imports.ImportPathToAssumedName(pkg.Path()) != pkg.Name() { + imp.name = pkg.Name() + } + c.deepState.enqueue(candidate{ + obj: obj, + score: stdScore, + imp: imp, + }) + } + } + } + } + + if c.opts.unimported { + if err := c.unimportedPackages(ctx, seen); err != nil { + return err + } + } + + if c.inference.typeName.isTypeParam { + // If we are completing a type param, offer each structural type. + // This ensures we suggest "[]int" and "[]float64" for a constraint + // with type union "[]int | []float64". + if t, _ := c.inference.objType.(*types.Interface); t != nil { + terms, _ := typeparams.InterfaceTermSet(t) + for _, term := range terms { + c.injectType(ctx, term.Type()) + } + } + } else { + c.injectType(ctx, c.inference.objType) + } + + // Add keyword completion items appropriate in the current context. + c.addKeywordCompletions() + + return nil +} + +// injectType manufactures candidates based on the given type. This is +// intended for types not discoverable via lexical search, such as +// composite and/or generic types. For example, if the type is "[]int", +// this method makes sure you get candidates "[]int{}" and "[]int" +// (the latter applies when completing a type name). +func (c *completer) injectType(ctx context.Context, t types.Type) { + if t == nil { + return + } + + t = source.Deref(t) + + // If we have an expected type and it is _not_ a named type, handle + // it specially. Non-named types like "[]int" will never be + // considered via a lexical search, so we need to directly inject + // them. Also allow generic types since lexical search does not + // infer instantiated versions of them. + if named, _ := t.(*types.Named); named == nil || typeparams.ForNamed(named).Len() > 0 { + // If our expected type is "[]int", this will add a literal + // candidate of "[]int{}". + c.literal(ctx, t, nil) + + if _, isBasic := t.(*types.Basic); !isBasic { + // If we expect a non-basic type name (e.g. "[]int"), hack up + // a named type whose name is literally "[]int". This allows + // us to reuse our object based completion machinery. + fakeNamedType := candidate{ + obj: types.NewTypeName(token.NoPos, nil, types.TypeString(t, c.qf), t), + score: stdScore, + } + // Make sure the type name matches before considering + // candidate. This cuts down on useless candidates. + if c.matchingTypeName(&fakeNamedType) { + c.deepState.enqueue(fakeNamedType) + } + } + } +} + +func (c *completer) unimportedPackages(ctx context.Context, seen map[string]struct{}) error { + var prefix string + if c.surrounding != nil { + prefix = c.surrounding.Prefix() + } + + // Don't suggest unimported packages if we have absolutely nothing + // to go on. + if prefix == "" { + return nil + } + + count := 0 + + // Search packages across the entire workspace. + all, err := c.snapshot.AllMetadata(ctx) + if err != nil { + return err + } + pkgNameByPath := make(map[source.PackagePath]string) + var paths []string // actually PackagePaths + for _, m := range all { + if m.ForTest != "" { + continue // skip all test variants + } + if m.Name == "main" { + continue // main is non-importable + } + if !strings.HasPrefix(string(m.Name), prefix) { + continue // not a match + } + paths = append(paths, string(m.PkgPath)) + pkgNameByPath[m.PkgPath] = string(m.Name) + } + + // Rank candidates using goimports' algorithm. + var relevances map[string]float64 + if len(paths) != 0 { + if err := c.snapshot.RunProcessEnvFunc(ctx, func(opts *imports.Options) error { + var err error + relevances, err = imports.ScoreImportPaths(ctx, opts.Env, paths) + return err + }); err != nil { + return err + } + } + sort.Slice(paths, func(i, j int) bool { + if relevances[paths[i]] != relevances[paths[j]] { + return relevances[paths[i]] > relevances[paths[j]] + } + + // Fall back to lexical sort to keep truncated set of candidates + // in a consistent order. + return paths[i] < paths[j] + }) + + for _, path := range paths { + name := pkgNameByPath[source.PackagePath(path)] + if _, ok := seen[name]; ok { + continue + } + imp := &importInfo{ + importPath: path, + } + if imports.ImportPathToAssumedName(path) != name { + imp.name = name + } + if count >= maxUnimportedPackageNames { + return nil + } + c.deepState.enqueue(candidate{ + // Pass an empty *types.Package to disable deep completions. + obj: types.NewPkgName(0, nil, name, types.NewPackage(path, name)), + score: unimportedScore(relevances[path]), + imp: imp, + }) + count++ + } + + ctx, cancel := context.WithCancel(ctx) + + var mu sync.Mutex + add := func(pkg imports.ImportFix) { + mu.Lock() + defer mu.Unlock() + if _, ok := seen[pkg.IdentName]; ok { + return + } + if _, ok := relevances[pkg.StmtInfo.ImportPath]; ok { + return + } + + if count >= maxUnimportedPackageNames { + cancel() + return + } + + // Do not add the unimported packages to seen, since we can have + // multiple packages of the same name as completion suggestions, since + // only one will be chosen. + obj := types.NewPkgName(0, nil, pkg.IdentName, types.NewPackage(pkg.StmtInfo.ImportPath, pkg.IdentName)) + c.deepState.enqueue(candidate{ + obj: obj, + score: unimportedScore(pkg.Relevance), + imp: &importInfo{ + importPath: pkg.StmtInfo.ImportPath, + name: pkg.StmtInfo.Name, + }, + }) + count++ + } + c.completionCallbacks = append(c.completionCallbacks, func(opts *imports.Options) error { + defer cancel() + return imports.GetAllCandidates(ctx, add, prefix, c.filename, c.pkg.GetTypes().Name(), opts.Env) + }) + return nil +} + +// alreadyImports reports whether f has an import with the specified path. +func alreadyImports(f *ast.File, path source.ImportPath) bool { + for _, s := range f.Imports { + if source.UnquoteImportPath(s) == path { + return true + } + } + return false +} + +func (c *completer) inConstDecl() bool { + for _, n := range c.path { + if decl, ok := n.(*ast.GenDecl); ok && decl.Tok == token.CONST { + return true + } + } + return false +} + +// structLiteralFieldName finds completions for struct field names inside a struct literal. +func (c *completer) structLiteralFieldName(ctx context.Context) error { + clInfo := c.enclosingCompositeLiteral + + // Mark fields of the composite literal that have already been set, + // except for the current field. + addedFields := make(map[*types.Var]bool) + for _, el := range clInfo.cl.Elts { + if kvExpr, ok := el.(*ast.KeyValueExpr); ok { + if clInfo.kv == kvExpr { + continue + } + + if key, ok := kvExpr.Key.(*ast.Ident); ok { + if used, ok := c.pkg.GetTypesInfo().Uses[key]; ok { + if usedVar, ok := used.(*types.Var); ok { + addedFields[usedVar] = true + } + } + } + } + } + + deltaScore := 0.0001 + switch t := clInfo.clType.(type) { + case *types.Struct: + for i := 0; i < t.NumFields(); i++ { + field := t.Field(i) + if !addedFields[field] { + c.deepState.enqueue(candidate{ + obj: field, + score: highScore - float64(i)*deltaScore, + }) + } + } + + // Add lexical completions if we aren't certain we are in the key part of a + // key-value pair. + if clInfo.maybeInFieldName { + return c.lexical(ctx) + } + default: + return c.lexical(ctx) + } + + return nil +} + +func (cl *compLitInfo) isStruct() bool { + _, ok := cl.clType.(*types.Struct) + return ok +} + +// enclosingCompositeLiteral returns information about the composite literal enclosing the +// position. +func enclosingCompositeLiteral(path []ast.Node, pos token.Pos, info *types.Info) *compLitInfo { + for _, n := range path { + switch n := n.(type) { + case *ast.CompositeLit: + // The enclosing node will be a composite literal if the user has just + // opened the curly brace (e.g. &x{<>) or the completion request is triggered + // from an already completed composite literal expression (e.g. &x{foo: 1, <>}) + // + // The position is not part of the composite literal unless it falls within the + // curly braces (e.g. "foo.Foo<>Struct{}"). + if !(n.Lbrace < pos && pos <= n.Rbrace) { + // Keep searching since we may yet be inside a composite literal. + // For example "Foo{B: Ba<>{}}". + break + } + + tv, ok := info.Types[n] + if !ok { + return nil + } + + clInfo := compLitInfo{ + cl: n, + clType: source.Deref(tv.Type).Underlying(), + } + + var ( + expr ast.Expr + hasKeys bool + ) + for _, el := range n.Elts { + // Remember the expression that the position falls in, if any. + if el.Pos() <= pos && pos <= el.End() { + expr = el + } + + if kv, ok := el.(*ast.KeyValueExpr); ok { + hasKeys = true + // If expr == el then we know the position falls in this expression, + // so also record kv as the enclosing *ast.KeyValueExpr. + if expr == el { + clInfo.kv = kv + break + } + } + } + + if clInfo.kv != nil { + // If in a *ast.KeyValueExpr, we know we are in the key if the position + // is to the left of the colon (e.g. "Foo{F<>: V}". + clInfo.inKey = pos <= clInfo.kv.Colon + } else if hasKeys { + // If we aren't in a *ast.KeyValueExpr but the composite literal has + // other *ast.KeyValueExprs, we must be on the key side of a new + // *ast.KeyValueExpr (e.g. "Foo{F: V, <>}"). + clInfo.inKey = true + } else { + switch clInfo.clType.(type) { + case *types.Struct: + if len(n.Elts) == 0 { + // If the struct literal is empty, next could be a struct field + // name or an expression (e.g. "Foo{<>}" could become "Foo{F:}" + // or "Foo{someVar}"). + clInfo.maybeInFieldName = true + } else if len(n.Elts) == 1 { + // If there is one expression and the position is in that expression + // and the expression is an identifier, we may be writing a field + // name or an expression (e.g. "Foo{F<>}"). + _, clInfo.maybeInFieldName = expr.(*ast.Ident) + } + case *types.Map: + // If we aren't in a *ast.KeyValueExpr we must be adding a new key + // to the map. + clInfo.inKey = true + } + } + + return &clInfo + default: + if breaksExpectedTypeInference(n, pos) { + return nil + } + } + } + + return nil +} + +// enclosingFunction returns the signature and body of the function +// enclosing the given position. +func enclosingFunction(path []ast.Node, info *types.Info) *funcInfo { + for _, node := range path { + switch t := node.(type) { + case *ast.FuncDecl: + if obj, ok := info.Defs[t.Name]; ok { + return &funcInfo{ + sig: obj.Type().(*types.Signature), + body: t.Body, + } + } + case *ast.FuncLit: + if typ, ok := info.Types[t]; ok { + if sig, _ := typ.Type.(*types.Signature); sig == nil { + // golang/go#49397: it should not be possible, but we somehow arrived + // here with a non-signature type, most likely due to AST mangling + // such that node.Type is not a FuncType. + return nil + } + return &funcInfo{ + sig: typ.Type.(*types.Signature), + body: t.Body, + } + } + } + } + return nil +} + +func (c *completer) expectedCompositeLiteralType() types.Type { + clInfo := c.enclosingCompositeLiteral + switch t := clInfo.clType.(type) { + case *types.Slice: + if clInfo.inKey { + return types.Typ[types.UntypedInt] + } + return t.Elem() + case *types.Array: + if clInfo.inKey { + return types.Typ[types.UntypedInt] + } + return t.Elem() + case *types.Map: + if clInfo.inKey { + return t.Key() + } + return t.Elem() + case *types.Struct: + // If we are completing a key (i.e. field name), there is no expected type. + if clInfo.inKey { + return nil + } + + // If we are in a key-value pair, but not in the key, then we must be on the + // value side. The expected type of the value will be determined from the key. + if clInfo.kv != nil { + if key, ok := clInfo.kv.Key.(*ast.Ident); ok { + for i := 0; i < t.NumFields(); i++ { + if field := t.Field(i); field.Name() == key.Name { + return field.Type() + } + } + } + } else { + // If we aren't in a key-value pair and aren't in the key, we must be using + // implicit field names. + + // The order of the literal fields must match the order in the struct definition. + // Find the element that the position belongs to and suggest that field's type. + if i := exprAtPos(c.pos, clInfo.cl.Elts); i < t.NumFields() { + return t.Field(i).Type() + } + } + } + return nil +} + +// typeMod represents an operator that changes the expected type. +type typeMod struct { + mod typeModKind + arrayLen int64 +} + +type typeModKind int + +const ( + dereference typeModKind = iota // pointer indirection: "*" + reference // adds level of pointer: "&" for values, "*" for type names + chanRead // channel read operator: "<-" + sliceType // make a slice type: "[]" in "[]int" + arrayType // make an array type: "[2]" in "[2]int" + invoke // make a function call: "()" in "foo()" + takeSlice // take slice of array: "[:]" in "foo[:]" + takeDotDotDot // turn slice into variadic args: "..." in "foo..." + index // index into slice/array: "[0]" in "foo[0]" +) + +type objKind int + +const ( + kindAny objKind = 0 + kindArray objKind = 1 << iota + kindSlice + kindChan + kindMap + kindStruct + kindString + kindInt + kindBool + kindBytes + kindPtr + kindFloat + kindComplex + kindError + kindStringer + kindFunc +) + +// penalizedObj represents an object that should be disfavored as a +// completion candidate. +type penalizedObj struct { + // objChain is the full "chain", e.g. "foo.bar().baz" becomes + // []types.Object{foo, bar, baz}. + objChain []types.Object + // penalty is score penalty in the range (0, 1). + penalty float64 +} + +// candidateInference holds information we have inferred about a type that can be +// used at the current position. +type candidateInference struct { + // objType is the desired type of an object used at the query position. + objType types.Type + + // objKind is a mask of expected kinds of types such as "map", "slice", etc. + objKind objKind + + // variadic is true if we are completing the initial variadic + // parameter. For example: + // append([]T{}, <>) // objType=T variadic=true + // append([]T{}, T{}, <>) // objType=T variadic=false + variadic bool + + // modifiers are prefixes such as "*", "&" or "<-" that influence how + // a candidate type relates to the expected type. + modifiers []typeMod + + // convertibleTo is a type our candidate type must be convertible to. + convertibleTo types.Type + + // typeName holds information about the expected type name at + // position, if any. + typeName typeNameInference + + // assignees are the types that would receive a function call's + // results at the position. For example: + // + // foo := 123 + // foo, bar := <> + // + // at "<>", the assignees are [int, <invalid>]. + assignees []types.Type + + // variadicAssignees is true if we could be completing an inner + // function call that fills out an outer function call's variadic + // params. For example: + // + // func foo(int, ...string) {} + // + // foo(<>) // variadicAssignees=true + // foo(bar<>) // variadicAssignees=true + // foo(bar, baz<>) // variadicAssignees=false + variadicAssignees bool + + // penalized holds expressions that should be disfavored as + // candidates. For example, it tracks expressions already used in a + // switch statement's other cases. Each expression is tracked using + // its entire object "chain" allowing differentiation between + // "a.foo" and "b.foo" when "a" and "b" are the same type. + penalized []penalizedObj + + // objChain contains the chain of objects representing the + // surrounding *ast.SelectorExpr. For example, if we are completing + // "foo.bar.ba<>", objChain will contain []types.Object{foo, bar}. + objChain []types.Object +} + +// typeNameInference holds information about the expected type name at +// position. +type typeNameInference struct { + // wantTypeName is true if we expect the name of a type. + wantTypeName bool + + // modifiers are prefixes such as "*", "&" or "<-" that influence how + // a candidate type relates to the expected type. + modifiers []typeMod + + // assertableFrom is a type that must be assertable to our candidate type. + assertableFrom types.Type + + // wantComparable is true if we want a comparable type. + wantComparable bool + + // seenTypeSwitchCases tracks types that have already been used by + // the containing type switch. + seenTypeSwitchCases []types.Type + + // compLitType is true if we are completing a composite literal type + // name, e.g "foo<>{}". + compLitType bool + + // isTypeParam is true if we are completing a type instantiation parameter + isTypeParam bool +} + +// expectedCandidate returns information about the expected candidate +// for an expression at the query position. +func expectedCandidate(ctx context.Context, c *completer) (inf candidateInference) { + inf.typeName = expectTypeName(c) + + if c.enclosingCompositeLiteral != nil { + inf.objType = c.expectedCompositeLiteralType() + } + +Nodes: + for i, node := range c.path { + switch node := node.(type) { + case *ast.BinaryExpr: + // Determine if query position comes from left or right of op. + e := node.X + if c.pos < node.OpPos { + e = node.Y + } + if tv, ok := c.pkg.GetTypesInfo().Types[e]; ok { + switch node.Op { + case token.LAND, token.LOR: + // Don't infer "bool" type for "&&" or "||". Often you want + // to compose a boolean expression from non-boolean + // candidates. + default: + inf.objType = tv.Type + } + break Nodes + } + case *ast.AssignStmt: + // Only rank completions if you are on the right side of the token. + if c.pos > node.TokPos { + i := exprAtPos(c.pos, node.Rhs) + if i >= len(node.Lhs) { + i = len(node.Lhs) - 1 + } + if tv, ok := c.pkg.GetTypesInfo().Types[node.Lhs[i]]; ok { + inf.objType = tv.Type + } + + // If we have a single expression on the RHS, record the LHS + // assignees so we can favor multi-return function calls with + // matching result values. + if len(node.Rhs) <= 1 { + for _, lhs := range node.Lhs { + inf.assignees = append(inf.assignees, c.pkg.GetTypesInfo().TypeOf(lhs)) + } + } else { + // Otherwise, record our single assignee, even if its type is + // not available. We use this info to downrank functions + // with the wrong number of result values. + inf.assignees = append(inf.assignees, c.pkg.GetTypesInfo().TypeOf(node.Lhs[i])) + } + } + return inf + case *ast.ValueSpec: + if node.Type != nil && c.pos > node.Type.End() { + inf.objType = c.pkg.GetTypesInfo().TypeOf(node.Type) + } + return inf + case *ast.CallExpr: + // Only consider CallExpr args if position falls between parens. + if node.Lparen < c.pos && c.pos <= node.Rparen { + // For type conversions like "int64(foo)" we can only infer our + // desired type is convertible to int64. + if typ := typeConversion(node, c.pkg.GetTypesInfo()); typ != nil { + inf.convertibleTo = typ + break Nodes + } + + sig, _ := c.pkg.GetTypesInfo().Types[node.Fun].Type.(*types.Signature) + + if sig != nil && typeparams.ForSignature(sig).Len() > 0 { + // If we are completing a generic func call, re-check the call expression. + // This allows type param inference to work in cases like: + // + // func foo[T any](T) {} + // foo[int](<>) // <- get "int" completions instead of "T" + // + // TODO: remove this after https://go.dev/issue/52503 + info := &types.Info{Types: make(map[ast.Expr]types.TypeAndValue)} + types.CheckExpr(c.pkg.FileSet(), c.pkg.GetTypes(), node.Fun.Pos(), node.Fun, info) + sig, _ = info.Types[node.Fun].Type.(*types.Signature) + } + + if sig != nil { + inf = c.expectedCallParamType(inf, node, sig) + } + + if funIdent, ok := node.Fun.(*ast.Ident); ok { + obj := c.pkg.GetTypesInfo().ObjectOf(funIdent) + + if obj != nil && obj.Parent() == types.Universe { + // Defer call to builtinArgType so we can provide it the + // inferred type from its parent node. + defer func() { + inf = c.builtinArgType(obj, node, inf) + inf.objKind = c.builtinArgKind(ctx, obj, node) + }() + + // The expected type of builtin arguments like append() is + // the expected type of the builtin call itself. For + // example: + // + // var foo []int = append(<>) + // + // To find the expected type at <> we "skip" the append() + // node and get the expected type one level up, which is + // []int. + continue Nodes + } + } + + return inf + } + case *ast.ReturnStmt: + if c.enclosingFunc != nil { + sig := c.enclosingFunc.sig + // Find signature result that corresponds to our return statement. + if resultIdx := exprAtPos(c.pos, node.Results); resultIdx < len(node.Results) { + if resultIdx < sig.Results().Len() { + inf.objType = sig.Results().At(resultIdx).Type() + } + } + } + return inf + case *ast.CaseClause: + if swtch, ok := findSwitchStmt(c.path[i+1:], c.pos, node).(*ast.SwitchStmt); ok { + if tv, ok := c.pkg.GetTypesInfo().Types[swtch.Tag]; ok { + inf.objType = tv.Type + + // Record which objects have already been used in the case + // statements so we don't suggest them again. + for _, cc := range swtch.Body.List { + for _, caseExpr := range cc.(*ast.CaseClause).List { + // Don't record the expression we are currently completing. + if caseExpr.Pos() < c.pos && c.pos <= caseExpr.End() { + continue + } + + if objs := objChain(c.pkg.GetTypesInfo(), caseExpr); len(objs) > 0 { + inf.penalized = append(inf.penalized, penalizedObj{objChain: objs, penalty: 0.1}) + } + } + } + } + } + return inf + case *ast.SliceExpr: + // Make sure position falls within the brackets (e.g. "foo[a:<>]"). + if node.Lbrack < c.pos && c.pos <= node.Rbrack { + inf.objType = types.Typ[types.UntypedInt] + } + return inf + case *ast.IndexExpr: + // Make sure position falls within the brackets (e.g. "foo[<>]"). + if node.Lbrack < c.pos && c.pos <= node.Rbrack { + if tv, ok := c.pkg.GetTypesInfo().Types[node.X]; ok { + switch t := tv.Type.Underlying().(type) { + case *types.Map: + inf.objType = t.Key() + case *types.Slice, *types.Array: + inf.objType = types.Typ[types.UntypedInt] + } + + if ct := expectedConstraint(tv.Type, 0); ct != nil { + inf.objType = ct + inf.typeName.wantTypeName = true + inf.typeName.isTypeParam = true + } + } + } + return inf + case *typeparams.IndexListExpr: + if node.Lbrack < c.pos && c.pos <= node.Rbrack { + if tv, ok := c.pkg.GetTypesInfo().Types[node.X]; ok { + if ct := expectedConstraint(tv.Type, exprAtPos(c.pos, node.Indices)); ct != nil { + inf.objType = ct + inf.typeName.wantTypeName = true + inf.typeName.isTypeParam = true + } + } + } + return inf + case *ast.SendStmt: + // Make sure we are on right side of arrow (e.g. "foo <- <>"). + if c.pos > node.Arrow+1 { + if tv, ok := c.pkg.GetTypesInfo().Types[node.Chan]; ok { + if ch, ok := tv.Type.Underlying().(*types.Chan); ok { + inf.objType = ch.Elem() + } + } + } + return inf + case *ast.RangeStmt: + if source.NodeContains(node.X, c.pos) { + inf.objKind |= kindSlice | kindArray | kindMap | kindString + if node.Value == nil { + inf.objKind |= kindChan + } + } + return inf + case *ast.StarExpr: + inf.modifiers = append(inf.modifiers, typeMod{mod: dereference}) + case *ast.UnaryExpr: + switch node.Op { + case token.AND: + inf.modifiers = append(inf.modifiers, typeMod{mod: reference}) + case token.ARROW: + inf.modifiers = append(inf.modifiers, typeMod{mod: chanRead}) + } + case *ast.DeferStmt, *ast.GoStmt: + inf.objKind |= kindFunc + return inf + default: + if breaksExpectedTypeInference(node, c.pos) { + return inf + } + } + } + + return inf +} + +func (c *completer) expectedCallParamType(inf candidateInference, node *ast.CallExpr, sig *types.Signature) candidateInference { + numParams := sig.Params().Len() + if numParams == 0 { + return inf + } + + exprIdx := exprAtPos(c.pos, node.Args) + + // If we have one or zero arg expressions, we may be + // completing to a function call that returns multiple + // values, in turn getting passed in to the surrounding + // call. Record the assignees so we can favor function + // calls that return matching values. + if len(node.Args) <= 1 && exprIdx == 0 { + for i := 0; i < sig.Params().Len(); i++ { + inf.assignees = append(inf.assignees, sig.Params().At(i).Type()) + } + + // Record that we may be completing into variadic parameters. + inf.variadicAssignees = sig.Variadic() + } + + // Make sure not to run past the end of expected parameters. + if exprIdx >= numParams { + inf.objType = sig.Params().At(numParams - 1).Type() + } else { + inf.objType = sig.Params().At(exprIdx).Type() + } + + if sig.Variadic() && exprIdx >= (numParams-1) { + // If we are completing a variadic param, deslice the variadic type. + inf.objType = deslice(inf.objType) + // Record whether we are completing the initial variadic param. + inf.variadic = exprIdx == numParams-1 && len(node.Args) <= numParams + + // Check if we can infer object kind from printf verb. + inf.objKind |= printfArgKind(c.pkg.GetTypesInfo(), node, exprIdx) + } + + // If our expected type is an uninstantiated generic type param, + // swap to the constraint which will do a decent job filtering + // candidates. + if tp, _ := inf.objType.(*typeparams.TypeParam); tp != nil { + inf.objType = tp.Constraint() + } + + return inf +} + +func expectedConstraint(t types.Type, idx int) types.Type { + var tp *typeparams.TypeParamList + if named, _ := t.(*types.Named); named != nil { + tp = typeparams.ForNamed(named) + } else if sig, _ := t.Underlying().(*types.Signature); sig != nil { + tp = typeparams.ForSignature(sig) + } + if tp == nil || idx >= tp.Len() { + return nil + } + return tp.At(idx).Constraint() +} + +// objChain decomposes e into a chain of objects if possible. For +// example, "foo.bar().baz" will yield []types.Object{foo, bar, baz}. +// If any part can't be turned into an object, return nil. +func objChain(info *types.Info, e ast.Expr) []types.Object { + var objs []types.Object + + for e != nil { + switch n := e.(type) { + case *ast.Ident: + obj := info.ObjectOf(n) + if obj == nil { + return nil + } + objs = append(objs, obj) + e = nil + case *ast.SelectorExpr: + obj := info.ObjectOf(n.Sel) + if obj == nil { + return nil + } + objs = append(objs, obj) + e = n.X + case *ast.CallExpr: + if len(n.Args) > 0 { + return nil + } + e = n.Fun + default: + return nil + } + } + + // Reverse order so the layout matches the syntactic order. + for i := 0; i < len(objs)/2; i++ { + objs[i], objs[len(objs)-1-i] = objs[len(objs)-1-i], objs[i] + } + + return objs +} + +// applyTypeModifiers applies the list of type modifiers to a type. +// It returns nil if the modifiers could not be applied. +func (ci candidateInference) applyTypeModifiers(typ types.Type, addressable bool) types.Type { + for _, mod := range ci.modifiers { + switch mod.mod { + case dereference: + // For every "*" indirection operator, remove a pointer layer + // from candidate type. + if ptr, ok := typ.Underlying().(*types.Pointer); ok { + typ = ptr.Elem() + } else { + return nil + } + case reference: + // For every "&" address operator, add another pointer layer to + // candidate type, if the candidate is addressable. + if addressable { + typ = types.NewPointer(typ) + } else { + return nil + } + case chanRead: + // For every "<-" operator, remove a layer of channelness. + if ch, ok := typ.(*types.Chan); ok { + typ = ch.Elem() + } else { + return nil + } + } + } + + return typ +} + +// applyTypeNameModifiers applies the list of type modifiers to a type name. +func (ci candidateInference) applyTypeNameModifiers(typ types.Type) types.Type { + for _, mod := range ci.typeName.modifiers { + switch mod.mod { + case reference: + typ = types.NewPointer(typ) + case arrayType: + typ = types.NewArray(typ, mod.arrayLen) + case sliceType: + typ = types.NewSlice(typ) + } + } + return typ +} + +// matchesVariadic returns true if we are completing a variadic +// parameter and candType is a compatible slice type. +func (ci candidateInference) matchesVariadic(candType types.Type) bool { + return ci.variadic && ci.objType != nil && assignableTo(candType, types.NewSlice(ci.objType)) +} + +// findSwitchStmt returns an *ast.CaseClause's corresponding *ast.SwitchStmt or +// *ast.TypeSwitchStmt. path should start from the case clause's first ancestor. +func findSwitchStmt(path []ast.Node, pos token.Pos, c *ast.CaseClause) ast.Stmt { + // Make sure position falls within a "case <>:" clause. + if exprAtPos(pos, c.List) >= len(c.List) { + return nil + } + // A case clause is always nested within a block statement in a switch statement. + if len(path) < 2 { + return nil + } + if _, ok := path[0].(*ast.BlockStmt); !ok { + return nil + } + switch s := path[1].(type) { + case *ast.SwitchStmt: + return s + case *ast.TypeSwitchStmt: + return s + default: + return nil + } +} + +// breaksExpectedTypeInference reports if an expression node's type is unrelated +// to its child expression node types. For example, "Foo{Bar: x.Baz(<>)}" should +// expect a function argument, not a composite literal value. +func breaksExpectedTypeInference(n ast.Node, pos token.Pos) bool { + switch n := n.(type) { + case *ast.CompositeLit: + // Doesn't break inference if pos is in type name. + // For example: "Foo<>{Bar: 123}" + return !source.NodeContains(n.Type, pos) + case *ast.CallExpr: + // Doesn't break inference if pos is in func name. + // For example: "Foo<>(123)" + return !source.NodeContains(n.Fun, pos) + case *ast.FuncLit, *ast.IndexExpr, *ast.SliceExpr: + return true + default: + return false + } +} + +// expectTypeName returns information about the expected type name at position. +func expectTypeName(c *completer) typeNameInference { + var inf typeNameInference + +Nodes: + for i, p := range c.path { + switch n := p.(type) { + case *ast.FieldList: + // Expect a type name if pos is in a FieldList. This applies to + // FuncType params/results, FuncDecl receiver, StructType, and + // InterfaceType. We don't need to worry about the field name + // because completion bails out early if pos is in an *ast.Ident + // that defines an object. + inf.wantTypeName = true + break Nodes + case *ast.CaseClause: + // Expect type names in type switch case clauses. + if swtch, ok := findSwitchStmt(c.path[i+1:], c.pos, n).(*ast.TypeSwitchStmt); ok { + // The case clause types must be assertable from the type switch parameter. + ast.Inspect(swtch.Assign, func(n ast.Node) bool { + if ta, ok := n.(*ast.TypeAssertExpr); ok { + inf.assertableFrom = c.pkg.GetTypesInfo().TypeOf(ta.X) + return false + } + return true + }) + inf.wantTypeName = true + + // Track the types that have already been used in this + // switch's case statements so we don't recommend them. + for _, e := range swtch.Body.List { + for _, typeExpr := range e.(*ast.CaseClause).List { + // Skip if type expression contains pos. We don't want to + // count it as already used if the user is completing it. + if typeExpr.Pos() < c.pos && c.pos <= typeExpr.End() { + continue + } + + if t := c.pkg.GetTypesInfo().TypeOf(typeExpr); t != nil { + inf.seenTypeSwitchCases = append(inf.seenTypeSwitchCases, t) + } + } + } + + break Nodes + } + return typeNameInference{} + case *ast.TypeAssertExpr: + // Expect type names in type assert expressions. + if n.Lparen < c.pos && c.pos <= n.Rparen { + // The type in parens must be assertable from the expression type. + inf.assertableFrom = c.pkg.GetTypesInfo().TypeOf(n.X) + inf.wantTypeName = true + break Nodes + } + return typeNameInference{} + case *ast.StarExpr: + inf.modifiers = append(inf.modifiers, typeMod{mod: reference}) + case *ast.CompositeLit: + // We want a type name if position is in the "Type" part of a + // composite literal (e.g. "Foo<>{}"). + if n.Type != nil && n.Type.Pos() <= c.pos && c.pos <= n.Type.End() { + inf.wantTypeName = true + inf.compLitType = true + + if i < len(c.path)-1 { + // Track preceding "&" operator. Technically it applies to + // the composite literal and not the type name, but if + // affects our type completion nonetheless. + if u, ok := c.path[i+1].(*ast.UnaryExpr); ok && u.Op == token.AND { + inf.modifiers = append(inf.modifiers, typeMod{mod: reference}) + } + } + } + break Nodes + case *ast.ArrayType: + // If we are inside the "Elt" part of an array type, we want a type name. + if n.Elt.Pos() <= c.pos && c.pos <= n.Elt.End() { + inf.wantTypeName = true + if n.Len == nil { + // No "Len" expression means a slice type. + inf.modifiers = append(inf.modifiers, typeMod{mod: sliceType}) + } else { + // Try to get the array type using the constant value of "Len". + tv, ok := c.pkg.GetTypesInfo().Types[n.Len] + if ok && tv.Value != nil && tv.Value.Kind() == constant.Int { + if arrayLen, ok := constant.Int64Val(tv.Value); ok { + inf.modifiers = append(inf.modifiers, typeMod{mod: arrayType, arrayLen: arrayLen}) + } + } + } + + // ArrayTypes can be nested, so keep going if our parent is an + // ArrayType. + if i < len(c.path)-1 { + if _, ok := c.path[i+1].(*ast.ArrayType); ok { + continue Nodes + } + } + + break Nodes + } + case *ast.MapType: + inf.wantTypeName = true + if n.Key != nil { + inf.wantComparable = source.NodeContains(n.Key, c.pos) + } else { + // If the key is empty, assume we are completing the key if + // pos is directly after the "map[". + inf.wantComparable = c.pos == n.Pos()+token.Pos(len("map[")) + } + break Nodes + case *ast.ValueSpec: + inf.wantTypeName = source.NodeContains(n.Type, c.pos) + break Nodes + case *ast.TypeSpec: + inf.wantTypeName = source.NodeContains(n.Type, c.pos) + default: + if breaksExpectedTypeInference(p, c.pos) { + return typeNameInference{} + } + } + } + + return inf +} + +func (c *completer) fakeObj(T types.Type) *types.Var { + return types.NewVar(token.NoPos, c.pkg.GetTypes(), "", T) +} + +// derivableTypes iterates types you can derive from t. For example, +// from "foo" we might derive "&foo", and "foo()". +func derivableTypes(t types.Type, addressable bool, f func(t types.Type, addressable bool, mod typeModKind) bool) bool { + switch t := t.Underlying().(type) { + case *types.Signature: + // If t is a func type with a single result, offer the result type. + if t.Results().Len() == 1 && f(t.Results().At(0).Type(), false, invoke) { + return true + } + case *types.Array: + if f(t.Elem(), true, index) { + return true + } + // Try converting array to slice. + if f(types.NewSlice(t.Elem()), false, takeSlice) { + return true + } + case *types.Pointer: + if f(t.Elem(), false, dereference) { + return true + } + case *types.Slice: + if f(t.Elem(), true, index) { + return true + } + case *types.Map: + if f(t.Elem(), false, index) { + return true + } + case *types.Chan: + if f(t.Elem(), false, chanRead) { + return true + } + } + + // Check if c is addressable and a pointer to c matches our type inference. + if addressable && f(types.NewPointer(t), false, reference) { + return true + } + + return false +} + +// anyCandType reports whether f returns true for any candidate type +// derivable from c. It searches up to three levels of type +// modification. For example, given "foo" we could discover "***foo" +// or "*foo()". +func (c *candidate) anyCandType(f func(t types.Type, addressable bool) bool) bool { + if c.obj == nil || c.obj.Type() == nil { + return false + } + + const maxDepth = 3 + + var searchTypes func(t types.Type, addressable bool, mods []typeModKind) bool + searchTypes = func(t types.Type, addressable bool, mods []typeModKind) bool { + if f(t, addressable) { + if len(mods) > 0 { + newMods := make([]typeModKind, len(mods)+len(c.mods)) + copy(newMods, mods) + copy(newMods[len(mods):], c.mods) + c.mods = newMods + } + return true + } + + if len(mods) == maxDepth { + return false + } + + return derivableTypes(t, addressable, func(t types.Type, addressable bool, mod typeModKind) bool { + return searchTypes(t, addressable, append(mods, mod)) + }) + } + + return searchTypes(c.obj.Type(), c.addressable, make([]typeModKind, 0, maxDepth)) +} + +// matchingCandidate reports whether cand matches our type inferences. +// It mutates cand's score in certain cases. +func (c *completer) matchingCandidate(cand *candidate) bool { + if c.completionContext.commentCompletion { + return false + } + + // Bail out early if we are completing a field name in a composite literal. + if v, ok := cand.obj.(*types.Var); ok && v.IsField() && c.wantStructFieldCompletions() { + return true + } + + if isTypeName(cand.obj) { + return c.matchingTypeName(cand) + } else if c.wantTypeName() { + // If we want a type, a non-type object never matches. + return false + } + + if c.inference.candTypeMatches(cand) { + return true + } + + candType := cand.obj.Type() + if candType == nil { + return false + } + + if sig, ok := candType.Underlying().(*types.Signature); ok { + if c.inference.assigneesMatch(cand, sig) { + // Invoke the candidate if its results are multi-assignable. + cand.mods = append(cand.mods, invoke) + return true + } + } + + // Default to invoking *types.Func candidates. This is so function + // completions in an empty statement (or other cases with no expected type) + // are invoked by default. + if isFunc(cand.obj) { + cand.mods = append(cand.mods, invoke) + } + + return false +} + +// candTypeMatches reports whether cand makes a good completion +// candidate given the candidate inference. cand's score may be +// mutated to downrank the candidate in certain situations. +func (ci *candidateInference) candTypeMatches(cand *candidate) bool { + var ( + expTypes = make([]types.Type, 0, 2) + variadicType types.Type + ) + if ci.objType != nil { + expTypes = append(expTypes, ci.objType) + + if ci.variadic { + variadicType = types.NewSlice(ci.objType) + expTypes = append(expTypes, variadicType) + } + } + + return cand.anyCandType(func(candType types.Type, addressable bool) bool { + // Take into account any type modifiers on the expected type. + candType = ci.applyTypeModifiers(candType, addressable) + if candType == nil { + return false + } + + if ci.convertibleTo != nil && convertibleTo(candType, ci.convertibleTo) { + return true + } + + for _, expType := range expTypes { + if isEmptyInterface(expType) { + continue + } + + matches := ci.typeMatches(expType, candType) + if !matches { + // If candType doesn't otherwise match, consider if we can + // convert candType directly to expType. + if considerTypeConversion(candType, expType, cand.path) { + cand.convertTo = expType + // Give a major score penalty so we always prefer directly + // assignable candidates, all else equal. + cand.score *= 0.5 + return true + } + + continue + } + + if expType == variadicType { + cand.mods = append(cand.mods, takeDotDotDot) + } + + // Lower candidate score for untyped conversions. This avoids + // ranking untyped constants above candidates with an exact type + // match. Don't lower score of builtin constants, e.g. "true". + if isUntyped(candType) && !types.Identical(candType, expType) && cand.obj.Parent() != types.Universe { + // Bigger penalty for deep completions into other packages to + // avoid random constants from other packages popping up all + // the time. + if len(cand.path) > 0 && isPkgName(cand.path[0]) { + cand.score *= 0.5 + } else { + cand.score *= 0.75 + } + } + + return true + } + + // If we don't have a specific expected type, fall back to coarser + // object kind checks. + if ci.objType == nil || isEmptyInterface(ci.objType) { + // If we were able to apply type modifiers to our candidate type, + // count that as a match. For example: + // + // var foo chan int + // <-fo<> + // + // We were able to apply the "<-" type modifier to "foo", so "foo" + // matches. + if len(ci.modifiers) > 0 { + return true + } + + // If we didn't have an exact type match, check if our object kind + // matches. + if ci.kindMatches(candType) { + if ci.objKind == kindFunc { + cand.mods = append(cand.mods, invoke) + } + return true + } + } + + return false + }) +} + +// considerTypeConversion returns true if we should offer a completion +// automatically converting "from" to "to". +func considerTypeConversion(from, to types.Type, path []types.Object) bool { + // Don't offer to convert deep completions from other packages. + // Otherwise there are many random package level consts/vars that + // pop up as candidates all the time. + if len(path) > 0 && isPkgName(path[0]) { + return false + } + + if _, ok := from.(*typeparams.TypeParam); ok { + return false + } + + if !convertibleTo(from, to) { + return false + } + + // Don't offer to convert ints to strings since that probably + // doesn't do what the user wants. + if isBasicKind(from, types.IsInteger) && isBasicKind(to, types.IsString) { + return false + } + + return true +} + +// typeMatches reports whether an object of candType makes a good +// completion candidate given the expected type expType. +func (ci *candidateInference) typeMatches(expType, candType types.Type) bool { + // Handle untyped values specially since AssignableTo gives false negatives + // for them (see https://golang.org/issue/32146). + if candBasic, ok := candType.Underlying().(*types.Basic); ok { + if expBasic, ok := expType.Underlying().(*types.Basic); ok { + // Note that the candidate and/or the expected can be untyped. + // In "fo<> == 100" the expected type is untyped, and the + // candidate could also be an untyped constant. + + // Sort by is_untyped and then by is_int to simplify below logic. + a, b := candBasic.Info(), expBasic.Info() + if a&types.IsUntyped == 0 || (b&types.IsInteger > 0 && b&types.IsUntyped > 0) { + a, b = b, a + } + + // If at least one is untyped... + if a&types.IsUntyped > 0 { + switch { + // Untyped integers are compatible with floats. + case a&types.IsInteger > 0 && b&types.IsFloat > 0: + return true + + // Check if their constant kind (bool|int|float|complex|string) matches. + // This doesn't take into account the constant value, so there will be some + // false positives due to integer sign and overflow. + case a&types.IsConstType == b&types.IsConstType: + return true + } + } + } + } + + // AssignableTo covers the case where the types are equal, but also handles + // cases like assigning a concrete type to an interface type. + return assignableTo(candType, expType) +} + +// kindMatches reports whether candType's kind matches our expected +// kind (e.g. slice, map, etc.). +func (ci *candidateInference) kindMatches(candType types.Type) bool { + return ci.objKind > 0 && ci.objKind&candKind(candType) > 0 +} + +// assigneesMatch reports whether an invocation of sig matches the +// number and type of any assignees. +func (ci *candidateInference) assigneesMatch(cand *candidate, sig *types.Signature) bool { + if len(ci.assignees) == 0 { + return false + } + + // Uniresult functions are always usable and are handled by the + // normal, non-assignees type matching logic. + if sig.Results().Len() == 1 { + return false + } + + // Don't prefer completing into func(...interface{}) calls since all + // functions would match. + if ci.variadicAssignees && len(ci.assignees) == 1 && isEmptyInterface(deslice(ci.assignees[0])) { + return false + } + + var numberOfResultsCouldMatch bool + if ci.variadicAssignees { + numberOfResultsCouldMatch = sig.Results().Len() >= len(ci.assignees)-1 + } else { + numberOfResultsCouldMatch = sig.Results().Len() == len(ci.assignees) + } + + // If our signature doesn't return the right number of values, it's + // not a match, so downrank it. For example: + // + // var foo func() (int, int) + // a, b, c := <> // downrank "foo()" since it only returns two values + if !numberOfResultsCouldMatch { + cand.score /= 2 + return false + } + + // If at least one assignee has a valid type, and all valid + // assignees match the corresponding sig result value, the signature + // is a match. + allMatch := false + for i := 0; i < sig.Results().Len(); i++ { + var assignee types.Type + + // If we are completing into variadic parameters, deslice the + // expected variadic type. + if ci.variadicAssignees && i >= len(ci.assignees)-1 { + assignee = ci.assignees[len(ci.assignees)-1] + if elem := deslice(assignee); elem != nil { + assignee = elem + } + } else { + assignee = ci.assignees[i] + } + + if assignee == nil || assignee == types.Typ[types.Invalid] { + continue + } + + allMatch = ci.typeMatches(assignee, sig.Results().At(i).Type()) + if !allMatch { + break + } + } + return allMatch +} + +func (c *completer) matchingTypeName(cand *candidate) bool { + if !c.wantTypeName() { + return false + } + + typeMatches := func(candType types.Type) bool { + // Take into account any type name modifier prefixes. + candType = c.inference.applyTypeNameModifiers(candType) + + if from := c.inference.typeName.assertableFrom; from != nil { + // Don't suggest the starting type in type assertions. For example, + // if "foo" is an io.Writer, don't suggest "foo.(io.Writer)". + if types.Identical(from, candType) { + return false + } + + if intf, ok := from.Underlying().(*types.Interface); ok { + if !types.AssertableTo(intf, candType) { + return false + } + } + } + + if c.inference.typeName.wantComparable && !types.Comparable(candType) { + return false + } + + // Skip this type if it has already been used in another type + // switch case. + for _, seen := range c.inference.typeName.seenTypeSwitchCases { + if types.Identical(candType, seen) { + return false + } + } + + // We can expect a type name and have an expected type in cases like: + // + // var foo []int + // foo = []i<> + // + // Where our expected type is "[]int", and we expect a type name. + if c.inference.objType != nil { + return assignableTo(candType, c.inference.objType) + } + + // Default to saying any type name is a match. + return true + } + + t := cand.obj.Type() + + if typeMatches(t) { + return true + } + + if !types.IsInterface(t) && typeMatches(types.NewPointer(t)) { + if c.inference.typeName.compLitType { + // If we are completing a composite literal type as in + // "foo<>{}", to make a pointer we must prepend "&". + cand.mods = append(cand.mods, reference) + } else { + // If we are completing a normal type name such as "foo<>", to + // make a pointer we must prepend "*". + cand.mods = append(cand.mods, dereference) + } + return true + } + + return false +} + +var ( + // "interface { Error() string }" (i.e. error) + errorIntf = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) + + // "interface { String() string }" (i.e. fmt.Stringer) + stringerIntf = types.NewInterfaceType([]*types.Func{ + types.NewFunc(token.NoPos, nil, "String", types.NewSignature( + nil, + nil, + types.NewTuple(types.NewParam(token.NoPos, nil, "", types.Typ[types.String])), + false, + )), + }, nil).Complete() + + byteType = types.Universe.Lookup("byte").Type() +) + +// candKind returns the objKind of candType, if any. +func candKind(candType types.Type) objKind { + var kind objKind + + switch t := candType.Underlying().(type) { + case *types.Array: + kind |= kindArray + if t.Elem() == byteType { + kind |= kindBytes + } + case *types.Slice: + kind |= kindSlice + if t.Elem() == byteType { + kind |= kindBytes + } + case *types.Chan: + kind |= kindChan + case *types.Map: + kind |= kindMap + case *types.Pointer: + kind |= kindPtr + + // Some builtins handle array pointers as arrays, so just report a pointer + // to an array as an array. + if _, isArray := t.Elem().Underlying().(*types.Array); isArray { + kind |= kindArray + } + case *types.Basic: + switch info := t.Info(); { + case info&types.IsString > 0: + kind |= kindString + case info&types.IsInteger > 0: + kind |= kindInt + case info&types.IsFloat > 0: + kind |= kindFloat + case info&types.IsComplex > 0: + kind |= kindComplex + case info&types.IsBoolean > 0: + kind |= kindBool + } + case *types.Signature: + return kindFunc + } + + if types.Implements(candType, errorIntf) { + kind |= kindError + } + + if types.Implements(candType, stringerIntf) { + kind |= kindStringer + } + + return kind +} + +// innermostScope returns the innermost scope for c.pos. +func (c *completer) innermostScope() *types.Scope { + for _, s := range c.scopes { + if s != nil { + return s + } + } + return nil +} + +// isSlice reports whether the object's underlying type is a slice. +func isSlice(obj types.Object) bool { + if obj != nil && obj.Type() != nil { + if _, ok := obj.Type().Underlying().(*types.Slice); ok { + return true + } + } + return false +} + +// forEachPackageMember calls f(tok, id, fn) for each package-level +// TYPE/VAR/CONST/FUNC declaration in the Go source file, based on a +// quick partial parse. fn is non-nil only for function declarations. +// The AST position information is garbage. +func forEachPackageMember(content []byte, f func(tok token.Token, id *ast.Ident, fn *ast.FuncDecl)) { + purged := purgeFuncBodies(content) + file, _ := parser.ParseFile(token.NewFileSet(), "", purged, 0) + for _, decl := range file.Decls { + switch decl := decl.(type) { + case *ast.GenDecl: + for _, spec := range decl.Specs { + switch spec := spec.(type) { + case *ast.ValueSpec: // var/const + for _, id := range spec.Names { + f(decl.Tok, id, nil) + } + case *ast.TypeSpec: + f(decl.Tok, spec.Name, nil) + } + } + case *ast.FuncDecl: + if decl.Recv == nil { + f(token.FUNC, decl.Name, decl) + } + } + } +} + +// purgeFuncBodies returns a copy of src in which the contents of each +// outermost {...} region except struct and interface types have been +// deleted. It does not preserve newlines. This reduces the amount of +// work required to parse the top-level declarations. +func purgeFuncBodies(src []byte) []byte { + // Destroy the content of any {...}-bracketed regions that are + // not immediately preceded by a "struct" or "interface" + // token. That includes function bodies, composite literals, + // switch/select bodies, and all blocks of statements. + // This will lead to non-void functions that don't have return + // statements, which of course is a type error, but that's ok. + + var out bytes.Buffer + file := token.NewFileSet().AddFile("", -1, len(src)) + var sc scanner.Scanner + sc.Init(file, src, nil, 0) + var prev token.Token + var cursor int // last consumed src offset + var braces []token.Pos // stack of unclosed braces or -1 for struct/interface type + for { + pos, tok, _ := sc.Scan() + if tok == token.EOF { + break + } + switch tok { + case token.COMMENT: + // TODO(adonovan): opt: skip, to save an estimated 20% of time. + + case token.LBRACE: + if prev == token.STRUCT || prev == token.INTERFACE { + pos = -1 + } + braces = append(braces, pos) + + case token.RBRACE: + if last := len(braces) - 1; last >= 0 { + top := braces[last] + braces = braces[:last] + if top < 0 { + // struct/interface type: leave alone + } else if len(braces) == 0 { // toplevel only + // Delete {...} body. + start, _ := safetoken.Offset(file, top) + end, _ := safetoken.Offset(file, pos) + out.Write(src[cursor : start+len("{")]) + cursor = end + } + } + } + prev = tok + } + out.Write(src[cursor:]) + return out.Bytes() +} diff --git a/gopls/internal/lsp/source/completion/deep_completion.go b/gopls/internal/lsp/source/completion/deep_completion.go new file mode 100644 index 000000000..a72d56191 --- /dev/null +++ b/gopls/internal/lsp/source/completion/deep_completion.go @@ -0,0 +1,362 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package completion + +import ( + "context" + "go/types" + "strings" + "time" +) + +// MaxDeepCompletions limits deep completion results because in most cases +// there are too many to be useful. +const MaxDeepCompletions = 3 + +// deepCompletionState stores our state as we search for deep completions. +// "deep completion" refers to searching into objects' fields and methods to +// find more completion candidates. +type deepCompletionState struct { + // enabled indicates whether deep completion is permitted. + enabled bool + + // queueClosed is used to disable adding new sub-fields to search queue + // once we're running out of our time budget. + queueClosed bool + + // thisQueue holds the current breadth first search queue. + thisQueue []candidate + + // nextQueue holds the next breadth first search iteration's queue. + nextQueue []candidate + + // highScores tracks the highest deep candidate scores we have found + // so far. This is used to avoid work for low scoring deep candidates. + highScores [MaxDeepCompletions]float64 + + // candidateCount is the count of unique deep candidates encountered + // so far. + candidateCount int +} + +// enqueue adds a candidate to the search queue. +func (s *deepCompletionState) enqueue(cand candidate) { + s.nextQueue = append(s.nextQueue, cand) +} + +// dequeue removes and returns the leftmost element from the search queue. +func (s *deepCompletionState) dequeue() *candidate { + var cand *candidate + cand, s.thisQueue = &s.thisQueue[len(s.thisQueue)-1], s.thisQueue[:len(s.thisQueue)-1] + return cand +} + +// scorePenalty computes a deep candidate score penalty. A candidate is +// penalized based on depth to favor shallower candidates. We also give a +// slight bonus to unexported objects and a slight additional penalty to +// function objects. +func (s *deepCompletionState) scorePenalty(cand *candidate) float64 { + var deepPenalty float64 + for _, dc := range cand.path { + deepPenalty++ + + if !dc.Exported() { + deepPenalty -= 0.1 + } + + if _, isSig := dc.Type().Underlying().(*types.Signature); isSig { + deepPenalty += 0.1 + } + } + + // Normalize penalty to a max depth of 10. + return deepPenalty / 10 +} + +// isHighScore returns whether score is among the top MaxDeepCompletions deep +// candidate scores encountered so far. If so, it adds score to highScores, +// possibly displacing an existing high score. +func (s *deepCompletionState) isHighScore(score float64) bool { + // Invariant: s.highScores is sorted with highest score first. Unclaimed + // positions are trailing zeros. + + // If we beat an existing score then take its spot. + for i, deepScore := range s.highScores { + if score <= deepScore { + continue + } + + if deepScore != 0 && i != len(s.highScores)-1 { + // If this wasn't an empty slot then we need to scooch everyone + // down one spot. + copy(s.highScores[i+1:], s.highScores[i:]) + } + s.highScores[i] = score + return true + } + + return false +} + +// newPath returns path from search root for an object following a given +// candidate. +func (s *deepCompletionState) newPath(cand candidate, obj types.Object) []types.Object { + path := make([]types.Object, len(cand.path)+1) + copy(path, cand.path) + path[len(path)-1] = obj + + return path +} + +// deepSearch searches a candidate and its subordinate objects for completion +// items if deep completion is enabled and adds the valid candidates to +// completion items. +func (c *completer) deepSearch(ctx context.Context) { + defer func() { + // We can return early before completing the search, so be sure to + // clear out our queues to not impact any further invocations. + c.deepState.thisQueue = c.deepState.thisQueue[:0] + c.deepState.nextQueue = c.deepState.nextQueue[:0] + }() + + for len(c.deepState.nextQueue) > 0 { + c.deepState.thisQueue, c.deepState.nextQueue = c.deepState.nextQueue, c.deepState.thisQueue[:0] + + outer: + for _, cand := range c.deepState.thisQueue { + obj := cand.obj + + if obj == nil { + continue + } + + // At the top level, dedupe by object. + if len(cand.path) == 0 { + if c.seen[obj] { + continue + } + c.seen[obj] = true + } + + // If obj is not accessible because it lives in another package and is + // not exported, don't treat it as a completion candidate unless it's + // a package completion candidate. + if !c.completionContext.packageCompletion && + obj.Pkg() != nil && obj.Pkg() != c.pkg.GetTypes() && !obj.Exported() { + continue + } + + // If we want a type name, don't offer non-type name candidates. + // However, do offer package names since they can contain type names, + // and do offer any candidate without a type since we aren't sure if it + // is a type name or not (i.e. unimported candidate). + if c.wantTypeName() && obj.Type() != nil && !isTypeName(obj) && !isPkgName(obj) { + continue + } + + // When searching deep, make sure we don't have a cycle in our chain. + // We don't dedupe by object because we want to allow both "foo.Baz" + // and "bar.Baz" even though "Baz" is represented the same types.Object + // in both. + for _, seenObj := range cand.path { + if seenObj == obj { + continue outer + } + } + + c.addCandidate(ctx, &cand) + + c.deepState.candidateCount++ + if c.opts.budget > 0 && c.deepState.candidateCount%100 == 0 { + spent := float64(time.Since(c.startTime)) / float64(c.opts.budget) + select { + case <-ctx.Done(): + return + default: + // If we are almost out of budgeted time, no further elements + // should be added to the queue. This ensures remaining time is + // used for processing current queue. + if !c.deepState.queueClosed && spent >= 0.85 { + c.deepState.queueClosed = true + } + } + } + + // if deep search is disabled, don't add any more candidates. + if !c.deepState.enabled || c.deepState.queueClosed { + continue + } + + // Searching members for a type name doesn't make sense. + if isTypeName(obj) { + continue + } + if obj.Type() == nil { + continue + } + + // Don't search embedded fields because they were already included in their + // parent's fields. + if v, ok := obj.(*types.Var); ok && v.Embedded() { + continue + } + + if sig, ok := obj.Type().Underlying().(*types.Signature); ok { + // If obj is a function that takes no arguments and returns one + // value, keep searching across the function call. + if sig.Params().Len() == 0 && sig.Results().Len() == 1 { + path := c.deepState.newPath(cand, obj) + // The result of a function call is not addressable. + c.methodsAndFields(sig.Results().At(0).Type(), false, cand.imp, func(newCand candidate) { + newCand.pathInvokeMask = cand.pathInvokeMask | (1 << uint64(len(cand.path))) + newCand.path = path + c.deepState.enqueue(newCand) + }) + } + } + + path := c.deepState.newPath(cand, obj) + switch obj := obj.(type) { + case *types.PkgName: + c.packageMembers(obj.Imported(), stdScore, cand.imp, func(newCand candidate) { + newCand.pathInvokeMask = cand.pathInvokeMask + newCand.path = path + c.deepState.enqueue(newCand) + }) + default: + c.methodsAndFields(obj.Type(), cand.addressable, cand.imp, func(newCand candidate) { + newCand.pathInvokeMask = cand.pathInvokeMask + newCand.path = path + c.deepState.enqueue(newCand) + }) + } + } + } +} + +// addCandidate adds a completion candidate to suggestions, without searching +// its members for more candidates. +func (c *completer) addCandidate(ctx context.Context, cand *candidate) { + obj := cand.obj + if c.matchingCandidate(cand) { + cand.score *= highScore + + if p := c.penalty(cand); p > 0 { + cand.score *= (1 - p) + } + } else if isTypeName(obj) { + // If obj is a *types.TypeName that didn't otherwise match, check + // if a literal object of this type makes a good candidate. + + // We only care about named types (i.e. don't want builtin types). + if _, isNamed := obj.Type().(*types.Named); isNamed { + c.literal(ctx, obj.Type(), cand.imp) + } + } + + // Lower score of method calls so we prefer fields and vars over calls. + if cand.hasMod(invoke) { + if sig, ok := obj.Type().Underlying().(*types.Signature); ok && sig.Recv() != nil { + cand.score *= 0.9 + } + } + + // Prefer private objects over public ones. + if !obj.Exported() && obj.Parent() != types.Universe { + cand.score *= 1.1 + } + + // Slight penalty for index modifier (e.g. changing "foo" to + // "foo[]") to curb false positives. + if cand.hasMod(index) { + cand.score *= 0.9 + } + + // Favor shallow matches by lowering score according to depth. + cand.score -= cand.score * c.deepState.scorePenalty(cand) + + if cand.score < 0 { + cand.score = 0 + } + + cand.name = deepCandName(cand) + if item, err := c.item(ctx, *cand); err == nil { + c.items = append(c.items, item) + } +} + +// deepCandName produces the full candidate name including any +// ancestor objects. For example, "foo.bar().baz" for candidate "baz". +func deepCandName(cand *candidate) string { + totalLen := len(cand.obj.Name()) + for i, obj := range cand.path { + totalLen += len(obj.Name()) + 1 + if cand.pathInvokeMask&(1<<uint16(i)) > 0 { + totalLen += 2 + } + } + + var buf strings.Builder + buf.Grow(totalLen) + + for i, obj := range cand.path { + buf.WriteString(obj.Name()) + if cand.pathInvokeMask&(1<<uint16(i)) > 0 { + buf.WriteByte('(') + buf.WriteByte(')') + } + buf.WriteByte('.') + } + + buf.WriteString(cand.obj.Name()) + + return buf.String() +} + +// penalty reports a score penalty for cand in the range (0, 1). +// For example, a candidate is penalized if it has already been used +// in another switch case statement. +func (c *completer) penalty(cand *candidate) float64 { + for _, p := range c.inference.penalized { + if c.objChainMatches(cand, p.objChain) { + return p.penalty + } + } + + return 0 +} + +// objChainMatches reports whether cand combined with the surrounding +// object prefix matches chain. +func (c *completer) objChainMatches(cand *candidate, chain []types.Object) bool { + // For example, when completing: + // + // foo.ba<> + // + // If we are considering the deep candidate "bar.baz", cand is baz, + // objChain is [foo] and deepChain is [bar]. We would match the + // chain [foo, bar, baz]. + if len(chain) != len(c.inference.objChain)+len(cand.path)+1 { + return false + } + + if chain[len(chain)-1] != cand.obj { + return false + } + + for i, o := range c.inference.objChain { + if chain[i] != o { + return false + } + } + + for i, o := range cand.path { + if chain[i+len(c.inference.objChain)] != o { + return false + } + } + + return true +} diff --git a/gopls/internal/lsp/source/completion/deep_completion_test.go b/gopls/internal/lsp/source/completion/deep_completion_test.go new file mode 100644 index 000000000..27009af1b --- /dev/null +++ b/gopls/internal/lsp/source/completion/deep_completion_test.go @@ -0,0 +1,33 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package completion + +import ( + "testing" +) + +func TestDeepCompletionIsHighScore(t *testing.T) { + // Test that deepCompletionState.isHighScore properly tracks the top + // N=MaxDeepCompletions scores. + + var s deepCompletionState + + if !s.isHighScore(1) { + // No other scores yet, anything is a winner. + t.Error("1 should be high score") + } + + // Fill up with higher scores. + for i := 0; i < MaxDeepCompletions; i++ { + if !s.isHighScore(10) { + t.Error("10 should be high score") + } + } + + // High scores should be filled with 10s so 2 is not a high score. + if s.isHighScore(2) { + t.Error("2 shouldn't be high score") + } +} diff --git a/gopls/internal/lsp/source/completion/definition.go b/gopls/internal/lsp/source/completion/definition.go new file mode 100644 index 000000000..d7f51f002 --- /dev/null +++ b/gopls/internal/lsp/source/completion/definition.go @@ -0,0 +1,160 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package completion + +import ( + "go/ast" + "go/types" + "strings" + "unicode" + "unicode/utf8" + + "golang.org/x/tools/gopls/internal/lsp/protocol" + "golang.org/x/tools/gopls/internal/lsp/snippet" + "golang.org/x/tools/gopls/internal/lsp/source" +) + +// some function definitions in test files can be completed +// So far, TestFoo(t *testing.T), TestMain(m *testing.M) +// BenchmarkFoo(b *testing.B), FuzzFoo(f *testing.F) + +// path[0] is known to be *ast.Ident +func definition(path []ast.Node, obj types.Object, pgf *source.ParsedGoFile) ([]CompletionItem, *Selection) { + if _, ok := obj.(*types.Func); !ok { + return nil, nil // not a function at all + } + if !strings.HasSuffix(pgf.URI.Filename(), "_test.go") { + return nil, nil // not a test file + } + + name := path[0].(*ast.Ident).Name + if len(name) == 0 { + // can't happen + return nil, nil + } + start := path[0].Pos() + end := path[0].End() + sel := &Selection{ + content: "", + cursor: start, + tokFile: pgf.Tok, + start: start, + end: end, + mapper: pgf.Mapper, + } + var ans []CompletionItem + var hasParens bool + n, ok := path[1].(*ast.FuncDecl) + if !ok { + return nil, nil // can't happen + } + if n.Recv != nil { + return nil, nil // a method, not a function + } + t := n.Type.Params + if t.Closing != t.Opening { + hasParens = true + } + + // Always suggest TestMain, if possible + if strings.HasPrefix("TestMain", name) { + if hasParens { + ans = append(ans, defItem("TestMain", obj)) + } else { + ans = append(ans, defItem("TestMain(m *testing.M)", obj)) + } + } + + // If a snippet is possible, suggest it + if strings.HasPrefix("Test", name) { + if hasParens { + ans = append(ans, defItem("Test", obj)) + } else { + ans = append(ans, defSnippet("Test", "(t *testing.T)", obj)) + } + return ans, sel + } else if strings.HasPrefix("Benchmark", name) { + if hasParens { + ans = append(ans, defItem("Benchmark", obj)) + } else { + ans = append(ans, defSnippet("Benchmark", "(b *testing.B)", obj)) + } + return ans, sel + } else if strings.HasPrefix("Fuzz", name) { + if hasParens { + ans = append(ans, defItem("Fuzz", obj)) + } else { + ans = append(ans, defSnippet("Fuzz", "(f *testing.F)", obj)) + } + return ans, sel + } + + // Fill in the argument for what the user has already typed + if got := defMatches(name, "Test", path, "(t *testing.T)"); got != "" { + ans = append(ans, defItem(got, obj)) + } else if got := defMatches(name, "Benchmark", path, "(b *testing.B)"); got != "" { + ans = append(ans, defItem(got, obj)) + } else if got := defMatches(name, "Fuzz", path, "(f *testing.F)"); got != "" { + ans = append(ans, defItem(got, obj)) + } + return ans, sel +} + +// defMatches returns text for defItem, never for defSnippet +func defMatches(name, pat string, path []ast.Node, arg string) string { + if !strings.HasPrefix(name, pat) { + return "" + } + c, _ := utf8.DecodeRuneInString(name[len(pat):]) + if unicode.IsLower(c) { + return "" + } + fd, ok := path[1].(*ast.FuncDecl) + if !ok { + // we don't know what's going on + return "" + } + fp := fd.Type.Params + if len(fp.List) > 0 { + // signature already there, nothing to suggest + return "" + } + if fp.Opening != fp.Closing { + // nothing: completion works on words, not easy to insert arg + return "" + } + // suggesting signature too + return name + arg +} + +func defSnippet(prefix, suffix string, obj types.Object) CompletionItem { + var sn snippet.Builder + sn.WriteText(prefix) + sn.WritePlaceholder(func(b *snippet.Builder) { b.WriteText("Xxx") }) + sn.WriteText(suffix + " {\n\t") + sn.WriteFinalTabstop() + sn.WriteText("\n}") + return CompletionItem{ + Label: prefix + "Xxx" + suffix, + Detail: "tab, type the rest of the name, then tab", + Kind: protocol.FunctionCompletion, + Depth: 0, + Score: 10, + snippet: &sn, + Documentation: prefix + " test function", + isSlice: isSlice(obj), + } +} +func defItem(val string, obj types.Object) CompletionItem { + return CompletionItem{ + Label: val, + InsertText: val, + Kind: protocol.FunctionCompletion, + Depth: 0, + Score: 9, // prefer the snippets when available + Documentation: "complete the function name", + isSlice: isSlice(obj), + } +} diff --git a/gopls/internal/lsp/source/completion/format.go b/gopls/internal/lsp/source/completion/format.go new file mode 100644 index 000000000..c2d2c0bc0 --- /dev/null +++ b/gopls/internal/lsp/source/completion/format.go @@ -0,0 +1,338 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package completion + +import ( + "context" + "errors" + "fmt" + "go/ast" + "go/doc" + "go/types" + "strings" + + "golang.org/x/tools/gopls/internal/lsp/protocol" + "golang.org/x/tools/gopls/internal/lsp/safetoken" + "golang.org/x/tools/gopls/internal/lsp/snippet" + "golang.org/x/tools/gopls/internal/lsp/source" + "golang.org/x/tools/gopls/internal/span" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/imports" + "golang.org/x/tools/internal/typeparams" +) + +var ( + errNoMatch = errors.New("not a surrounding match") + errLowScore = errors.New("not a high scoring candidate") +) + +// item formats a candidate to a CompletionItem. +func (c *completer) item(ctx context.Context, cand candidate) (CompletionItem, error) { + obj := cand.obj + + // if the object isn't a valid match against the surrounding, return early. + matchScore := c.matcher.Score(cand.name) + if matchScore <= 0 { + return CompletionItem{}, errNoMatch + } + cand.score *= float64(matchScore) + + // Ignore deep candidates that won't be in the MaxDeepCompletions anyway. + if len(cand.path) != 0 && !c.deepState.isHighScore(cand.score) { + return CompletionItem{}, errLowScore + } + + // Handle builtin types separately. + if obj.Parent() == types.Universe { + return c.formatBuiltin(ctx, cand) + } + + var ( + label = cand.name + detail = types.TypeString(obj.Type(), c.qf) + insert = label + kind = protocol.TextCompletion + snip snippet.Builder + protocolEdits []protocol.TextEdit + ) + if obj.Type() == nil { + detail = "" + } + if isTypeName(obj) && c.wantTypeParams() { + x := cand.obj.(*types.TypeName) + if named, ok := x.Type().(*types.Named); ok { + tp := typeparams.ForNamed(named) + label += source.FormatTypeParams(tp) + insert = label // maintain invariant above (label == insert) + } + } + + snip.WriteText(insert) + + switch obj := obj.(type) { + case *types.TypeName: + detail, kind = source.FormatType(obj.Type(), c.qf) + case *types.Const: + kind = protocol.ConstantCompletion + case *types.Var: + if _, ok := obj.Type().(*types.Struct); ok { + detail = "struct{...}" // for anonymous structs + } else if obj.IsField() { + var err error + detail, err = source.FormatVarType(ctx, c.snapshot, c.pkg, obj, c.qf, c.mq) + if err != nil { + return CompletionItem{}, err + } + } + if obj.IsField() { + kind = protocol.FieldCompletion + c.structFieldSnippet(cand, detail, &snip) + } else { + kind = protocol.VariableCompletion + } + if obj.Type() == nil { + break + } + case *types.Func: + sig, ok := obj.Type().Underlying().(*types.Signature) + if !ok { + break + } + kind = protocol.FunctionCompletion + if sig != nil && sig.Recv() != nil { + kind = protocol.MethodCompletion + } + case *types.PkgName: + kind = protocol.ModuleCompletion + detail = fmt.Sprintf("%q", obj.Imported().Path()) + case *types.Label: + kind = protocol.ConstantCompletion + detail = "label" + } + + var prefix string + for _, mod := range cand.mods { + switch mod { + case reference: + prefix = "&" + prefix + case dereference: + prefix = "*" + prefix + case chanRead: + prefix = "<-" + prefix + } + } + + var ( + suffix string + funcType = obj.Type() + ) +Suffixes: + for _, mod := range cand.mods { + switch mod { + case invoke: + if sig, ok := funcType.Underlying().(*types.Signature); ok { + s, err := source.NewSignature(ctx, c.snapshot, c.pkg, sig, nil, c.qf, c.mq) + if err != nil { + return CompletionItem{}, err + } + c.functionCallSnippet("", s.TypeParams(), s.Params(), &snip) + if sig.Results().Len() == 1 { + funcType = sig.Results().At(0).Type() + } + detail = "func" + s.Format() + } + + if !c.opts.snippets { + // Without snippets the candidate will not include "()". Don't + // add further suffixes since they will be invalid. For + // example, with snippets "foo()..." would become "foo..." + // without snippets if we added the dotDotDot. + break Suffixes + } + case takeSlice: + suffix += "[:]" + case takeDotDotDot: + suffix += "..." + case index: + snip.WriteText("[") + snip.WritePlaceholder(nil) + snip.WriteText("]") + } + } + + // If this candidate needs an additional import statement, + // add the additional text edits needed. + if cand.imp != nil { + addlEdits, err := c.importEdits(cand.imp) + + if err != nil { + return CompletionItem{}, err + } + + protocolEdits = append(protocolEdits, addlEdits...) + if kind != protocol.ModuleCompletion { + if detail != "" { + detail += " " + } + detail += fmt.Sprintf("(from %q)", cand.imp.importPath) + } + } + + if cand.convertTo != nil { + typeName := types.TypeString(cand.convertTo, c.qf) + + switch cand.convertTo.(type) { + // We need extra parens when casting to these types. For example, + // we need "(*int)(foo)", not "*int(foo)". + case *types.Pointer, *types.Signature: + typeName = "(" + typeName + ")" + } + + prefix = typeName + "(" + prefix + suffix = ")" + } + + if prefix != "" { + // If we are in a selector, add an edit to place prefix before selector. + if sel := enclosingSelector(c.path, c.pos); sel != nil { + edits, err := c.editText(sel.Pos(), sel.Pos(), prefix) + if err != nil { + return CompletionItem{}, err + } + protocolEdits = append(protocolEdits, edits...) + } else { + // If there is no selector, just stick the prefix at the start. + insert = prefix + insert + snip.PrependText(prefix) + } + } + + if suffix != "" { + insert += suffix + snip.WriteText(suffix) + } + + detail = strings.TrimPrefix(detail, "untyped ") + // override computed detail with provided detail, if something is provided. + if cand.detail != "" { + detail = cand.detail + } + item := CompletionItem{ + Label: label, + InsertText: insert, + AdditionalTextEdits: protocolEdits, + Detail: detail, + Kind: kind, + Score: cand.score, + Depth: len(cand.path), + snippet: &snip, + isSlice: isSlice(obj), + } + // If the user doesn't want documentation for completion items. + if !c.opts.documentation { + return item, nil + } + pos := safetoken.StartPosition(c.pkg.FileSet(), obj.Pos()) + + // We ignore errors here, because some types, like "unsafe" or "error", + // may not have valid positions that we can use to get documentation. + if !pos.IsValid() { + return item, nil + } + + comment, err := source.HoverDocForObject(ctx, c.snapshot, c.pkg.FileSet(), obj) + if err != nil { + event.Error(ctx, fmt.Sprintf("failed to find Hover for %q", obj.Name()), err) + return item, nil + } + if c.opts.fullDocumentation { + item.Documentation = comment.Text() + } else { + item.Documentation = doc.Synopsis(comment.Text()) + } + // The desired pattern is `^// Deprecated`, but the prefix has been removed + // TODO(rfindley): It doesn't look like this does the right thing for + // multi-line comments. + if strings.HasPrefix(comment.Text(), "Deprecated") { + if c.snapshot.View().Options().CompletionTags { + item.Tags = []protocol.CompletionItemTag{protocol.ComplDeprecated} + } else if c.snapshot.View().Options().CompletionDeprecated { + item.Deprecated = true + } + } + + return item, nil +} + +// importEdits produces the text edits necessary to add the given import to the current file. +func (c *completer) importEdits(imp *importInfo) ([]protocol.TextEdit, error) { + if imp == nil { + return nil, nil + } + + pgf, err := c.pkg.File(span.URIFromPath(c.filename)) + if err != nil { + return nil, err + } + + return source.ComputeOneImportFixEdits(c.snapshot, pgf, &imports.ImportFix{ + StmtInfo: imports.ImportInfo{ + ImportPath: imp.importPath, + Name: imp.name, + }, + // IdentName is unused on this path and is difficult to get. + FixType: imports.AddImport, + }) +} + +func (c *completer) formatBuiltin(ctx context.Context, cand candidate) (CompletionItem, error) { + obj := cand.obj + item := CompletionItem{ + Label: obj.Name(), + InsertText: obj.Name(), + Score: cand.score, + } + switch obj.(type) { + case *types.Const: + item.Kind = protocol.ConstantCompletion + case *types.Builtin: + item.Kind = protocol.FunctionCompletion + sig, err := source.NewBuiltinSignature(ctx, c.snapshot, obj.Name()) + if err != nil { + return CompletionItem{}, err + } + item.Detail = "func" + sig.Format() + item.snippet = &snippet.Builder{} + c.functionCallSnippet(obj.Name(), sig.TypeParams(), sig.Params(), item.snippet) + case *types.TypeName: + if types.IsInterface(obj.Type()) { + item.Kind = protocol.InterfaceCompletion + } else { + item.Kind = protocol.ClassCompletion + } + case *types.Nil: + item.Kind = protocol.VariableCompletion + } + return item, nil +} + +// decide if the type params (if any) should be part of the completion +// which only possible for types.Named and types.Signature +// (so far, only in receivers, e.g.; func (s *GENERIC[K, V])..., which is a types.Named) +func (c *completer) wantTypeParams() bool { + // Need to be lexically in a receiver, and a child of an IndexListExpr + // (but IndexListExpr only exists with go1.18) + start := c.path[0].Pos() + for i, nd := range c.path { + if fd, ok := nd.(*ast.FuncDecl); ok { + if i > 0 && fd.Recv != nil && start < fd.Recv.End() { + return true + } else { + return false + } + } + } + return false +} diff --git a/gopls/internal/lsp/source/completion/fuzz.go b/gopls/internal/lsp/source/completion/fuzz.go new file mode 100644 index 000000000..08e7654c7 --- /dev/null +++ b/gopls/internal/lsp/source/completion/fuzz.go @@ -0,0 +1,142 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package completion + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "strings" + + "golang.org/x/tools/gopls/internal/lsp/protocol" +) + +// golang/go#51089 +// *testing.F deserves special treatment as member use is constrained: +// The arguments to f.Fuzz are determined by the arguments to a previous f.Add +// Inside f.Fuzz only f.Failed and f.Name are allowed. +// PJW: are there other packages where we can deduce usage constraints? + +// if we find fuzz completions, then return true, as those are the only completions to offer +func (c *completer) fuzz(typ types.Type, mset *types.MethodSet, imp *importInfo, cb func(candidate), fset *token.FileSet) bool { + // 1. inside f.Fuzz? (only f.Failed and f.Name) + // 2. possible completing f.Fuzz? + // [Ident,SelectorExpr,Callexpr,ExprStmt,BlockiStmt,FuncDecl(Fuzz...)] + // 3. before f.Fuzz, same (for 2., offer choice when looking at an F) + + // does the path contain FuncLit as arg to f.Fuzz CallExpr? + inside := false +Loop: + for i, n := range c.path { + switch v := n.(type) { + case *ast.CallExpr: + if len(v.Args) != 1 { + continue Loop + } + if _, ok := v.Args[0].(*ast.FuncLit); !ok { + continue + } + if s, ok := v.Fun.(*ast.SelectorExpr); !ok || s.Sel.Name != "Fuzz" { + continue + } + if i > 2 { // avoid t.Fuzz itself in tests + inside = true + break Loop + } + } + } + if inside { + for i := 0; i < mset.Len(); i++ { + o := mset.At(i).Obj() + if o.Name() == "Failed" || o.Name() == "Name" { + cb(candidate{ + obj: o, + score: stdScore, + imp: imp, + addressable: true, + }) + } + } + return true + } + // if it could be t.Fuzz, look for the preceding t.Add + id, ok := c.path[0].(*ast.Ident) + if ok && strings.HasPrefix("Fuzz", id.Name) { + var add *ast.CallExpr + f := func(n ast.Node) bool { + if n == nil { + return true + } + call, ok := n.(*ast.CallExpr) + if !ok { + return true + } + s, ok := call.Fun.(*ast.SelectorExpr) + if !ok { + return true + } + if s.Sel.Name != "Add" { + return true + } + // Sel.X should be of type *testing.F + got := c.pkg.GetTypesInfo().Types[s.X] + if got.Type.String() == "*testing.F" { + add = call + } + return false // because we're done... + } + // look at the enclosing FuzzFoo functions + if len(c.path) < 2 { + return false + } + n := c.path[len(c.path)-2] + if _, ok := n.(*ast.FuncDecl); !ok { + // the path should start with ast.File, ast.FuncDecl, ... + // but it didn't, so give up + return false + } + ast.Inspect(n, f) + if add == nil { + // looks like f.Fuzz without a preceding f.Add. + // let the regular completion handle it. + return false + } + + lbl := "Fuzz(func(t *testing.T" + for i, a := range add.Args { + info := c.pkg.GetTypesInfo().TypeOf(a) + if info == nil { + return false // How could this happen, but better safe than panic. + } + lbl += fmt.Sprintf(", %c %s", 'a'+i, info) + } + lbl += ")" + xx := CompletionItem{ + Label: lbl, + InsertText: lbl, + Kind: protocol.FunctionCompletion, + Depth: 0, + Score: 10, // pretty confident the user should see this + Documentation: "argument types from f.Add", + isSlice: false, + } + c.items = append(c.items, xx) + for i := 0; i < mset.Len(); i++ { + o := mset.At(i).Obj() + if o.Name() != "Fuzz" { + cb(candidate{ + obj: o, + score: stdScore, + imp: imp, + addressable: true, + }) + } + } + return true // done + } + // let the standard processing take care of it instead + return false +} diff --git a/gopls/internal/lsp/source/completion/keywords.go b/gopls/internal/lsp/source/completion/keywords.go new file mode 100644 index 000000000..a068ca2d5 --- /dev/null +++ b/gopls/internal/lsp/source/completion/keywords.go @@ -0,0 +1,154 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package completion + +import ( + "go/ast" + + "golang.org/x/tools/gopls/internal/lsp/protocol" + "golang.org/x/tools/gopls/internal/lsp/source" +) + +const ( + BREAK = "break" + CASE = "case" + CHAN = "chan" + CONST = "const" + CONTINUE = "continue" + DEFAULT = "default" + DEFER = "defer" + ELSE = "else" + FALLTHROUGH = "fallthrough" + FOR = "for" + FUNC = "func" + GO = "go" + GOTO = "goto" + IF = "if" + IMPORT = "import" + INTERFACE = "interface" + MAP = "map" + PACKAGE = "package" + RANGE = "range" + RETURN = "return" + SELECT = "select" + STRUCT = "struct" + SWITCH = "switch" + TYPE = "type" + VAR = "var" +) + +// addKeywordCompletions offers keyword candidates appropriate at the position. +func (c *completer) addKeywordCompletions() { + seen := make(map[string]bool) + + if c.wantTypeName() && c.inference.objType == nil { + // If we want a type name but don't have an expected obj type, + // include "interface", "struct", "func", "chan", and "map". + + // "interface" and "struct" are more common declaring named types. + // Give them a higher score if we are in a type declaration. + structIntf, funcChanMap := stdScore, highScore + if len(c.path) > 1 { + if _, namedDecl := c.path[1].(*ast.TypeSpec); namedDecl { + structIntf, funcChanMap = highScore, stdScore + } + } + + c.addKeywordItems(seen, structIntf, STRUCT, INTERFACE) + c.addKeywordItems(seen, funcChanMap, FUNC, CHAN, MAP) + } + + // If we are at the file scope, only offer decl keywords. We don't + // get *ast.Idents at the file scope because non-keyword identifiers + // turn into *ast.BadDecl, not *ast.Ident. + if len(c.path) == 1 || isASTFile(c.path[1]) { + c.addKeywordItems(seen, stdScore, TYPE, CONST, VAR, FUNC, IMPORT) + return + } else if _, ok := c.path[0].(*ast.Ident); !ok { + // Otherwise only offer keywords if the client is completing an identifier. + return + } + + if len(c.path) > 2 { + // Offer "range" if we are in ast.ForStmt.Init. This is what the + // AST looks like before "range" is typed, e.g. "for i := r<>". + if loop, ok := c.path[2].(*ast.ForStmt); ok && source.NodeContains(loop.Init, c.pos) { + c.addKeywordItems(seen, stdScore, RANGE) + } + } + + // Only suggest keywords if we are beginning a statement. + switch n := c.path[1].(type) { + case *ast.BlockStmt, *ast.ExprStmt: + // OK - our ident must be at beginning of statement. + case *ast.CommClause: + // Make sure we aren't in the Comm statement. + if !n.Colon.IsValid() || c.pos <= n.Colon { + return + } + case *ast.CaseClause: + // Make sure we aren't in the case List. + if !n.Colon.IsValid() || c.pos <= n.Colon { + return + } + default: + return + } + + // Filter out keywords depending on scope + // Skip the first one because we want to look at the enclosing scopes + path := c.path[1:] + for i, n := range path { + switch node := n.(type) { + case *ast.CaseClause: + // only recommend "fallthrough" and "break" within the bodies of a case clause + if c.pos > node.Colon { + c.addKeywordItems(seen, stdScore, BREAK) + // "fallthrough" is only valid in switch statements. + // A case clause is always nested within a block statement in a switch statement, + // that block statement is nested within either a TypeSwitchStmt or a SwitchStmt. + if i+2 >= len(path) { + continue + } + if _, ok := path[i+2].(*ast.SwitchStmt); ok { + c.addKeywordItems(seen, stdScore, FALLTHROUGH) + } + } + case *ast.CommClause: + if c.pos > node.Colon { + c.addKeywordItems(seen, stdScore, BREAK) + } + case *ast.TypeSwitchStmt, *ast.SelectStmt, *ast.SwitchStmt: + c.addKeywordItems(seen, stdScore, CASE, DEFAULT) + case *ast.ForStmt, *ast.RangeStmt: + c.addKeywordItems(seen, stdScore, BREAK, CONTINUE) + // This is a bit weak, functions allow for many keywords + case *ast.FuncDecl: + if node.Body != nil && c.pos > node.Body.Lbrace { + c.addKeywordItems(seen, stdScore, DEFER, RETURN, FOR, GO, SWITCH, SELECT, IF, ELSE, VAR, CONST, GOTO, TYPE) + } + } + } +} + +// addKeywordItems dedupes and adds completion items for the specified +// keywords with the specified score. +func (c *completer) addKeywordItems(seen map[string]bool, score float64, kws ...string) { + for _, kw := range kws { + if seen[kw] { + continue + } + seen[kw] = true + + if matchScore := c.matcher.Score(kw); matchScore > 0 { + c.items = append(c.items, CompletionItem{ + Label: kw, + Kind: protocol.KeywordCompletion, + InsertText: kw, + Score: score * float64(matchScore), + }) + } + } +} diff --git a/gopls/internal/lsp/source/completion/labels.go b/gopls/internal/lsp/source/completion/labels.go new file mode 100644 index 000000000..e4fd961e3 --- /dev/null +++ b/gopls/internal/lsp/source/completion/labels.go @@ -0,0 +1,112 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package completion + +import ( + "go/ast" + "go/token" + "math" +) + +type labelType int + +const ( + labelNone labelType = iota + labelBreak + labelContinue + labelGoto +) + +// wantLabelCompletion returns true if we want (only) label +// completions at the position. +func (c *completer) wantLabelCompletion() labelType { + if _, ok := c.path[0].(*ast.Ident); ok && len(c.path) > 1 { + // We want a label if we are an *ast.Ident child of a statement + // that accepts a label, e.g. "break Lo<>". + return takesLabel(c.path[1]) + } + + return labelNone +} + +// takesLabel returns the corresponding labelType if n is a statement +// that accepts a label, otherwise labelNone. +func takesLabel(n ast.Node) labelType { + if bs, ok := n.(*ast.BranchStmt); ok { + switch bs.Tok { + case token.BREAK: + return labelBreak + case token.CONTINUE: + return labelContinue + case token.GOTO: + return labelGoto + } + } + return labelNone +} + +// labels adds completion items for labels defined in the enclosing +// function. +func (c *completer) labels(lt labelType) { + if c.enclosingFunc == nil { + return + } + + addLabel := func(score float64, l *ast.LabeledStmt) { + labelObj := c.pkg.GetTypesInfo().ObjectOf(l.Label) + if labelObj != nil { + c.deepState.enqueue(candidate{obj: labelObj, score: score}) + } + } + + switch lt { + case labelBreak, labelContinue: + // "break" and "continue" only accept labels from enclosing statements. + + for i, p := range c.path { + switch p := p.(type) { + case *ast.FuncLit: + // Labels are function scoped, so don't continue out of functions. + return + case *ast.LabeledStmt: + switch p.Stmt.(type) { + case *ast.ForStmt, *ast.RangeStmt: + // Loop labels can be used for "break" or "continue". + addLabel(highScore*math.Pow(.99, float64(i)), p) + case *ast.SwitchStmt, *ast.SelectStmt, *ast.TypeSwitchStmt: + // Switch and select labels can be used only for "break". + if lt == labelBreak { + addLabel(highScore*math.Pow(.99, float64(i)), p) + } + } + } + } + case labelGoto: + // Goto accepts any label in the same function not in a nested + // block. It also doesn't take labels that would jump across + // variable definitions, but ignore that case for now. + ast.Inspect(c.enclosingFunc.body, func(n ast.Node) bool { + if n == nil { + return false + } + + switch n := n.(type) { + // Only search into block-like nodes enclosing our "goto". + // This prevents us from finding labels in nested blocks. + case *ast.BlockStmt, *ast.CommClause, *ast.CaseClause: + for _, p := range c.path { + if n == p { + return true + } + } + return false + case *ast.LabeledStmt: + addLabel(highScore, n) + } + + return true + }) + } +} diff --git a/gopls/internal/lsp/source/completion/literal.go b/gopls/internal/lsp/source/completion/literal.go new file mode 100644 index 000000000..06ed559be --- /dev/null +++ b/gopls/internal/lsp/source/completion/literal.go @@ -0,0 +1,592 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package completion + +import ( + "context" + "fmt" + "go/types" + "strings" + "unicode" + + "golang.org/x/tools/gopls/internal/lsp/protocol" + "golang.org/x/tools/gopls/internal/lsp/snippet" + "golang.org/x/tools/gopls/internal/lsp/source" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/typeparams" +) + +// literal generates composite literal, function literal, and make() +// completion items. +func (c *completer) literal(ctx context.Context, literalType types.Type, imp *importInfo) { + if !c.opts.literal { + return + } + + expType := c.inference.objType + + if c.inference.matchesVariadic(literalType) { + // Don't offer literal slice candidates for variadic arguments. + // For example, don't offer "[]interface{}{}" in "fmt.Print(<>)". + return + } + + // Avoid literal candidates if the expected type is an empty + // interface. It isn't very useful to suggest a literal candidate of + // every possible type. + if expType != nil && isEmptyInterface(expType) { + return + } + + // We handle unnamed literal completions explicitly before searching + // for candidates. Avoid named-type literal completions for + // unnamed-type expected type since that results in duplicate + // candidates. For example, in + // + // type mySlice []int + // var []int = <> + // + // don't offer "mySlice{}" since we have already added a candidate + // of "[]int{}". + if _, named := literalType.(*types.Named); named && expType != nil { + if _, named := source.Deref(expType).(*types.Named); !named { + return + } + } + + // Check if an object of type literalType would match our expected type. + cand := candidate{ + obj: c.fakeObj(literalType), + } + + switch literalType.Underlying().(type) { + // These literal types are addressable (e.g. "&[]int{}"), others are + // not (e.g. can't do "&(func(){})"). + case *types.Struct, *types.Array, *types.Slice, *types.Map: + cand.addressable = true + } + + if !c.matchingCandidate(&cand) || cand.convertTo != nil { + return + } + + var ( + qf = c.qf + sel = enclosingSelector(c.path, c.pos) + ) + + // Don't qualify the type name if we are in a selector expression + // since the package name is already present. + if sel != nil { + qf = func(_ *types.Package) string { return "" } + } + + snip, typeName := c.typeNameSnippet(literalType, qf) + + // A type name of "[]int" doesn't work very will with the matcher + // since "[" isn't a valid identifier prefix. Here we strip off the + // slice (and array) prefix yielding just "int". + matchName := typeName + switch t := literalType.(type) { + case *types.Slice: + matchName = types.TypeString(t.Elem(), qf) + case *types.Array: + matchName = types.TypeString(t.Elem(), qf) + } + + addlEdits, err := c.importEdits(imp) + if err != nil { + event.Error(ctx, "error adding import for literal candidate", err) + return + } + + // If prefix matches the type name, client may want a composite literal. + if score := c.matcher.Score(matchName); score > 0 { + if cand.hasMod(reference) { + if sel != nil { + // If we are in a selector we must place the "&" before the selector. + // For example, "foo.B<>" must complete to "&foo.Bar{}", not + // "foo.&Bar{}". + edits, err := c.editText(sel.Pos(), sel.Pos(), "&") + if err != nil { + event.Error(ctx, "error making edit for literal pointer completion", err) + return + } + addlEdits = append(addlEdits, edits...) + } else { + // Otherwise we can stick the "&" directly before the type name. + typeName = "&" + typeName + snip.PrependText("&") + } + } + + switch t := literalType.Underlying().(type) { + case *types.Struct, *types.Array, *types.Slice, *types.Map: + c.compositeLiteral(t, snip.Clone(), typeName, float64(score), addlEdits) + case *types.Signature: + // Add a literal completion for a signature type that implements + // an interface. For example, offer "http.HandlerFunc()" when + // expected type is "http.Handler". + if expType != nil && types.IsInterface(expType) { + c.basicLiteral(t, snip.Clone(), typeName, float64(score), addlEdits) + } + case *types.Basic: + // Add a literal completion for basic types that implement our + // expected interface (e.g. named string type http.Dir + // implements http.FileSystem), or are identical to our expected + // type (i.e. yielding a type conversion such as "float64()"). + if expType != nil && (types.IsInterface(expType) || types.Identical(expType, literalType)) { + c.basicLiteral(t, snip.Clone(), typeName, float64(score), addlEdits) + } + } + } + + // If prefix matches "make", client may want a "make()" + // invocation. We also include the type name to allow for more + // flexible fuzzy matching. + if score := c.matcher.Score("make." + matchName); !cand.hasMod(reference) && score > 0 { + switch literalType.Underlying().(type) { + case *types.Slice: + // The second argument to "make()" for slices is required, so default to "0". + c.makeCall(snip.Clone(), typeName, "0", float64(score), addlEdits) + case *types.Map, *types.Chan: + // Maps and channels don't require the second argument, so omit + // to keep things simple for now. + c.makeCall(snip.Clone(), typeName, "", float64(score), addlEdits) + } + } + + // If prefix matches "func", client may want a function literal. + if score := c.matcher.Score("func"); !cand.hasMod(reference) && score > 0 && (expType == nil || !types.IsInterface(expType)) { + switch t := literalType.Underlying().(type) { + case *types.Signature: + c.functionLiteral(ctx, t, float64(score)) + } + } +} + +// literalCandidateScore is the base score for literal candidates. +// Literal candidates match the expected type so they should be high +// scoring, but we want them ranked below lexical objects of the +// correct type, so scale down highScore. +const literalCandidateScore = highScore / 2 + +// functionLiteral adds a function literal completion item for the +// given signature. +func (c *completer) functionLiteral(ctx context.Context, sig *types.Signature, matchScore float64) { + snip := &snippet.Builder{} + snip.WriteText("func(") + + // First we generate names for each param and keep a seen count so + // we know if we need to uniquify param names. For example, + // "func(int)" will become "func(i int)", but "func(int, int64)" + // will become "func(i1 int, i2 int64)". + var ( + paramNames = make([]string, sig.Params().Len()) + paramNameCount = make(map[string]int) + hasTypeParams bool + ) + for i := 0; i < sig.Params().Len(); i++ { + var ( + p = sig.Params().At(i) + name = p.Name() + ) + + if tp, _ := p.Type().(*typeparams.TypeParam); tp != nil && !c.typeParamInScope(tp) { + hasTypeParams = true + } + + if name == "" { + // If the param has no name in the signature, guess a name based + // on the type. Use an empty qualifier to ignore the package. + // For example, we want to name "http.Request" "r", not "hr". + typeName, err := source.FormatVarType(ctx, c.snapshot, c.pkg, p, + func(p *types.Package) string { return "" }, + func(source.PackageName, source.ImportPath, source.PackagePath) string { return "" }) + if err != nil { + // In general, the only error we should encounter while formatting is + // context cancellation. + if ctx.Err() == nil { + event.Error(ctx, "formatting var type", err) + } + return + } + name = abbreviateTypeName(typeName) + } + paramNames[i] = name + if name != "_" { + paramNameCount[name]++ + } + } + + for n, c := range paramNameCount { + // Any names we saw more than once will need a unique suffix added + // on. Reset the count to 1 to act as the suffix for the first + // name. + if c >= 2 { + paramNameCount[n] = 1 + } else { + delete(paramNameCount, n) + } + } + + for i := 0; i < sig.Params().Len(); i++ { + if hasTypeParams && !c.opts.placeholders { + // If there are type params in the args then the user must + // choose the concrete types. If placeholders are disabled just + // drop them between the parens and let them fill things in. + snip.WritePlaceholder(nil) + break + } + + if i > 0 { + snip.WriteText(", ") + } + + var ( + p = sig.Params().At(i) + name = paramNames[i] + ) + + // Uniquify names by adding on an incrementing numeric suffix. + if idx, found := paramNameCount[name]; found { + paramNameCount[name]++ + name = fmt.Sprintf("%s%d", name, idx) + } + + if name != p.Name() && c.opts.placeholders { + // If we didn't use the signature's param name verbatim then we + // may have chosen a poor name. Give the user a placeholder so + // they can easily fix the name. + snip.WritePlaceholder(func(b *snippet.Builder) { + b.WriteText(name) + }) + } else { + snip.WriteText(name) + } + + // If the following param's type is identical to this one, omit + // this param's type string. For example, emit "i, j int" instead + // of "i int, j int". + if i == sig.Params().Len()-1 || !types.Identical(p.Type(), sig.Params().At(i+1).Type()) { + snip.WriteText(" ") + typeStr, err := source.FormatVarType(ctx, c.snapshot, c.pkg, p, c.qf, c.mq) + if err != nil { + // In general, the only error we should encounter while formatting is + // context cancellation. + if ctx.Err() == nil { + event.Error(ctx, "formatting var type", err) + } + return + } + if sig.Variadic() && i == sig.Params().Len()-1 { + typeStr = strings.Replace(typeStr, "[]", "...", 1) + } + + if tp, _ := p.Type().(*typeparams.TypeParam); tp != nil && !c.typeParamInScope(tp) { + snip.WritePlaceholder(func(snip *snippet.Builder) { + snip.WriteText(typeStr) + }) + } else { + snip.WriteText(typeStr) + } + } + } + snip.WriteText(")") + + results := sig.Results() + if results.Len() > 0 { + snip.WriteText(" ") + } + + resultsNeedParens := results.Len() > 1 || + results.Len() == 1 && results.At(0).Name() != "" + + var resultHasTypeParams bool + for i := 0; i < results.Len(); i++ { + if tp, _ := results.At(i).Type().(*typeparams.TypeParam); tp != nil && !c.typeParamInScope(tp) { + resultHasTypeParams = true + } + } + + if resultsNeedParens { + snip.WriteText("(") + } + for i := 0; i < results.Len(); i++ { + if resultHasTypeParams && !c.opts.placeholders { + // Leave an empty tabstop if placeholders are disabled and there + // are type args that need specificying. + snip.WritePlaceholder(nil) + break + } + + if i > 0 { + snip.WriteText(", ") + } + r := results.At(i) + if name := r.Name(); name != "" { + snip.WriteText(name + " ") + } + + text, err := source.FormatVarType(ctx, c.snapshot, c.pkg, r, c.qf, c.mq) + if err != nil { + // In general, the only error we should encounter while formatting is + // context cancellation. + if ctx.Err() == nil { + event.Error(ctx, "formatting var type", err) + } + return + } + if tp, _ := r.Type().(*typeparams.TypeParam); tp != nil && !c.typeParamInScope(tp) { + snip.WritePlaceholder(func(snip *snippet.Builder) { + snip.WriteText(text) + }) + } else { + snip.WriteText(text) + } + } + if resultsNeedParens { + snip.WriteText(")") + } + + snip.WriteText(" {") + snip.WriteFinalTabstop() + snip.WriteText("}") + + c.items = append(c.items, CompletionItem{ + Label: "func(...) {}", + Score: matchScore * literalCandidateScore, + Kind: protocol.VariableCompletion, + snippet: snip, + }) +} + +// conventionalAcronyms contains conventional acronyms for type names +// in lower case. For example, "ctx" for "context" and "err" for "error". +var conventionalAcronyms = map[string]string{ + "context": "ctx", + "error": "err", + "tx": "tx", + "responsewriter": "w", +} + +// abbreviateTypeName abbreviates type names into acronyms. For +// example, "fooBar" is abbreviated "fb". Care is taken to ignore +// non-identifier runes. For example, "[]int" becomes "i", and +// "struct { i int }" becomes "s". +func abbreviateTypeName(s string) string { + var ( + b strings.Builder + useNextUpper bool + ) + + // Trim off leading non-letters. We trim everything between "[" and + // "]" to handle array types like "[someConst]int". + var inBracket bool + s = strings.TrimFunc(s, func(r rune) bool { + if inBracket { + inBracket = r != ']' + return true + } + + if r == '[' { + inBracket = true + } + + return !unicode.IsLetter(r) + }) + + if acr, ok := conventionalAcronyms[strings.ToLower(s)]; ok { + return acr + } + + for i, r := range s { + // Stop if we encounter a non-identifier rune. + if !unicode.IsLetter(r) && !unicode.IsNumber(r) { + break + } + + if i == 0 { + b.WriteRune(unicode.ToLower(r)) + } + + if unicode.IsUpper(r) { + if useNextUpper { + b.WriteRune(unicode.ToLower(r)) + useNextUpper = false + } + } else { + useNextUpper = true + } + } + + return b.String() +} + +// compositeLiteral adds a composite literal completion item for the given typeName. +func (c *completer) compositeLiteral(T types.Type, snip *snippet.Builder, typeName string, matchScore float64, edits []protocol.TextEdit) { + snip.WriteText("{") + // Don't put the tab stop inside the composite literal curlies "{}" + // for structs that have no accessible fields. + if strct, ok := T.(*types.Struct); !ok || fieldsAccessible(strct, c.pkg.GetTypes()) { + snip.WriteFinalTabstop() + } + snip.WriteText("}") + + nonSnippet := typeName + "{}" + + c.items = append(c.items, CompletionItem{ + Label: nonSnippet, + InsertText: nonSnippet, + Score: matchScore * literalCandidateScore, + Kind: protocol.VariableCompletion, + AdditionalTextEdits: edits, + snippet: snip, + }) +} + +// basicLiteral adds a literal completion item for the given basic +// type name typeName. +func (c *completer) basicLiteral(T types.Type, snip *snippet.Builder, typeName string, matchScore float64, edits []protocol.TextEdit) { + // Never give type conversions like "untyped int()". + if isUntyped(T) { + return + } + + snip.WriteText("(") + snip.WriteFinalTabstop() + snip.WriteText(")") + + nonSnippet := typeName + "()" + + c.items = append(c.items, CompletionItem{ + Label: nonSnippet, + InsertText: nonSnippet, + Detail: T.String(), + Score: matchScore * literalCandidateScore, + Kind: protocol.VariableCompletion, + AdditionalTextEdits: edits, + snippet: snip, + }) +} + +// makeCall adds a completion item for a "make()" call given a specific type. +func (c *completer) makeCall(snip *snippet.Builder, typeName string, secondArg string, matchScore float64, edits []protocol.TextEdit) { + // Keep it simple and don't add any placeholders for optional "make()" arguments. + + snip.PrependText("make(") + if secondArg != "" { + snip.WriteText(", ") + snip.WritePlaceholder(func(b *snippet.Builder) { + if c.opts.placeholders { + b.WriteText(secondArg) + } + }) + } + snip.WriteText(")") + + var nonSnippet strings.Builder + nonSnippet.WriteString("make(" + typeName) + if secondArg != "" { + nonSnippet.WriteString(", ") + nonSnippet.WriteString(secondArg) + } + nonSnippet.WriteByte(')') + + c.items = append(c.items, CompletionItem{ + Label: nonSnippet.String(), + InsertText: nonSnippet.String(), + Score: matchScore * literalCandidateScore, + Kind: protocol.FunctionCompletion, + AdditionalTextEdits: edits, + snippet: snip, + }) +} + +// Create a snippet for a type name where type params become placeholders. +func (c *completer) typeNameSnippet(literalType types.Type, qf types.Qualifier) (*snippet.Builder, string) { + var ( + snip snippet.Builder + typeName string + named, _ = literalType.(*types.Named) + ) + + if named != nil && named.Obj() != nil && typeparams.ForNamed(named).Len() > 0 && !c.fullyInstantiated(named) { + // We are not "fully instantiated" meaning we have type params that must be specified. + if pkg := qf(named.Obj().Pkg()); pkg != "" { + typeName = pkg + "." + } + + // We do this to get "someType" instead of "someType[T]". + typeName += named.Obj().Name() + snip.WriteText(typeName + "[") + + if c.opts.placeholders { + for i := 0; i < typeparams.ForNamed(named).Len(); i++ { + if i > 0 { + snip.WriteText(", ") + } + snip.WritePlaceholder(func(snip *snippet.Builder) { + snip.WriteText(types.TypeString(typeparams.ForNamed(named).At(i), qf)) + }) + } + } else { + snip.WritePlaceholder(nil) + } + snip.WriteText("]") + typeName += "[...]" + } else { + // We don't have unspecified type params so use default type formatting. + typeName = types.TypeString(literalType, qf) + snip.WriteText(typeName) + } + + return &snip, typeName +} + +// fullyInstantiated reports whether all of t's type params have +// specified type args. +func (c *completer) fullyInstantiated(t *types.Named) bool { + tps := typeparams.ForNamed(t) + tas := typeparams.NamedTypeArgs(t) + + if tps.Len() != tas.Len() { + return false + } + + for i := 0; i < tas.Len(); i++ { + switch ta := tas.At(i).(type) { + case *typeparams.TypeParam: + // A *TypeParam only counts as specified if it is currently in + // scope (i.e. we are in a generic definition). + if !c.typeParamInScope(ta) { + return false + } + case *types.Named: + if !c.fullyInstantiated(ta) { + return false + } + } + } + return true +} + +// typeParamInScope returns whether tp's object is in scope at c.pos. +// This tells you whether you are in a generic definition and can +// assume tp has been specified. +func (c *completer) typeParamInScope(tp *typeparams.TypeParam) bool { + obj := tp.Obj() + if obj == nil { + return false + } + + scope := c.innermostScope() + if scope == nil { + return false + } + + _, foundObj := scope.LookupParent(obj.Name(), c.pos) + return obj == foundObj +} diff --git a/gopls/internal/lsp/source/completion/package.go b/gopls/internal/lsp/source/completion/package.go new file mode 100644 index 000000000..f3bc30688 --- /dev/null +++ b/gopls/internal/lsp/source/completion/package.go @@ -0,0 +1,351 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package completion + +import ( + "bytes" + "context" + "errors" + "fmt" + "go/ast" + "go/parser" + "go/scanner" + "go/token" + "go/types" + "path/filepath" + "strings" + "unicode" + + "golang.org/x/tools/gopls/internal/lsp/protocol" + "golang.org/x/tools/gopls/internal/lsp/safetoken" + "golang.org/x/tools/gopls/internal/lsp/source" + "golang.org/x/tools/gopls/internal/span" + "golang.org/x/tools/internal/fuzzy" +) + +// packageClauseCompletions offers completions for a package declaration when +// one is not present in the given file. +func packageClauseCompletions(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, position protocol.Position) ([]CompletionItem, *Selection, error) { + // We know that the AST for this file will be empty due to the missing + // package declaration, but parse it anyway to get a mapper. + // TODO(adonovan): opt: there's no need to parse just to get a mapper. + pgf, err := snapshot.ParseGo(ctx, fh, source.ParseFull) + if err != nil { + return nil, nil, err + } + + offset, err := pgf.Mapper.PositionOffset(position) + if err != nil { + return nil, nil, err + } + surrounding, err := packageCompletionSurrounding(pgf, offset) + if err != nil { + return nil, nil, fmt.Errorf("invalid position for package completion: %w", err) + } + + packageSuggestions, err := packageSuggestions(ctx, snapshot, fh.URI(), "") + if err != nil { + return nil, nil, err + } + + var items []CompletionItem + for _, pkg := range packageSuggestions { + insertText := fmt.Sprintf("package %s", pkg.name) + items = append(items, CompletionItem{ + Label: insertText, + Kind: protocol.ModuleCompletion, + InsertText: insertText, + Score: pkg.score, + }) + } + + return items, surrounding, nil +} + +// packageCompletionSurrounding returns surrounding for package completion if a +// package completions can be suggested at a given cursor offset. A valid location +// for package completion is above any declarations or import statements. +func packageCompletionSurrounding(pgf *source.ParsedGoFile, offset int) (*Selection, error) { + m := pgf.Mapper + // If the file lacks a package declaration, the parser will return an empty + // AST. As a work-around, try to parse an expression from the file contents. + fset := token.NewFileSet() + expr, _ := parser.ParseExprFrom(fset, m.URI.Filename(), pgf.Src, parser.Mode(0)) + if expr == nil { + return nil, fmt.Errorf("unparseable file (%s)", m.URI) + } + tok := fset.File(expr.Pos()) + cursor := tok.Pos(offset) + + // If we were able to parse out an identifier as the first expression from + // the file, it may be the beginning of a package declaration ("pack "). + // We can offer package completions if the cursor is in the identifier. + if name, ok := expr.(*ast.Ident); ok { + if cursor >= name.Pos() && cursor <= name.End() { + if !strings.HasPrefix(PACKAGE, name.Name) { + return nil, fmt.Errorf("cursor in non-matching ident") + } + return &Selection{ + content: name.Name, + cursor: cursor, + tokFile: tok, + start: name.Pos(), + end: name.End(), + mapper: m, + }, nil + } + } + + // The file is invalid, but it contains an expression that we were able to + // parse. We will use this expression to construct the cursor's + // "surrounding". + + // First, consider the possibility that we have a valid "package" keyword + // with an empty package name ("package "). "package" is parsed as an + // *ast.BadDecl since it is a keyword. This logic would allow "package" to + // appear on any line of the file as long as it's the first code expression + // in the file. + lines := strings.Split(string(pgf.Src), "\n") + cursorLine := tok.Line(cursor) + if cursorLine <= 0 || cursorLine > len(lines) { + return nil, fmt.Errorf("invalid line number") + } + if safetoken.StartPosition(fset, expr.Pos()).Line == cursorLine { + words := strings.Fields(lines[cursorLine-1]) + if len(words) > 0 && words[0] == PACKAGE { + content := PACKAGE + // Account for spaces if there are any. + if len(words) > 1 { + content += " " + } + + start := expr.Pos() + end := token.Pos(int(expr.Pos()) + len(content) + 1) + // We have verified that we have a valid 'package' keyword as our + // first expression. Ensure that cursor is in this keyword or + // otherwise fallback to the general case. + if cursor >= start && cursor <= end { + return &Selection{ + content: content, + cursor: cursor, + tokFile: tok, + start: start, + end: end, + mapper: m, + }, nil + } + } + } + + // If the cursor is after the start of the expression, no package + // declaration will be valid. + if cursor > expr.Pos() { + return nil, fmt.Errorf("cursor after expression") + } + + // If the cursor is in a comment, don't offer any completions. + if cursorInComment(tok, cursor, m.Content) { + return nil, fmt.Errorf("cursor in comment") + } + + // The surrounding range in this case is the cursor. + return &Selection{ + content: "", + tokFile: tok, + start: cursor, + end: cursor, + cursor: cursor, + mapper: m, + }, nil +} + +func cursorInComment(file *token.File, cursor token.Pos, src []byte) bool { + var s scanner.Scanner + s.Init(file, src, func(_ token.Position, _ string) {}, scanner.ScanComments) + for { + pos, tok, lit := s.Scan() + if pos <= cursor && cursor <= token.Pos(int(pos)+len(lit)) { + return tok == token.COMMENT + } + if tok == token.EOF { + break + } + } + return false +} + +// packageNameCompletions returns name completions for a package clause using +// the current name as prefix. +func (c *completer) packageNameCompletions(ctx context.Context, fileURI span.URI, name *ast.Ident) error { + cursor := int(c.pos - name.NamePos) + if cursor < 0 || cursor > len(name.Name) { + return errors.New("cursor is not in package name identifier") + } + + c.completionContext.packageCompletion = true + + prefix := name.Name[:cursor] + packageSuggestions, err := packageSuggestions(ctx, c.snapshot, fileURI, prefix) + if err != nil { + return err + } + + for _, pkg := range packageSuggestions { + c.deepState.enqueue(pkg) + } + return nil +} + +// packageSuggestions returns a list of packages from workspace packages that +// have the given prefix and are used in the same directory as the given +// file. This also includes test packages for these packages (<pkg>_test) and +// the directory name itself. +func packageSuggestions(ctx context.Context, snapshot source.Snapshot, fileURI span.URI, prefix string) (packages []candidate, err error) { + active, err := snapshot.ActiveMetadata(ctx) + if err != nil { + return nil, err + } + + toCandidate := func(name string, score float64) candidate { + obj := types.NewPkgName(0, nil, name, types.NewPackage("", name)) + return candidate{obj: obj, name: name, detail: name, score: score} + } + + matcher := fuzzy.NewMatcher(prefix) + + // Always try to suggest a main package + defer func() { + if score := float64(matcher.Score("main")); score > 0 { + packages = append(packages, toCandidate("main", score*lowScore)) + } + }() + + dirPath := filepath.Dir(fileURI.Filename()) + dirName := filepath.Base(dirPath) + if !isValidDirName(dirName) { + return packages, nil + } + pkgName := convertDirNameToPkgName(dirName) + + seenPkgs := make(map[source.PackageName]struct{}) + + // The `go` command by default only allows one package per directory but we + // support multiple package suggestions since gopls is build system agnostic. + for _, m := range active { + if m.Name == "main" || m.Name == "" { + continue + } + if _, ok := seenPkgs[m.Name]; ok { + continue + } + + // Only add packages that are previously used in the current directory. + var relevantPkg bool + for _, uri := range m.CompiledGoFiles { + if filepath.Dir(uri.Filename()) == dirPath { + relevantPkg = true + break + } + } + if !relevantPkg { + continue + } + + // Add a found package used in current directory as a high relevance + // suggestion and the test package for it as a medium relevance + // suggestion. + if score := float64(matcher.Score(string(m.Name))); score > 0 { + packages = append(packages, toCandidate(string(m.Name), score*highScore)) + } + seenPkgs[m.Name] = struct{}{} + + testPkgName := m.Name + "_test" + if _, ok := seenPkgs[testPkgName]; ok || strings.HasSuffix(string(m.Name), "_test") { + continue + } + if score := float64(matcher.Score(string(testPkgName))); score > 0 { + packages = append(packages, toCandidate(string(testPkgName), score*stdScore)) + } + seenPkgs[testPkgName] = struct{}{} + } + + // Add current directory name as a low relevance suggestion. + if _, ok := seenPkgs[pkgName]; !ok { + if score := float64(matcher.Score(string(pkgName))); score > 0 { + packages = append(packages, toCandidate(string(pkgName), score*lowScore)) + } + + testPkgName := pkgName + "_test" + if score := float64(matcher.Score(string(testPkgName))); score > 0 { + packages = append(packages, toCandidate(string(testPkgName), score*lowScore)) + } + } + + return packages, nil +} + +// isValidDirName checks whether the passed directory name can be used in +// a package path. Requirements for a package path can be found here: +// https://golang.org/ref/mod#go-mod-file-ident. +func isValidDirName(dirName string) bool { + if dirName == "" { + return false + } + + for i, ch := range dirName { + if isLetter(ch) || isDigit(ch) { + continue + } + if i == 0 { + // Directory name can start only with '_'. '.' is not allowed in module paths. + // '-' and '~' are not allowed because elements of package paths must be + // safe command-line arguments. + if ch == '_' { + continue + } + } else { + // Modules path elements can't end with '.' + if isAllowedPunctuation(ch) && (i != len(dirName)-1 || ch != '.') { + continue + } + } + + return false + } + return true +} + +// convertDirNameToPkgName converts a valid directory name to a valid package name. +// It leaves only letters and digits. All letters are mapped to lower case. +func convertDirNameToPkgName(dirName string) source.PackageName { + var buf bytes.Buffer + for _, ch := range dirName { + switch { + case isLetter(ch): + buf.WriteRune(unicode.ToLower(ch)) + + case buf.Len() != 0 && isDigit(ch): + buf.WriteRune(ch) + } + } + return source.PackageName(buf.String()) +} + +// isLetter and isDigit allow only ASCII characters because +// "Each path element is a non-empty string made of up ASCII letters, +// ASCII digits, and limited ASCII punctuation" +// (see https://golang.org/ref/mod#go-mod-file-ident). + +func isLetter(ch rune) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' +} + +func isDigit(ch rune) bool { + return '0' <= ch && ch <= '9' +} + +func isAllowedPunctuation(ch rune) bool { + return ch == '_' || ch == '-' || ch == '~' || ch == '.' +} diff --git a/gopls/internal/lsp/source/completion/package_test.go b/gopls/internal/lsp/source/completion/package_test.go new file mode 100644 index 000000000..614359fa5 --- /dev/null +++ b/gopls/internal/lsp/source/completion/package_test.go @@ -0,0 +1,81 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package completion + +import ( + "testing" + + "golang.org/x/tools/gopls/internal/lsp/source" +) + +func TestIsValidDirName(t *testing.T) { + tests := []struct { + dirName string + valid bool + }{ + {dirName: "", valid: false}, + // + {dirName: "a", valid: true}, + {dirName: "abcdef", valid: true}, + {dirName: "AbCdEf", valid: true}, + // + {dirName: "1a35", valid: true}, + {dirName: "a16", valid: true}, + // + {dirName: "_a", valid: true}, + {dirName: "a_", valid: true}, + // + {dirName: "~a", valid: false}, + {dirName: "a~", valid: true}, + // + {dirName: "-a", valid: false}, + {dirName: "a-", valid: true}, + // + {dirName: ".a", valid: false}, + {dirName: "a.", valid: false}, + // + {dirName: "a~_b--c.-e", valid: true}, + {dirName: "~a~_b--c.-e", valid: false}, + {dirName: "a~_b--c.-e--~", valid: true}, + {dirName: "a~_b--2134dc42.-e6--~", valid: true}, + {dirName: "abc`def", valid: false}, + {dirName: "тест", valid: false}, + {dirName: "你好", valid: false}, + } + for _, tt := range tests { + valid := isValidDirName(tt.dirName) + if tt.valid != valid { + t.Errorf("%s: expected %v, got %v", tt.dirName, tt.valid, valid) + } + } +} + +func TestConvertDirNameToPkgName(t *testing.T) { + tests := []struct { + dirName string + pkgName source.PackageName + }{ + {dirName: "a", pkgName: "a"}, + {dirName: "abcdef", pkgName: "abcdef"}, + {dirName: "AbCdEf", pkgName: "abcdef"}, + {dirName: "1a35", pkgName: "a35"}, + {dirName: "14a35", pkgName: "a35"}, + {dirName: "a16", pkgName: "a16"}, + {dirName: "_a", pkgName: "a"}, + {dirName: "a_", pkgName: "a"}, + {dirName: "a~", pkgName: "a"}, + {dirName: "a-", pkgName: "a"}, + {dirName: "a~_b--c.-e", pkgName: "abce"}, + {dirName: "a~_b--c.-e--~", pkgName: "abce"}, + {dirName: "a~_b--2134dc42.-e6--~", pkgName: "ab2134dc42e6"}, + } + for _, tt := range tests { + pkgName := convertDirNameToPkgName(tt.dirName) + if tt.pkgName != pkgName { + t.Errorf("%s: expected %v, got %v", tt.dirName, tt.pkgName, pkgName) + continue + } + } +} diff --git a/gopls/internal/lsp/source/completion/postfix_snippets.go b/gopls/internal/lsp/source/completion/postfix_snippets.go new file mode 100644 index 000000000..0737ec246 --- /dev/null +++ b/gopls/internal/lsp/source/completion/postfix_snippets.go @@ -0,0 +1,471 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package completion + +import ( + "context" + "fmt" + "go/ast" + "go/token" + "go/types" + "log" + "reflect" + "strings" + "sync" + "text/template" + + "golang.org/x/tools/gopls/internal/lsp/protocol" + "golang.org/x/tools/gopls/internal/lsp/snippet" + "golang.org/x/tools/gopls/internal/lsp/source" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/imports" +) + +// Postfix snippets are artificial methods that allow the user to +// compose common operations in an "argument oriented" fashion. For +// example, instead of "sort.Slice(someSlice, ...)" a user can expand +// "someSlice.sort!". + +// postfixTmpl represents a postfix snippet completion candidate. +type postfixTmpl struct { + // label is the completion candidate's label presented to the user. + label string + + // details is passed along to the client as the candidate's details. + details string + + // body is the template text. See postfixTmplArgs for details on the + // facilities available to the template. + body string + + tmpl *template.Template +} + +// postfixTmplArgs are the template execution arguments available to +// the postfix snippet templates. +type postfixTmplArgs struct { + // StmtOK is true if it is valid to replace the selector with a + // statement. For example: + // + // func foo() { + // bar.sort! // statement okay + // + // someMethod(bar.sort!) // statement not okay + // } + StmtOK bool + + // X is the textual SelectorExpr.X. For example, when completing + // "foo.bar.print!", "X" is "foo.bar". + X string + + // Obj is the types.Object of SelectorExpr.X, if any. + Obj types.Object + + // Type is the type of "foo.bar" in "foo.bar.print!". + Type types.Type + + scope *types.Scope + snip snippet.Builder + importIfNeeded func(pkgPath string, scope *types.Scope) (name string, edits []protocol.TextEdit, err error) + edits []protocol.TextEdit + qf types.Qualifier + varNames map[string]bool +} + +var postfixTmpls = []postfixTmpl{{ + label: "sort", + details: "sort.Slice()", + body: `{{if and (eq .Kind "slice") .StmtOK -}} +{{.Import "sort"}}.Slice({{.X}}, func({{.VarName nil "i"}}, {{.VarName nil "j"}} int) bool { + {{.Cursor}} +}) +{{- end}}`, +}, { + label: "last", + details: "s[len(s)-1]", + body: `{{if and (eq .Kind "slice") .Obj -}} +{{.X}}[len({{.X}})-1] +{{- end}}`, +}, { + label: "reverse", + details: "reverse slice", + body: `{{if and (eq .Kind "slice") .StmtOK -}} +{{$i := .VarName nil "i"}}{{$j := .VarName nil "j" -}} +for {{$i}}, {{$j}} := 0, len({{.X}})-1; {{$i}} < {{$j}}; {{$i}}, {{$j}} = {{$i}}+1, {{$j}}-1 { + {{.X}}[{{$i}}], {{.X}}[{{$j}}] = {{.X}}[{{$j}}], {{.X}}[{{$i}}] +} +{{end}}`, +}, { + label: "range", + details: "range over slice", + body: `{{if and (eq .Kind "slice") .StmtOK -}} +for {{.VarName nil "i"}}, {{.VarName .ElemType "v"}} := range {{.X}} { + {{.Cursor}} +} +{{- end}}`, +}, { + label: "append", + details: "append and re-assign slice", + body: `{{if and (eq .Kind "slice") .StmtOK .Obj -}} +{{.X}} = append({{.X}}, {{.Cursor}}) +{{- end}}`, +}, { + label: "append", + details: "append to slice", + body: `{{if and (eq .Kind "slice") (not .StmtOK) -}} +append({{.X}}, {{.Cursor}}) +{{- end}}`, +}, { + label: "copy", + details: "duplicate slice", + body: `{{if and (eq .Kind "slice") .StmtOK .Obj -}} +{{$v := (.VarName nil (printf "%sCopy" .X))}}{{$v}} := make([]{{.TypeName .ElemType}}, len({{.X}})) +copy({{$v}}, {{.X}}) +{{end}}`, +}, { + label: "range", + details: "range over map", + body: `{{if and (eq .Kind "map") .StmtOK -}} +for {{.VarName .KeyType "k"}}, {{.VarName .ElemType "v"}} := range {{.X}} { + {{.Cursor}} +} +{{- end}}`, +}, { + label: "clear", + details: "clear map contents", + body: `{{if and (eq .Kind "map") .StmtOK -}} +{{$k := (.VarName .KeyType "k")}}for {{$k}} := range {{.X}} { + delete({{.X}}, {{$k}}) +} +{{end}}`, +}, { + label: "keys", + details: "create slice of keys", + body: `{{if and (eq .Kind "map") .StmtOK -}} +{{$keysVar := (.VarName nil "keys")}}{{$keysVar}} := make([]{{.TypeName .KeyType}}, 0, len({{.X}})) +{{$k := (.VarName .KeyType "k")}}for {{$k}} := range {{.X}} { + {{$keysVar}} = append({{$keysVar}}, {{$k}}) +} +{{end}}`, +}, { + label: "range", + details: "range over channel", + body: `{{if and (eq .Kind "chan") .StmtOK -}} +for {{.VarName .ElemType "e"}} := range {{.X}} { + {{.Cursor}} +} +{{- end}}`, +}, { + label: "var", + details: "assign to variables", + body: `{{if and (eq .Kind "tuple") .StmtOK -}} +{{$a := .}}{{range $i, $v := .Tuple}}{{if $i}}, {{end}}{{$a.VarName $v.Type $v.Name}}{{end}} := {{.X}} +{{- end}}`, +}, { + label: "var", + details: "assign to variable", + body: `{{if and (ne .Kind "tuple") .StmtOK -}} +{{.VarName .Type ""}} := {{.X}} +{{- end}}`, +}, { + label: "print", + details: "print to stdout", + body: `{{if and (ne .Kind "tuple") .StmtOK -}} +{{.Import "fmt"}}.Printf("{{.EscapeQuotes .X}}: %v\n", {{.X}}) +{{- end}}`, +}, { + label: "print", + details: "print to stdout", + body: `{{if and (eq .Kind "tuple") .StmtOK -}} +{{.Import "fmt"}}.Println({{.X}}) +{{- end}}`, +}, { + label: "split", + details: "split string", + body: `{{if (eq (.TypeName .Type) "string") -}} +{{.Import "strings"}}.Split({{.X}}, "{{.Cursor}}") +{{- end}}`, +}, { + label: "join", + details: "join string slice", + body: `{{if and (eq .Kind "slice") (eq (.TypeName .ElemType) "string") -}} +{{.Import "strings"}}.Join({{.X}}, "{{.Cursor}}") +{{- end}}`, +}} + +// Cursor indicates where the client's cursor should end up after the +// snippet is done. +func (a *postfixTmplArgs) Cursor() string { + a.snip.WriteFinalTabstop() + return "" +} + +// Import makes sure the package corresponding to path is imported, +// returning the identifier to use to refer to the package. +func (a *postfixTmplArgs) Import(path string) (string, error) { + name, edits, err := a.importIfNeeded(path, a.scope) + if err != nil { + return "", fmt.Errorf("couldn't import %q: %w", path, err) + } + a.edits = append(a.edits, edits...) + return name, nil +} + +func (a *postfixTmplArgs) EscapeQuotes(v string) string { + return strings.ReplaceAll(v, `"`, `\\"`) +} + +// ElemType returns the Elem() type of xType, if applicable. +func (a *postfixTmplArgs) ElemType() types.Type { + if e, _ := a.Type.(interface{ Elem() types.Type }); e != nil { + return e.Elem() + } + return nil +} + +// Kind returns the underlying kind of type, e.g. "slice", "struct", +// etc. +func (a *postfixTmplArgs) Kind() string { + t := reflect.TypeOf(a.Type.Underlying()) + return strings.ToLower(strings.TrimPrefix(t.String(), "*types.")) +} + +// KeyType returns the type of X's key. KeyType panics if X is not a +// map. +func (a *postfixTmplArgs) KeyType() types.Type { + return a.Type.Underlying().(*types.Map).Key() +} + +// Tuple returns the tuple result vars if X is a call expression. +func (a *postfixTmplArgs) Tuple() []*types.Var { + tuple, _ := a.Type.(*types.Tuple) + if tuple == nil { + return nil + } + + typs := make([]*types.Var, 0, tuple.Len()) + for i := 0; i < tuple.Len(); i++ { + typs = append(typs, tuple.At(i)) + } + return typs +} + +// TypeName returns the textual representation of type t. +func (a *postfixTmplArgs) TypeName(t types.Type) (string, error) { + if t == nil || t == types.Typ[types.Invalid] { + return "", fmt.Errorf("invalid type: %v", t) + } + return types.TypeString(t, a.qf), nil +} + +// VarName returns a suitable variable name for the type t. If t +// implements the error interface, "err" is used. If t is not a named +// type then nonNamedDefault is used. Otherwise a name is made by +// abbreviating the type name. If the resultant name is already in +// scope, an integer is appended to make a unique name. +func (a *postfixTmplArgs) VarName(t types.Type, nonNamedDefault string) string { + if t == nil { + t = types.Typ[types.Invalid] + } + + var name string + // go/types predicates are undefined on types.Typ[types.Invalid]. + if !types.Identical(t, types.Typ[types.Invalid]) && types.Implements(t, errorIntf) { + name = "err" + } else if _, isNamed := source.Deref(t).(*types.Named); !isNamed { + name = nonNamedDefault + } + + if name == "" { + name = types.TypeString(t, func(p *types.Package) string { + return "" + }) + name = abbreviateTypeName(name) + } + + if dot := strings.LastIndex(name, "."); dot > -1 { + name = name[dot+1:] + } + + uniqueName := name + for i := 2; ; i++ { + if s, _ := a.scope.LookupParent(uniqueName, token.NoPos); s == nil && !a.varNames[uniqueName] { + break + } + uniqueName = fmt.Sprintf("%s%d", name, i) + } + + a.varNames[uniqueName] = true + + return uniqueName +} + +func (c *completer) addPostfixSnippetCandidates(ctx context.Context, sel *ast.SelectorExpr) { + if !c.opts.postfix { + return + } + + initPostfixRules() + + if sel == nil || sel.Sel == nil { + return + } + + selType := c.pkg.GetTypesInfo().TypeOf(sel.X) + if selType == nil { + return + } + + // Skip empty tuples since there is no value to operate on. + if tuple, ok := selType.Underlying().(*types.Tuple); ok && tuple == nil { + return + } + + tokFile := c.pkg.FileSet().File(c.pos) + + // Only replace sel with a statement if sel is already a statement. + var stmtOK bool + for i, n := range c.path { + if n == sel && i < len(c.path)-1 { + switch p := c.path[i+1].(type) { + case *ast.ExprStmt: + stmtOK = true + case *ast.AssignStmt: + // In cases like: + // + // foo.<> + // bar = 123 + // + // detect that "foo." makes up the entire statement since the + // apparent selector spans lines. + stmtOK = tokFile.Line(c.pos) < tokFile.Line(p.TokPos) + } + break + } + } + + scope := c.pkg.GetTypes().Scope().Innermost(c.pos) + if scope == nil { + return + } + + // afterDot is the position after selector dot, e.g. "|" in + // "foo.|print". + afterDot := sel.Sel.Pos() + + // We must detect dangling selectors such as: + // + // foo.<> + // bar + // + // and adjust afterDot so that we don't mistakenly delete the + // newline thinking "bar" is part of our selector. + if startLine := tokFile.Line(sel.Pos()); startLine != tokFile.Line(afterDot) { + if tokFile.Line(c.pos) != startLine { + return + } + afterDot = c.pos + } + + for _, rule := range postfixTmpls { + // When completing foo.print<>, "print" is naturally overwritten, + // but we need to also remove "foo." so the snippet has a clean + // slate. + edits, err := c.editText(sel.Pos(), afterDot, "") + if err != nil { + event.Error(ctx, "error calculating postfix edits", err) + return + } + + tmplArgs := postfixTmplArgs{ + X: source.FormatNode(c.pkg.FileSet(), sel.X), + StmtOK: stmtOK, + Obj: exprObj(c.pkg.GetTypesInfo(), sel.X), + Type: selType, + qf: c.qf, + importIfNeeded: c.importIfNeeded, + scope: scope, + varNames: make(map[string]bool), + } + + // Feed the template straight into the snippet builder. This + // allows templates to build snippets as they are executed. + err = rule.tmpl.Execute(&tmplArgs.snip, &tmplArgs) + if err != nil { + event.Error(ctx, "error executing postfix template", err) + continue + } + + if strings.TrimSpace(tmplArgs.snip.String()) == "" { + continue + } + + score := c.matcher.Score(rule.label) + if score <= 0 { + continue + } + + c.items = append(c.items, CompletionItem{ + Label: rule.label + "!", + Detail: rule.details, + Score: float64(score) * 0.01, + Kind: protocol.SnippetCompletion, + snippet: &tmplArgs.snip, + AdditionalTextEdits: append(edits, tmplArgs.edits...), + }) + } +} + +var postfixRulesOnce sync.Once + +func initPostfixRules() { + postfixRulesOnce.Do(func() { + var idx int + for _, rule := range postfixTmpls { + var err error + rule.tmpl, err = template.New("postfix_snippet").Parse(rule.body) + if err != nil { + log.Panicf("error parsing postfix snippet template: %v", err) + } + postfixTmpls[idx] = rule + idx++ + } + postfixTmpls = postfixTmpls[:idx] + }) +} + +// importIfNeeded returns the package identifier and any necessary +// edits to import package pkgPath. +func (c *completer) importIfNeeded(pkgPath string, scope *types.Scope) (string, []protocol.TextEdit, error) { + defaultName := imports.ImportPathToAssumedName(pkgPath) + + // Check if file already imports pkgPath. + for _, s := range c.file.Imports { + // TODO(adonovan): what if pkgPath has a vendor/ suffix? + // This may be the cause of go.dev/issue/56291. + if source.UnquoteImportPath(s) == source.ImportPath(pkgPath) { + if s.Name == nil { + return defaultName, nil, nil + } + if s.Name.Name != "_" { + return s.Name.Name, nil, nil + } + } + } + + // Give up if the package's name is already in use by another object. + if _, obj := scope.LookupParent(defaultName, token.NoPos); obj != nil { + return "", nil, fmt.Errorf("import name %q of %q already in use", defaultName, pkgPath) + } + + edits, err := c.importEdits(&importInfo{ + importPath: pkgPath, + }) + if err != nil { + return "", nil, err + } + + return defaultName, edits, nil +} diff --git a/gopls/internal/lsp/source/completion/printf.go b/gopls/internal/lsp/source/completion/printf.go new file mode 100644 index 000000000..432011755 --- /dev/null +++ b/gopls/internal/lsp/source/completion/printf.go @@ -0,0 +1,172 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package completion + +import ( + "go/ast" + "go/constant" + "go/types" + "strconv" + "strings" + "unicode/utf8" +) + +// printfArgKind returns the expected objKind when completing a +// printf-like operand. call is the printf-like function call, and +// argIdx is the index of call.Args being completed. +func printfArgKind(info *types.Info, call *ast.CallExpr, argIdx int) objKind { + // Printf-like function name must end in "f". + fn := exprObj(info, call.Fun) + if fn == nil || !strings.HasSuffix(fn.Name(), "f") { + return kindAny + } + + sig, _ := fn.Type().(*types.Signature) + if sig == nil { + return kindAny + } + + // Must be variadic and take at least two params. + numParams := sig.Params().Len() + if !sig.Variadic() || numParams < 2 || argIdx < numParams-1 { + return kindAny + } + + // Param preceding variadic args must be a (format) string. + if !types.Identical(sig.Params().At(numParams-2).Type(), types.Typ[types.String]) { + return kindAny + } + + // Format string must be a constant. + strArg := info.Types[call.Args[numParams-2]].Value + if strArg == nil || strArg.Kind() != constant.String { + return kindAny + } + + return formatOperandKind(constant.StringVal(strArg), argIdx-(numParams-1)+1) +} + +// formatOperandKind returns the objKind corresponding to format's +// operandIdx'th operand. +func formatOperandKind(format string, operandIdx int) objKind { + var ( + prevOperandIdx int + kind = kindAny + ) + for { + i := strings.Index(format, "%") + if i == -1 { + break + } + + var operands []formatOperand + format, operands = parsePrintfVerb(format[i+1:], prevOperandIdx) + + // Check if any this verb's operands correspond to our target + // operandIdx. + for _, v := range operands { + if v.idx == operandIdx { + if kind == kindAny { + kind = v.kind + } else if v.kind != kindAny { + // If multiple verbs refer to the same operand, take the + // intersection of their kinds. + kind &= v.kind + } + } + + prevOperandIdx = v.idx + } + } + return kind +} + +type formatOperand struct { + // idx is the one-based printf operand index. + idx int + // kind is a mask of expected kinds of objects for this operand. + kind objKind +} + +// parsePrintfVerb parses the leading printf verb in f. The opening +// "%" must already be trimmed from f. prevIdx is the previous +// operand's index, or zero if this is the first verb. The format +// string is returned with the leading verb removed. Multiple operands +// can be returned in the case of dynamic widths such as "%*.*f". +func parsePrintfVerb(f string, prevIdx int) (string, []formatOperand) { + var verbs []formatOperand + + addVerb := func(k objKind) { + verbs = append(verbs, formatOperand{ + idx: prevIdx + 1, + kind: k, + }) + prevIdx++ + } + + for len(f) > 0 { + // Trim first rune off of f so we are guaranteed to make progress. + r, l := utf8.DecodeRuneInString(f) + f = f[l:] + + // We care about three things: + // 1. The verb, which maps directly to object kind. + // 2. Explicit operand indices like "%[2]s". + // 3. Dynamic widths using "*". + switch r { + case '%': + return f, nil + case '*': + addVerb(kindInt) + continue + case '[': + // Parse operand index as in "%[2]s". + i := strings.Index(f, "]") + if i == -1 { + return f, nil + } + + idx, err := strconv.Atoi(f[:i]) + f = f[i+1:] + if err != nil { + return f, nil + } + + prevIdx = idx - 1 + continue + case 'v', 'T': + addVerb(kindAny) + case 't': + addVerb(kindBool) + case 'c', 'd', 'o', 'O', 'U': + addVerb(kindInt) + case 'e', 'E', 'f', 'F', 'g', 'G': + addVerb(kindFloat | kindComplex) + case 'b': + addVerb(kindInt | kindFloat | kindComplex | kindBytes) + case 'q', 's': + addVerb(kindString | kindBytes | kindStringer | kindError) + case 'x', 'X': + // Omit kindStringer and kindError though technically allowed. + addVerb(kindString | kindBytes | kindInt | kindFloat | kindComplex) + case 'p': + addVerb(kindPtr | kindSlice) + case 'w': + addVerb(kindError) + case '+', '-', '#', ' ', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + // Flag or numeric width/precision value. + continue + default: + // Assume unrecognized rune is a custom fmt.Formatter verb. + addVerb(kindAny) + } + + if len(verbs) > 0 { + break + } + } + + return f, verbs +} diff --git a/gopls/internal/lsp/source/completion/printf_test.go b/gopls/internal/lsp/source/completion/printf_test.go new file mode 100644 index 000000000..19d295b8d --- /dev/null +++ b/gopls/internal/lsp/source/completion/printf_test.go @@ -0,0 +1,72 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package completion + +import ( + "fmt" + "testing" +) + +func TestFormatOperandKind(t *testing.T) { + cases := []struct { + f string + idx int + kind objKind + }{ + {"", 1, kindAny}, + {"%", 1, kindAny}, + {"%%%", 1, kindAny}, + {"%[1", 1, kindAny}, + {"%[?%s", 2, kindAny}, + {"%[abc]v", 1, kindAny}, + + {"%v", 1, kindAny}, + {"%T", 1, kindAny}, + {"%t", 1, kindBool}, + {"%d", 1, kindInt}, + {"%c", 1, kindInt}, + {"%o", 1, kindInt}, + {"%O", 1, kindInt}, + {"%U", 1, kindInt}, + {"%e", 1, kindFloat | kindComplex}, + {"%E", 1, kindFloat | kindComplex}, + {"%f", 1, kindFloat | kindComplex}, + {"%F", 1, kindFloat | kindComplex}, + {"%g", 1, kindFloat | kindComplex}, + {"%G", 1, kindFloat | kindComplex}, + {"%b", 1, kindInt | kindFloat | kindComplex | kindBytes}, + {"%q", 1, kindString | kindBytes | kindStringer | kindError}, + {"%s", 1, kindString | kindBytes | kindStringer | kindError}, + {"%x", 1, kindString | kindBytes | kindInt | kindFloat | kindComplex}, + {"%X", 1, kindString | kindBytes | kindInt | kindFloat | kindComplex}, + {"%p", 1, kindPtr | kindSlice}, + {"%w", 1, kindError}, + + {"%1.2f", 1, kindFloat | kindComplex}, + {"%*f", 1, kindInt}, + {"%*f", 2, kindFloat | kindComplex}, + {"%*.*f", 1, kindInt}, + {"%*.*f", 2, kindInt}, + {"%*.*f", 3, kindFloat | kindComplex}, + {"%[3]*.[2]*[1]f", 1, kindFloat | kindComplex}, + {"%[3]*.[2]*[1]f", 2, kindInt}, + {"%[3]*.[2]*[1]f", 3, kindInt}, + + {"foo %% %d", 1, kindInt}, + {"%#-12.34f", 1, kindFloat | kindComplex}, + {"% d", 1, kindInt}, + + {"%s %[1]X %d", 1, kindString | kindBytes}, + {"%s %[1]X %d", 2, kindInt}, + } + + for _, c := range cases { + t.Run(fmt.Sprintf("%q#%d", c.f, c.idx), func(t *testing.T) { + if got := formatOperandKind(c.f, c.idx); got != c.kind { + t.Errorf("expected %d (%[1]b), got %d (%[2]b)", c.kind, got) + } + }) + } +} diff --git a/gopls/internal/lsp/source/completion/snippet.go b/gopls/internal/lsp/source/completion/snippet.go new file mode 100644 index 000000000..f4ea767e9 --- /dev/null +++ b/gopls/internal/lsp/source/completion/snippet.go @@ -0,0 +1,116 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package completion + +import ( + "go/ast" + + "golang.org/x/tools/gopls/internal/lsp/safetoken" + "golang.org/x/tools/gopls/internal/lsp/snippet" +) + +// structFieldSnippet calculates the snippet for struct literal field names. +func (c *completer) structFieldSnippet(cand candidate, detail string, snip *snippet.Builder) { + if !c.wantStructFieldCompletions() { + return + } + + // If we are in a deep completion then we can't be completing a field + // name (e.g. "Foo{f<>}" completing to "Foo{f.Bar}" should not generate + // a snippet). + if len(cand.path) > 0 { + return + } + + clInfo := c.enclosingCompositeLiteral + + // If we are already in a key-value expression, we don't want a snippet. + if clInfo.kv != nil { + return + } + + // A plain snippet turns "Foo{Ba<>" into "Foo{Bar: <>". + snip.WriteText(": ") + snip.WritePlaceholder(func(b *snippet.Builder) { + // A placeholder snippet turns "Foo{Ba<>" into "Foo{Bar: <*int*>". + if c.opts.placeholders { + b.WriteText(detail) + } + }) + + fset := c.pkg.FileSet() + + // If the cursor position is on a different line from the literal's opening brace, + // we are in a multiline literal. Ignore line directives. + if safetoken.StartPosition(fset, c.pos).Line != safetoken.StartPosition(fset, clInfo.cl.Lbrace).Line { + snip.WriteText(",") + } +} + +// functionCallSnippet calculates the snippet for function calls. +func (c *completer) functionCallSnippet(name string, tparams, params []string, snip *snippet.Builder) { + // If there is no suffix then we need to reuse existing call parens + // "()" if present. If there is an identifier suffix then we always + // need to include "()" since we don't overwrite the suffix. + if c.surrounding != nil && c.surrounding.Suffix() == "" && len(c.path) > 1 { + // If we are the left side (i.e. "Fun") part of a call expression, + // we don't want a snippet since there are already parens present. + switch n := c.path[1].(type) { + case *ast.CallExpr: + // The Lparen != Rparen check detects fudged CallExprs we + // inserted when fixing the AST. In this case, we do still need + // to insert the calling "()" parens. + if n.Fun == c.path[0] && n.Lparen != n.Rparen { + return + } + case *ast.SelectorExpr: + if len(c.path) > 2 { + if call, ok := c.path[2].(*ast.CallExpr); ok && call.Fun == c.path[1] && call.Lparen != call.Rparen { + return + } + } + } + } + + snip.WriteText(name) + + if len(tparams) > 0 { + snip.WriteText("[") + if c.opts.placeholders { + for i, tp := range tparams { + if i > 0 { + snip.WriteText(", ") + } + snip.WritePlaceholder(func(b *snippet.Builder) { + b.WriteText(tp) + }) + } + } else { + snip.WritePlaceholder(nil) + } + snip.WriteText("]") + } + + snip.WriteText("(") + + if c.opts.placeholders { + // A placeholder snippet turns "someFun<>" into "someFunc(<*i int*>, *s string*)". + for i, p := range params { + if i > 0 { + snip.WriteText(", ") + } + snip.WritePlaceholder(func(b *snippet.Builder) { + b.WriteText(p) + }) + } + } else { + // A plain snippet turns "someFun<>" into "someFunc(<>)". + if len(params) > 0 { + snip.WritePlaceholder(nil) + } + } + + snip.WriteText(")") +} diff --git a/gopls/internal/lsp/source/completion/statements.go b/gopls/internal/lsp/source/completion/statements.go new file mode 100644 index 000000000..707375fa1 --- /dev/null +++ b/gopls/internal/lsp/source/completion/statements.go @@ -0,0 +1,361 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package completion + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + + "golang.org/x/tools/gopls/internal/lsp/protocol" + "golang.org/x/tools/gopls/internal/lsp/snippet" + "golang.org/x/tools/gopls/internal/lsp/source" +) + +// addStatementCandidates adds full statement completion candidates +// appropriate for the current context. +func (c *completer) addStatementCandidates() { + c.addErrCheck() + c.addAssignAppend() +} + +// addAssignAppend offers a completion candidate of the form: +// +// someSlice = append(someSlice, ) +// +// It will offer the "append" completion in either of two situations: +// +// 1. Position is in RHS of assign, prefix matches "append", and +// corresponding LHS object is a slice. For example, +// "foo = ap<>" completes to "foo = append(foo, )". +// +// 2. Prefix is an ident or selector in an *ast.ExprStmt (i.e. +// beginning of statement), and our best matching candidate is a +// slice. For example: "foo.ba" completes to "foo.bar = append(foo.bar, )". +func (c *completer) addAssignAppend() { + if len(c.path) < 3 { + return + } + + ident, _ := c.path[0].(*ast.Ident) + if ident == nil { + return + } + + var ( + // sliceText is the full name of our slice object, e.g. "s.abc" in + // "s.abc = app<>". + sliceText string + // needsLHS is true if we need to prepend the LHS slice name and + // "=" to our candidate. + needsLHS = false + fset = c.pkg.FileSet() + ) + + switch n := c.path[1].(type) { + case *ast.AssignStmt: + // We are already in an assignment. Make sure our prefix matches "append". + if c.matcher.Score("append") <= 0 { + return + } + + exprIdx := exprAtPos(c.pos, n.Rhs) + if exprIdx == len(n.Rhs) || exprIdx > len(n.Lhs)-1 { + return + } + + lhsType := c.pkg.GetTypesInfo().TypeOf(n.Lhs[exprIdx]) + if lhsType == nil { + return + } + + // Make sure our corresponding LHS object is a slice. + if _, isSlice := lhsType.Underlying().(*types.Slice); !isSlice { + return + } + + // The name or our slice is whatever's in the LHS expression. + sliceText = source.FormatNode(fset, n.Lhs[exprIdx]) + case *ast.SelectorExpr: + // Make sure we are a selector at the beginning of a statement. + if _, parentIsExprtStmt := c.path[2].(*ast.ExprStmt); !parentIsExprtStmt { + return + } + + // So far we only know the first part of our slice name. For + // example in "s.a<>" we only know our slice begins with "s." + // since the user could still be typing. + sliceText = source.FormatNode(fset, n.X) + "." + needsLHS = true + case *ast.ExprStmt: + needsLHS = true + default: + return + } + + var ( + label string + snip snippet.Builder + score = highScore + ) + + if needsLHS { + // Offer the long form assign + append candidate if our best + // candidate is a slice. + bestItem := c.topCandidate() + if bestItem == nil || !bestItem.isSlice { + return + } + + // Don't rank the full form assign + append candidate above the + // slice itself. + score = bestItem.Score - 0.01 + + // Fill in rest of sliceText now that we have the object name. + sliceText += bestItem.Label + + // Fill in the candidate's LHS bits. + label = fmt.Sprintf("%s = ", bestItem.Label) + snip.WriteText(label) + } + + snip.WriteText(fmt.Sprintf("append(%s, ", sliceText)) + snip.WritePlaceholder(nil) + snip.WriteText(")") + + c.items = append(c.items, CompletionItem{ + Label: label + fmt.Sprintf("append(%s, )", sliceText), + Kind: protocol.FunctionCompletion, + Score: score, + snippet: &snip, + }) +} + +// topCandidate returns the strictly highest scoring candidate +// collected so far. If the top two candidates have the same score, +// nil is returned. +func (c *completer) topCandidate() *CompletionItem { + var bestItem, secondBestItem *CompletionItem + for i := range c.items { + if bestItem == nil || c.items[i].Score > bestItem.Score { + bestItem = &c.items[i] + } else if secondBestItem == nil || c.items[i].Score > secondBestItem.Score { + secondBestItem = &c.items[i] + } + } + + // If secondBestItem has the same score, bestItem isn't + // the strict best. + if secondBestItem != nil && secondBestItem.Score == bestItem.Score { + return nil + } + + return bestItem +} + +// addErrCheck offers a completion candidate of the form: +// +// if err != nil { +// return nil, err +// } +// +// In the case of test functions, it offers a completion candidate of the form: +// +// if err != nil { +// t.Fatal(err) +// } +// +// The position must be in a function that returns an error, and the +// statement preceding the position must be an assignment where the +// final LHS object is an error. addErrCheck will synthesize +// zero values as necessary to make the return statement valid. +func (c *completer) addErrCheck() { + if len(c.path) < 2 || c.enclosingFunc == nil || !c.opts.placeholders { + return + } + + var ( + errorType = types.Universe.Lookup("error").Type() + result = c.enclosingFunc.sig.Results() + testVar = getTestVar(c.enclosingFunc, c.pkg) + isTest = testVar != "" + doesNotReturnErr = result.Len() == 0 || !types.Identical(result.At(result.Len()-1).Type(), errorType) + ) + // Make sure our enclosing function is a Test func or returns an error. + if !isTest && doesNotReturnErr { + return + } + + prevLine := prevStmt(c.pos, c.path) + if prevLine == nil { + return + } + + // Make sure our preceding statement was as assignment. + assign, _ := prevLine.(*ast.AssignStmt) + if assign == nil || len(assign.Lhs) == 0 { + return + } + + lastAssignee := assign.Lhs[len(assign.Lhs)-1] + + // Make sure the final assignee is an error. + if !types.Identical(c.pkg.GetTypesInfo().TypeOf(lastAssignee), errorType) { + return + } + + var ( + // errVar is e.g. "err" in "foo, err := bar()". + errVar = source.FormatNode(c.pkg.FileSet(), lastAssignee) + + // Whether we need to include the "if" keyword in our candidate. + needsIf = true + ) + + // If the returned error from the previous statement is "_", it is not a real object. + // If we don't have an error, and the function signature takes a testing.TB that is either ignored + // or an "_", then we also can't call t.Fatal(err). + if errVar == "_" { + return + } + + // Below we try to detect if the user has already started typing "if + // err" so we can replace what they've typed with our complete + // statement. + switch n := c.path[0].(type) { + case *ast.Ident: + switch c.path[1].(type) { + case *ast.ExprStmt: + // This handles: + // + // f, err := os.Open("foo") + // i<> + + // Make sure they are typing "if". + if c.matcher.Score("if") <= 0 { + return + } + case *ast.IfStmt: + // This handles: + // + // f, err := os.Open("foo") + // if er<> + + // Make sure they are typing the error's name. + if c.matcher.Score(errVar) <= 0 { + return + } + + needsIf = false + default: + return + } + case *ast.IfStmt: + // This handles: + // + // f, err := os.Open("foo") + // if <> + + // Avoid false positives by ensuring the if's cond is a bad + // expression. For example, don't offer the completion in cases + // like "if <> somethingElse". + if _, bad := n.Cond.(*ast.BadExpr); !bad { + return + } + + // If "if" is our direct prefix, we need to include it in our + // candidate since the existing "if" will be overwritten. + needsIf = c.pos == n.Pos()+token.Pos(len("if")) + } + + // Build up a snippet that looks like: + // + // if err != nil { + // return <zero value>, ..., ${1:err} + // } + // + // We make the error a placeholder so it is easy to alter the error. + var snip snippet.Builder + if needsIf { + snip.WriteText("if ") + } + snip.WriteText(fmt.Sprintf("%s != nil {\n\t", errVar)) + + var label string + if isTest { + snip.WriteText(fmt.Sprintf("%s.Fatal(%s)", testVar, errVar)) + label = fmt.Sprintf("%[1]s != nil { %[2]s.Fatal(%[1]s) }", errVar, testVar) + } else { + snip.WriteText("return ") + for i := 0; i < result.Len()-1; i++ { + snip.WriteText(formatZeroValue(result.At(i).Type(), c.qf)) + snip.WriteText(", ") + } + snip.WritePlaceholder(func(b *snippet.Builder) { + b.WriteText(errVar) + }) + label = fmt.Sprintf("%[1]s != nil { return %[1]s }", errVar) + } + + snip.WriteText("\n}") + + if needsIf { + label = "if " + label + } + + c.items = append(c.items, CompletionItem{ + Label: label, + // There doesn't seem to be a more appropriate kind. + Kind: protocol.KeywordCompletion, + Score: highScore, + snippet: &snip, + }) +} + +// getTestVar checks the function signature's input parameters and returns +// the name of the first parameter that implements "testing.TB". For example, +// func someFunc(t *testing.T) returns the string "t", func someFunc(b *testing.B) +// returns "b" etc. An empty string indicates that the function signature +// does not take a testing.TB parameter or does so but is ignored such +// as func someFunc(*testing.T). +func getTestVar(enclosingFunc *funcInfo, pkg source.Package) string { + if enclosingFunc == nil || enclosingFunc.sig == nil { + return "" + } + + var testingPkg *types.Package + for _, p := range pkg.GetTypes().Imports() { + if p.Path() == "testing" { + testingPkg = p + break + } + } + if testingPkg == nil { + return "" + } + tbObj := testingPkg.Scope().Lookup("TB") + if tbObj == nil { + return "" + } + iface, ok := tbObj.Type().Underlying().(*types.Interface) + if !ok { + return "" + } + + sig := enclosingFunc.sig + for i := 0; i < sig.Params().Len(); i++ { + param := sig.Params().At(i) + if param.Name() == "_" { + continue + } + if !types.Implements(param.Type(), iface) { + continue + } + return param.Name() + } + + return "" +} diff --git a/gopls/internal/lsp/source/completion/util.go b/gopls/internal/lsp/source/completion/util.go new file mode 100644 index 000000000..4b6ec09a0 --- /dev/null +++ b/gopls/internal/lsp/source/completion/util.go @@ -0,0 +1,344 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package completion + +import ( + "go/ast" + "go/token" + "go/types" + + "golang.org/x/tools/go/types/typeutil" + "golang.org/x/tools/gopls/internal/lsp/protocol" + "golang.org/x/tools/gopls/internal/lsp/safetoken" + "golang.org/x/tools/gopls/internal/lsp/source" + "golang.org/x/tools/internal/diff" + "golang.org/x/tools/internal/typeparams" +) + +// exprAtPos returns the index of the expression containing pos. +func exprAtPos(pos token.Pos, args []ast.Expr) int { + for i, expr := range args { + if expr.Pos() <= pos && pos <= expr.End() { + return i + } + } + return len(args) +} + +// eachField invokes fn for each field that can be selected from a +// value of type T. +func eachField(T types.Type, fn func(*types.Var)) { + // TODO(adonovan): this algorithm doesn't exclude ambiguous + // selections that match more than one field/method. + // types.NewSelectionSet should do that for us. + + // for termination on recursive types + var seen typeutil.Map + + var visit func(T types.Type) + visit = func(T types.Type) { + if T, ok := source.Deref(T).Underlying().(*types.Struct); ok { + if seen.At(T) != nil { + return + } + + for i := 0; i < T.NumFields(); i++ { + f := T.Field(i) + fn(f) + if f.Anonymous() { + seen.Set(T, true) + visit(f.Type()) + } + } + } + } + visit(T) +} + +// typeIsValid reports whether typ doesn't contain any Invalid types. +func typeIsValid(typ types.Type) bool { + // Check named types separately, because we don't want + // to call Underlying() on them to avoid problems with recursive types. + if _, ok := typ.(*types.Named); ok { + return true + } + + switch typ := typ.Underlying().(type) { + case *types.Basic: + return typ.Kind() != types.Invalid + case *types.Array: + return typeIsValid(typ.Elem()) + case *types.Slice: + return typeIsValid(typ.Elem()) + case *types.Pointer: + return typeIsValid(typ.Elem()) + case *types.Map: + return typeIsValid(typ.Key()) && typeIsValid(typ.Elem()) + case *types.Chan: + return typeIsValid(typ.Elem()) + case *types.Signature: + return typeIsValid(typ.Params()) && typeIsValid(typ.Results()) + case *types.Tuple: + for i := 0; i < typ.Len(); i++ { + if !typeIsValid(typ.At(i).Type()) { + return false + } + } + return true + case *types.Struct, *types.Interface: + // Don't bother checking structs, interfaces for validity. + return true + default: + return false + } +} + +// resolveInvalid traverses the node of the AST that defines the scope +// containing the declaration of obj, and attempts to find a user-friendly +// name for its invalid type. The resulting Object and its Type are fake. +func resolveInvalid(fset *token.FileSet, obj types.Object, node ast.Node, info *types.Info) types.Object { + var resultExpr ast.Expr + ast.Inspect(node, func(node ast.Node) bool { + switch n := node.(type) { + case *ast.ValueSpec: + for _, name := range n.Names { + if info.Defs[name] == obj { + resultExpr = n.Type + } + } + return false + case *ast.Field: // This case handles parameters and results of a FuncDecl or FuncLit. + for _, name := range n.Names { + if info.Defs[name] == obj { + resultExpr = n.Type + } + } + return false + default: + return true + } + }) + // Construct a fake type for the object and return a fake object with this type. + typename := source.FormatNode(fset, resultExpr) + typ := types.NewNamed(types.NewTypeName(token.NoPos, obj.Pkg(), typename, nil), types.Typ[types.Invalid], nil) + return types.NewVar(obj.Pos(), obj.Pkg(), obj.Name(), typ) +} + +func isPointer(T types.Type) bool { + _, ok := T.(*types.Pointer) + return ok +} + +func isVar(obj types.Object) bool { + _, ok := obj.(*types.Var) + return ok +} + +func isTypeName(obj types.Object) bool { + _, ok := obj.(*types.TypeName) + return ok +} + +func isFunc(obj types.Object) bool { + _, ok := obj.(*types.Func) + return ok +} + +func isEmptyInterface(T types.Type) bool { + intf, _ := T.(*types.Interface) + return intf != nil && intf.NumMethods() == 0 && typeparams.IsMethodSet(intf) +} + +func isUntyped(T types.Type) bool { + if basic, ok := T.(*types.Basic); ok { + return basic.Info()&types.IsUntyped > 0 + } + return false +} + +func isPkgName(obj types.Object) bool { + _, ok := obj.(*types.PkgName) + return ok +} + +func isASTFile(n ast.Node) bool { + _, ok := n.(*ast.File) + return ok +} + +func deslice(T types.Type) types.Type { + if slice, ok := T.Underlying().(*types.Slice); ok { + return slice.Elem() + } + return nil +} + +// isSelector returns the enclosing *ast.SelectorExpr when pos is in the +// selector. +func enclosingSelector(path []ast.Node, pos token.Pos) *ast.SelectorExpr { + if len(path) == 0 { + return nil + } + + if sel, ok := path[0].(*ast.SelectorExpr); ok { + return sel + } + + if _, ok := path[0].(*ast.Ident); ok && len(path) > 1 { + if sel, ok := path[1].(*ast.SelectorExpr); ok && pos >= sel.Sel.Pos() { + return sel + } + } + + return nil +} + +// enclosingDeclLHS returns LHS idents from containing value spec or +// assign statement. +func enclosingDeclLHS(path []ast.Node) []*ast.Ident { + for _, n := range path { + switch n := n.(type) { + case *ast.ValueSpec: + return n.Names + case *ast.AssignStmt: + ids := make([]*ast.Ident, 0, len(n.Lhs)) + for _, e := range n.Lhs { + if id, ok := e.(*ast.Ident); ok { + ids = append(ids, id) + } + } + return ids + } + } + + return nil +} + +// exprObj returns the types.Object associated with the *ast.Ident or +// *ast.SelectorExpr e. +func exprObj(info *types.Info, e ast.Expr) types.Object { + var ident *ast.Ident + switch expr := e.(type) { + case *ast.Ident: + ident = expr + case *ast.SelectorExpr: + ident = expr.Sel + default: + return nil + } + + return info.ObjectOf(ident) +} + +// typeConversion returns the type being converted to if call is a type +// conversion expression. +func typeConversion(call *ast.CallExpr, info *types.Info) types.Type { + // Type conversion (e.g. "float64(foo)"). + if fun, _ := exprObj(info, call.Fun).(*types.TypeName); fun != nil { + return fun.Type() + } + + return nil +} + +// fieldsAccessible returns whether s has at least one field accessible by p. +func fieldsAccessible(s *types.Struct, p *types.Package) bool { + for i := 0; i < s.NumFields(); i++ { + f := s.Field(i) + if f.Exported() || f.Pkg() == p { + return true + } + } + return false +} + +// prevStmt returns the statement that precedes the statement containing pos. +// For example: +// +// foo := 1 +// bar(1 + 2<>) +// +// If "<>" is pos, prevStmt returns "foo := 1" +func prevStmt(pos token.Pos, path []ast.Node) ast.Stmt { + var blockLines []ast.Stmt + for i := 0; i < len(path) && blockLines == nil; i++ { + switch n := path[i].(type) { + case *ast.BlockStmt: + blockLines = n.List + case *ast.CommClause: + blockLines = n.Body + case *ast.CaseClause: + blockLines = n.Body + } + } + + for i := len(blockLines) - 1; i >= 0; i-- { + if blockLines[i].End() < pos { + return blockLines[i] + } + } + + return nil +} + +// formatZeroValue produces Go code representing the zero value of T. It +// returns the empty string if T is invalid. +func formatZeroValue(T types.Type, qf types.Qualifier) string { + switch u := T.Underlying().(type) { + case *types.Basic: + switch { + case u.Info()&types.IsNumeric > 0: + return "0" + case u.Info()&types.IsString > 0: + return `""` + case u.Info()&types.IsBoolean > 0: + return "false" + default: + return "" + } + case *types.Pointer, *types.Interface, *types.Chan, *types.Map, *types.Slice, *types.Signature: + return "nil" + default: + return types.TypeString(T, qf) + "{}" + } +} + +// isBasicKind returns whether t is a basic type of kind k. +func isBasicKind(t types.Type, k types.BasicInfo) bool { + b, _ := t.Underlying().(*types.Basic) + return b != nil && b.Info()&k > 0 +} + +func (c *completer) editText(from, to token.Pos, newText string) ([]protocol.TextEdit, error) { + start, end, err := safetoken.Offsets(c.tokFile, from, to) + if err != nil { + return nil, err // can't happen: from/to came from c + } + return source.ToProtocolEdits(c.mapper, []diff.Edit{{ + Start: start, + End: end, + New: newText, + }}) +} + +// assignableTo is like types.AssignableTo, but returns false if +// either type is invalid. +func assignableTo(x, to types.Type) bool { + if x == types.Typ[types.Invalid] || to == types.Typ[types.Invalid] { + return false + } + + return types.AssignableTo(x, to) +} + +// convertibleTo is like types.ConvertibleTo, but returns false if +// either type is invalid. +func convertibleTo(x, to types.Type) bool { + if x == types.Typ[types.Invalid] || to == types.Typ[types.Invalid] { + return false + } + + return types.ConvertibleTo(x, to) +} diff --git a/gopls/internal/lsp/source/completion/util_test.go b/gopls/internal/lsp/source/completion/util_test.go new file mode 100644 index 000000000..c94d279fb --- /dev/null +++ b/gopls/internal/lsp/source/completion/util_test.go @@ -0,0 +1,28 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package completion + +import ( + "go/types" + "testing" +) + +func TestFormatZeroValue(t *testing.T) { + tests := []struct { + typ types.Type + want string + }{ + {types.Typ[types.String], `""`}, + {types.Typ[types.Byte], "0"}, + {types.Typ[types.Invalid], ""}, + {types.Universe.Lookup("error").Type(), "nil"}, + } + + for _, test := range tests { + if got := formatZeroValue(test.typ, nil); got != test.want { + t.Errorf("formatZeroValue(%v) = %q, want %q", test.typ, got, test.want) + } + } +} |