diff options
Diffstat (limited to 'internal/lsp/source/completion')
19 files changed, 0 insertions, 6799 deletions
diff --git a/internal/lsp/source/completion/builtin.go b/internal/lsp/source/completion/builtin.go deleted file mode 100644 index 39732d864..000000000 --- a/internal/lsp/source/completion/builtin.go +++ /dev/null @@ -1,147 +0,0 @@ -// 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/internal/lsp/source/completion/completion.go b/internal/lsp/source/completion/completion.go deleted file mode 100644 index 60c404dc5..000000000 --- a/internal/lsp/source/completion/completion.go +++ /dev/null @@ -1,2967 +0,0 @@ -// 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 ( - "context" - "fmt" - "go/ast" - "go/constant" - "go/scanner" - "go/token" - "go/types" - "math" - "sort" - "strconv" - "strings" - "sync" - "time" - "unicode" - - "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/imports" - "golang.org/x/tools/internal/lsp/fuzzy" - "golang.org/x/tools/internal/lsp/protocol" - "golang.org/x/tools/internal/lsp/snippet" - "golang.org/x/tools/internal/lsp/source" - "golang.org/x/tools/internal/typeparams" - errors "golang.org/x/xerrors" -) - -type CompletionItem struct { - // 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 - - // obj is the object from which this candidate was derived, if any. - // obj is for internal use only. - obj types.Object -} - -// 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 - 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 - - // pos is the position at which the request was triggered. - 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.ColumnMapper - - // startTime is when we started processing this completion request. It does - // not include any time the request spent in the queue. - startTime time.Time -} - -// 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 - pkg source.Package -} - -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 - cursor token.Pos - source.MappedRange -} - -func (p Selection) Content() string { - return p.content -} - -func (p Selection) Start() token.Pos { - return p.MappedRange.SpanRange().Start -} - -func (p Selection) End() token.Pos { - return p.MappedRange.SpanRange().End -} - -func (p Selection) Prefix() string { - return p.content[:p.cursor-p.SpanRange().Start] -} - -func (p Selection) Suffix() string { - return p.content[p.cursor-p.SpanRange().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. - MappedRange: source.NewMappedRange(c.snapshot.FileSet(), c.mapper, ident.Pos(), ident.End()), - } - - 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, - MappedRange: source.NewMappedRange(c.snapshot.FileSet(), c.mapper, c.pos, c.pos), - } - } - return c.surrounding -} - -// candidate represents a completion candidate. -type candidate struct { - // obj is the types.Object to complete to. - 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 insterted 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.GetParsedFile(ctx, snapshot, fh, 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, errors.Errorf("getting file for Completion: %w (package completions: %v)", err, innerErr) - } - return items, surrounding, nil - } - spn, err := pgf.Mapper.PointSpan(protoPos) - if err != nil { - return nil, nil, err - } - rng, err := spn.Range(pgf.Mapper.Converter) - 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, rng.Start-1, rng.Start-1) - if path == nil { - return nil, nil, errors.Errorf("cannot find node enclosing position") - } - - pos := rng.Start - - // 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, snapshot.FileSet(), pgf.Mapper, fh) - 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} - } - } - } - - opts := snapshot.View().Options() - c := &completer{ - pkg: pkg, - snapshot: snapshot, - qf: source.Qualifier(pgf.File, pkg.GetTypes(), pkg.GetTypesInfo()), - completionContext: completionContext{ - triggerCharacter: protoContext.TriggerCharacter, - triggerKind: protoContext.TriggerKind, - }, - fh: fh, - filename: fh.URI().Filename(), - file: pgf.File, - path: path, - pos: pos, - seen: make(map[types.Object]bool), - enclosingFunc: enclosingFunction(path, pkg.GetTypesInfo()), - enclosingCompositeLiteral: enclosingCompositeLiteral(path, rng.Start, 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, - } - - 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 identifer 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.snapshot.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, - MappedRange: source.NewMappedRange(c.snapshot.FileSet(), c.mapper, start, end), - } - - 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.snapshot.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, - MappedRange: source.NewMappedRange(c.snapshot.FileSet(), c.mapper, - token.Pos(int(cursorComment.Slash)+start), token.Pos(int(cursorComment.Slash)+end)), - } - 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) - - // Is sel a qualified identifier? - if id, ok := sel.X.(*ast.Ident); ok { - if pkgName, ok := c.pkg.GetTypesInfo().Uses[id].(*types.PkgName); ok { - var pkg source.Package - for _, imp := range c.pkg.Imports() { - if imp.PkgPath() == pkgName.Imported().Path() { - pkg = imp - } - } - // If the package is not imported, try searching for unimported - // completions. - if pkg == nil && c.opts.unimported { - if err := c.unimportedMembers(ctx, id); err != nil { - return err - } - } - c.packageMembers(pkgName.Imported(), stdScore, nil, func(cand candidate) { - c.deepState.enqueue(cand) - }) - return nil - } - } - - // Invariant: sel is a true selector. - tv, ok := c.pkg.GetTypesInfo().Types[sel.X] - if ok { - c.methodsAndFields(tv.Type, tv.Addressable(), nil, func(cand candidate) { - c.deepState.enqueue(cand) - }) - - c.addPostfixSnippetCandidates(ctx, sel) - - return nil - } - - // Try unimported packages. - if id, ok := sel.X.(*ast.Ident); ok && c.opts.unimported { - if err := c.unimportedMembers(ctx, id); err != nil { - return err - } - } - return nil -} - -func (c *completer) unimportedMembers(ctx context.Context, id *ast.Ident) error { - // Try loaded packages first. They're relevant, fast, and fully typed. - known, err := c.snapshot.CachedImportPaths(ctx) - if err != nil { - return err - } - - var paths []string - for path, pkg := range known { - if pkg.GetTypes().Name() != id.Name { - continue - } - paths = append(paths, path) - } - - 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]] - }) - - for _, path := range paths { - pkg := known[path] - if pkg.GetTypes().Name() != id.Name { - continue - } - imp := &importInfo{ - importPath: path, - pkg: pkg, - } - if imports.ImportPathToAssumedName(path) != pkg.GetTypes().Name() { - imp.name = pkg.GetTypes().Name() - } - c.packageMembers(pkg.GetTypes(), unimportedScore(relevances[path]), imp, func(cand candidate) { - c.deepState.enqueue(cand) - }) - if len(c.items) >= unimportedMemberTarget { - return nil - } - } - - ctx, cancel := context.WithCancel(ctx) - - var mu sync.Mutex - add := func(pkgExport imports.PackageExport) { - mu.Lock() - defer mu.Unlock() - if _, ok := known[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 typ.String() == "*testing.F" && addressable { - // is that a sufficient test? (or is more care needed?) - if c.fuzz(typ, mset, imp, cb, c.snapshot.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), - }) - }) -} - -// lexical finds completions in the lexical environment. -func (c *completer) lexical(ctx context.Context) error { - scopes := source.CollectScopes(c.pkg.GetTypesInfo(), c.path, c.pos) - scopes = append(scopes, c.pkg.GetTypes().Scope(), types.Universe) - - var ( - builtinIota = types.Universe.Lookup("iota") - builtinNil = types.Universe.Lookup("nil") - // comparable is an interface that exists on the dev.typeparams Go branch. - // Filter it out from completion results to stabilize tests. - // TODO(rFindley) update (or remove) our handling for comparable once the - // type parameter API has stabilized. - builtinAny = types.Universe.Lookup("any") - 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 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 == builtinComparable || obj == builtinAny { - continue - } - - // 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.snapshot.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. - if _, ok := seen[pkg.Name()]; !ok && pkg != c.pkg.GetTypes() && !alreadyImports(c.file, 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 -} - -// injectInferredType manufacters candidates based on the given type. -// 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. - if _, named := t.(*types.Named); !named { - // 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 - - known, err := c.snapshot.CachedImportPaths(ctx) - if err != nil { - return err - } - var paths []string - for path, pkg := range known { - if !strings.HasPrefix(pkg.GetTypes().Name(), prefix) { - continue - } - paths = append(paths, path) - } - - 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 { - pkg := known[path] - if _, ok := seen[pkg.GetTypes().Name()]; ok { - continue - } - imp := &importInfo{ - importPath: path, - pkg: pkg, - } - if imports.ImportPathToAssumedName(path) != pkg.GetTypes().Name() { - imp.name = pkg.GetTypes().Name() - } - if count >= maxUnimportedPackageNames { - return nil - } - c.deepState.enqueue(candidate{ - // Pass an empty *types.Package to disable deep completions. - obj: types.NewPkgName(0, nil, pkg.GetTypes().Name(), types.NewPackage(path, pkg.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 string) bool { - for _, s := range f.Imports { - if source.ImportPath(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 { - // Otherwse, 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 - } - - if tv, ok := c.pkg.GetTypesInfo().Types[node.Fun]; ok { - if sig, ok := tv.Type.(*types.Signature); ok { - 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 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 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 && types.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 && types.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 !types.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 types.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 wouuld 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 { - 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 types.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 !source.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 -} diff --git a/internal/lsp/source/completion/deep_completion.go b/internal/lsp/source/completion/deep_completion.go deleted file mode 100644 index a13d807d4..000000000 --- a/internal/lsp/source/completion/deep_completion.go +++ /dev/null @@ -1,362 +0,0 @@ -// 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 wether 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/internal/lsp/source/completion/deep_completion_test.go b/internal/lsp/source/completion/deep_completion_test.go deleted file mode 100644 index 27009af1b..000000000 --- a/internal/lsp/source/completion/deep_completion_test.go +++ /dev/null @@ -1,33 +0,0 @@ -// 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/internal/lsp/source/completion/definition.go b/internal/lsp/source/completion/definition.go deleted file mode 100644 index 17b251cb0..000000000 --- a/internal/lsp/source/completion/definition.go +++ /dev/null @@ -1,127 +0,0 @@ -// 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/token" - "go/types" - "strings" - "unicode" - "unicode/utf8" - - "golang.org/x/tools/internal/lsp/protocol" - "golang.org/x/tools/internal/lsp/snippet" - "golang.org/x/tools/internal/lsp/source" -) - -// some definitions 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, fset *token.FileSet, mapper *protocol.ColumnMapper, fh source.FileHandle) ([]CompletionItem, *Selection) { - if _, ok := obj.(*types.Func); !ok { - return nil, nil // not a function at all - } - if !strings.HasSuffix(fh.URI().Filename(), "_test.go") { - return nil, nil - } - - name := path[0].(*ast.Ident).Name - if len(name) == 0 { - // can't happen - return nil, nil - } - pos := path[0].Pos() - sel := &Selection{ - content: "", - cursor: pos, - MappedRange: source.NewMappedRange(fset, mapper, pos, pos), - } - var ans []CompletionItem - - // Always suggest TestMain, if possible - if strings.HasPrefix("TestMain", name) { - ans = []CompletionItem{defItem("TestMain(m *testing.M)", obj)} - } - - // If a snippet is possible, suggest it - if strings.HasPrefix("Test", name) { - ans = append(ans, defSnippet("Test", "Xxx", "(t *testing.T)", obj)) - return ans, sel - } else if strings.HasPrefix("Benchmark", name) { - ans = append(ans, defSnippet("Benchmark", "Xxx", "(b *testing.B)", obj)) - return ans, sel - } else if strings.HasPrefix("Fuzz", name) { - ans = append(ans, defSnippet("Fuzz", "Xxx", "(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 -} - -func defMatches(name, pat string, path []ast.Node, arg string) string { - idx := strings.Index(name, pat) - if idx < 0 { - 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 fp != nil && len(fp.List) > 0 { - // signature already there, minimal suggestion - return name - } - // suggesting signature too - return name + arg -} - -func defSnippet(prefix, placeholder, suffix string, obj types.Object) CompletionItem { - var sn snippet.Builder - sn.WriteText(prefix) - if placeholder != "" { - sn.WritePlaceholder(func(b *snippet.Builder) { b.WriteText(placeholder) }) - } - sn.WriteText(suffix + " {\n") - sn.WriteFinalTabstop() - sn.WriteText("\n}") - return CompletionItem{ - Label: prefix + placeholder + suffix, - Detail: "tab, type the rest of the name, then tab", - Kind: protocol.FunctionCompletion, - Depth: 0, - Score: 10, - snippet: &sn, - Documentation: prefix + " test function", - obj: 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 parameter", - obj: obj, - } -} diff --git a/internal/lsp/source/completion/format.go b/internal/lsp/source/completion/format.go deleted file mode 100644 index e67456911..000000000 --- a/internal/lsp/source/completion/format.go +++ /dev/null @@ -1,340 +0,0 @@ -// 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/ast" - "go/doc" - "go/types" - "strings" - - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/imports" - "golang.org/x/tools/internal/lsp/debug/tag" - "golang.org/x/tools/internal/lsp/protocol" - "golang.org/x/tools/internal/lsp/snippet" - "golang.org/x/tools/internal/lsp/source" - "golang.org/x/tools/internal/span" - "golang.org/x/tools/internal/typeparams" - errors "golang.org/x/xerrors" -) - -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 wont 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() { - detail = source.FormatVarType(ctx, c.snapshot, c.pkg, obj, c.qf) - } - 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 := source.NewSignature(ctx, c.snapshot, c.pkg, sig, nil, c.qf) - 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, - obj: obj, - } - // If the user doesn't want documentation for completion items. - if !c.opts.documentation { - return item, nil - } - pos := c.snapshot.FileSet().Position(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 - } - uri := span.URIFromPath(pos.Filename) - - // Find the source file of the candidate. - pkg, err := source.FindPackageFromPos(ctx, c.snapshot, obj.Pos()) - if err != nil { - return item, nil - } - - decl, err := c.snapshot.PosToDecl(ctx, pkg, obj.Pos()) - if err != nil { - return CompletionItem{}, err - } - hover, err := source.FindHoverContext(ctx, c.snapshot, pkg, obj, decl, nil) - if err != nil { - event.Error(ctx, "failed to find Hover", err, tag.URI.Of(uri)) - return item, nil - } - if c.opts.fullDocumentation { - item.Documentation = hover.Comment.Text() - } else { - item.Documentation = doc.Synopsis(hover.Comment.Text()) - } - // The desired pattern is `^// Deprecated`, but the prefix has been removed - if strings.HasPrefix(hover.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/internal/lsp/source/completion/fuzz.go b/internal/lsp/source/completion/fuzz.go deleted file mode 100644 index 92349ab93..000000000 --- a/internal/lsp/source/completion/fuzz.go +++ /dev/null @@ -1,142 +0,0 @@ -// 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/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", - obj: nil, - } - 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/internal/lsp/source/completion/keywords.go b/internal/lsp/source/completion/keywords.go deleted file mode 100644 index bbf59b022..000000000 --- a/internal/lsp/source/completion/keywords.go +++ /dev/null @@ -1,154 +0,0 @@ -// 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/internal/lsp/protocol" - "golang.org/x/tools/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/internal/lsp/source/completion/labels.go b/internal/lsp/source/completion/labels.go deleted file mode 100644 index e4fd961e3..000000000 --- a/internal/lsp/source/completion/labels.go +++ /dev/null @@ -1,112 +0,0 @@ -// 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/internal/lsp/source/completion/literal.go b/internal/lsp/source/completion/literal.go deleted file mode 100644 index 5025f1f74..000000000 --- a/internal/lsp/source/completion/literal.go +++ /dev/null @@ -1,440 +0,0 @@ -// 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/internal/event" - "golang.org/x/tools/internal/lsp/protocol" - "golang.org/x/tools/internal/lsp/snippet" - "golang.org/x/tools/internal/lsp/source" -) - -// 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 "" } - } - - typeName := types.TypeString(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 - } - } - - switch t := literalType.Underlying().(type) { - case *types.Struct, *types.Array, *types.Slice, *types.Map: - c.compositeLiteral(t, 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 source.IsInterface(expType) { - c.basicLiteral(t, 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 source.IsInterface(expType) || types.Identical(expType, literalType) { - c.basicLiteral(t, 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(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(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 && !source.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) - ) - for i := 0; i < sig.Params().Len(); i++ { - var ( - p = sig.Params().At(i) - name = p.Name() - ) - 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". - name = source.FormatVarType(ctx, c.snapshot, c.pkg, p, func(p *types.Package) string { - return "" - }) - name = abbreviateTypeName(name) - } - 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 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 := source.FormatVarType(ctx, c.snapshot, c.pkg, p, c.qf) - if sig.Variadic() && i == sig.Params().Len()-1 { - typeStr = strings.Replace(typeStr, "[]", "...", 1) - } - 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() != "" - - if resultsNeedParens { - snip.WriteText("(") - } - for i := 0; i < results.Len(); i++ { - if i > 0 { - snip.WriteText(", ") - } - r := results.At(i) - if name := r.Name(); name != "" { - snip.WriteText(name + " ") - } - snip.WriteText(source.FormatVarType(ctx, c.snapshot, c.pkg, r, c.qf)) - } - 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, typeName string, matchScore float64, edits []protocol.TextEdit) { - snip := &snippet.Builder{} - snip.WriteText(typeName + "{") - // 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, typeName string, matchScore float64, edits []protocol.TextEdit) { - // Never give type conversions like "untyped int()". - if isUntyped(T) { - return - } - - snip := &snippet.Builder{} - snip.WriteText(typeName + "(") - 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(typeName string, secondArg string, matchScore float64, edits []protocol.TextEdit) { - // Keep it simple and don't add any placeholders for optional "make()" arguments. - - snip := &snippet.Builder{} - snip.WriteText("make(" + typeName) - 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, - }) -} diff --git a/internal/lsp/source/completion/package.go b/internal/lsp/source/completion/package.go deleted file mode 100644 index c7e52d718..000000000 --- a/internal/lsp/source/completion/package.go +++ /dev/null @@ -1,364 +0,0 @@ -// 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" - "fmt" - "go/ast" - "go/parser" - "go/scanner" - "go/token" - "go/types" - "path/filepath" - "strings" - "unicode" - - "golang.org/x/tools/internal/lsp/debug" - "golang.org/x/tools/internal/lsp/fuzzy" - "golang.org/x/tools/internal/lsp/protocol" - "golang.org/x/tools/internal/lsp/source" - "golang.org/x/tools/internal/span" - errors "golang.org/x/xerrors" -) - -// 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, pos 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. - pgf, err := snapshot.ParseGo(ctx, fh, source.ParseFull) - if err != nil { - return nil, nil, err - } - - cursorSpan, err := pgf.Mapper.PointSpan(pos) - if err != nil { - return nil, nil, err - } - rng, err := cursorSpan.Range(pgf.Mapper.Converter) - if err != nil { - return nil, nil, err - } - - surrounding, err := packageCompletionSurrounding(ctx, snapshot.FileSet(), pgf, rng.Start) - if err != nil { - return nil, nil, errors.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 position. A valid location -// for package completion is above any declarations or import statements. -func packageCompletionSurrounding(ctx context.Context, fset *token.FileSet, pgf *source.ParsedGoFile, pos token.Pos) (*Selection, error) { - // 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. - filename := pgf.URI.Filename() - expr, _ := parser.ParseExprFrom(fset, filename, pgf.Src, parser.Mode(0)) - if expr == nil { - return nil, fmt.Errorf("unparseable file (%s)", pgf.URI) - } - tok := fset.File(expr.Pos()) - offset, err := source.Offset(pgf.Tok, pos) - if err != nil { - return nil, err - } - if offset > tok.Size() { - debug.Bug(ctx, "out of bounds cursor", "cursor offset (%d) out of bounds for %s (size: %d)", offset, pgf.URI, tok.Size()) - return nil, fmt.Errorf("cursor out of bounds") - } - cursor := tok.Pos(offset) - m := &protocol.ColumnMapper{ - URI: pgf.URI, - Content: pgf.Src, - Converter: span.NewContentConverter(filename, pgf.Src), - } - - // 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, - MappedRange: source.NewMappedRange(fset, m, name.Pos(), name.End()), - }, 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 fset.Position(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, - MappedRange: source.NewMappedRange(fset, m, start, end), - }, 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(fset, cursor, pgf.Src) { - return nil, fmt.Errorf("cursor in comment") - } - - // The surrounding range in this case is the cursor except for empty file, - // in which case it's end of file - 1 - start, end := cursor, cursor - if tok.Size() == 0 { - start, end = tok.Pos(0)-1, tok.Pos(0)-1 - } - - return &Selection{ - content: "", - cursor: cursor, - MappedRange: source.NewMappedRange(fset, m, start, end), - }, nil -} - -func cursorInComment(fset *token.FileSet, cursor token.Pos, src []byte) bool { - var s scanner.Scanner - s.Init(fset.File(cursor), 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) { - workspacePackages, err := snapshot.ActivePackages(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[string]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 _, pkg := range workspacePackages { - if pkg.Name() == "main" || pkg.Name() == "" { - continue - } - if _, ok := seenPkgs[pkg.Name()]; ok { - continue - } - - // Only add packages that are previously used in the current directory. - var relevantPkg bool - for _, pgf := range pkg.CompiledGoFiles() { - if filepath.Dir(pgf.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(pkg.Name())); score > 0 { - packages = append(packages, toCandidate(pkg.Name(), score*highScore)) - } - seenPkgs[pkg.Name()] = struct{}{} - - testPkgName := pkg.Name() + "_test" - if _, ok := seenPkgs[testPkgName]; ok || strings.HasSuffix(pkg.Name(), "_test") { - continue - } - if score := float64(matcher.Score(testPkgName)); score > 0 { - packages = append(packages, toCandidate(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(pkgName)); score > 0 { - packages = append(packages, toCandidate(pkgName, score*lowScore)) - } - - testPkgName := pkgName + "_test" - if score := float64(matcher.Score(testPkgName)); score > 0 { - packages = append(packages, toCandidate(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) string { - 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 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/internal/lsp/source/completion/package_test.go b/internal/lsp/source/completion/package_test.go deleted file mode 100644 index 6436984fd..000000000 --- a/internal/lsp/source/completion/package_test.go +++ /dev/null @@ -1,77 +0,0 @@ -// 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" - -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 string - }{ - {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/internal/lsp/source/completion/postfix_snippets.go b/internal/lsp/source/completion/postfix_snippets.go deleted file mode 100644 index 7ea962118..000000000 --- a/internal/lsp/source/completion/postfix_snippets.go +++ /dev/null @@ -1,461 +0,0 @@ -// 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/internal/event" - "golang.org/x/tools/internal/imports" - "golang.org/x/tools/internal/lsp/protocol" - "golang.org/x/tools/internal/lsp/snippet" - "golang.org/x/tools/internal/lsp/source" - errors "golang.org/x/xerrors" -) - -// 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: "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 "", errors.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 - if 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.snapshot.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.snapshot.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 { - if source.ImportPath(s) == 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/internal/lsp/source/completion/printf.go b/internal/lsp/source/completion/printf.go deleted file mode 100644 index ce74af53b..000000000 --- a/internal/lsp/source/completion/printf.go +++ /dev/null @@ -1,172 +0,0 @@ -// 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/precicision 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/internal/lsp/source/completion/printf_test.go b/internal/lsp/source/completion/printf_test.go deleted file mode 100644 index 19d295b8d..000000000 --- a/internal/lsp/source/completion/printf_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// 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/internal/lsp/source/completion/snippet.go b/internal/lsp/source/completion/snippet.go deleted file mode 100644 index 72c351f94..000000000 --- a/internal/lsp/source/completion/snippet.go +++ /dev/null @@ -1,115 +0,0 @@ -// 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/internal/lsp/snippet" -) - -// structFieldSnippets 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.snapshot.FileSet() - - // If the cursor position is on a different line from the literal's opening brace, - // we are in a multiline literal. - if fset.Position(c.pos).Line != fset.Position(clInfo.cl.Lbrace).Line { - snip.WriteText(",") - } -} - -// functionCallSnippets 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/internal/lsp/source/completion/statements.go b/internal/lsp/source/completion/statements.go deleted file mode 100644 index 3280bb52c..000000000 --- a/internal/lsp/source/completion/statements.go +++ /dev/null @@ -1,360 +0,0 @@ -// 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/internal/lsp/protocol" - "golang.org/x/tools/internal/lsp/snippet" - "golang.org/x/tools/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 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, )". -// -// Or -// -// 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.snapshot.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.obj == nil || bestItem.obj.Type() == nil { - return - } - - if _, isSlice := bestItem.obj.Type().Underlying().(*types.Slice); !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.snapshot.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 "" - } - - sig := enclosingFunc.sig - for i := 0; i < sig.Params().Len(); i++ { - param := sig.Params().At(i) - if param.Name() == "_" { - continue - } - testingPkg, err := pkg.GetImport("testing") - if err != nil { - continue - } - tbObj := testingPkg.GetTypes().Scope().Lookup("TB") - if tbObj == nil { - continue - } - iface, ok := tbObj.Type().Underlying().(*types.Interface) - if !ok { - continue - } - if !types.Implements(param.Type(), iface) { - continue - } - return param.Name() - } - - return "" -} diff --git a/internal/lsp/source/completion/util.go b/internal/lsp/source/completion/util.go deleted file mode 100644 index 505c7e256..000000000 --- a/internal/lsp/source/completion/util.go +++ /dev/null @@ -1,326 +0,0 @@ -// 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/internal/lsp/diff" - "golang.org/x/tools/internal/lsp/protocol" - "golang.org/x/tools/internal/lsp/source" -) - -// 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 map[*types.Struct]bool - - var visit func(T types.Type) - visit = func(T types.Type) { - if T, ok := source.Deref(T).Underlying().(*types.Struct); ok { - if seen[T] { - return - } - - for i := 0; i < T.NumFields(); i++ { - f := T.Field(i) - fn(f) - if f.Anonymous() { - if seen == nil { - // Lazily create "seen" since it is only needed for - // embedded structs. - seen = make(map[*types.Struct]bool) - } - seen[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 -} - -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) { - rng := source.NewMappedRange(c.snapshot.FileSet(), c.mapper, from, to) - spn, err := rng.Span() - if err != nil { - return nil, err - } - return source.ToProtocolEdits(c.mapper, []diff.TextEdit{{ - Span: spn, - NewText: newText, - }}) -} diff --git a/internal/lsp/source/completion/util_test.go b/internal/lsp/source/completion/util_test.go deleted file mode 100644 index c94d279fb..000000000 --- a/internal/lsp/source/completion/util_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// 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) - } - } -} |