// 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 source import ( "go/ast" "go/token" "go/types" "strings" "unicode" "golang.org/x/tools/internal/lsp/diff" "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/lsp/snippet" "golang.org/x/tools/internal/telemetry/log" ) // literal generates composite literal, function literal, and make() // completion items. func (c *completer) literal(literalType types.Type, imp *importInfo) { if !c.opts.Literal { return } expType := c.expectedType.objType if c.expectedType.variadic { // Don't offer literal slice candidates for variadic arguments. // For example, don't offer "[]interface{}{}" in "fmt.Print(<>)". if c.expectedType.matchesVariadic(literalType) { return } // Otherwise, consider our expected type to be the variadic // element type, not the slice type. if slice, ok := expType.(*types.Slice); ok { expType = slice.Elem() } } // 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 := deref(expType).(*types.Named); !named { return } } // Check if an object of type literalType would match our expected type. cand := candidate{ obj: c.fakeObj(literalType), addressable: true, } if !c.matchingCandidate(&cand) { 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 { log.Error(c.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.takeAddress { 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 := referenceEdit(c.snapshot.View().Session().Cache().FileSet(), c.mapper, sel) if err != nil { log.Error(c.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 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 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.takeAddress && 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.takeAddress && score >= 0 && !isInterface(expType) { switch t := literalType.Underlying().(type) { case *types.Signature: c.functionLiteral(t, float64(score)) } } } // referenceEdit produces text edits that prepend a "&" operator to the // specified node. func referenceEdit(fset *token.FileSet, m *protocol.ColumnMapper, node ast.Node) ([]protocol.TextEdit, error) { rng := newMappedRange(fset, m, node.Pos(), node.Pos()) spn, err := rng.Span() if err != nil { return nil, err } return ToProtocolEdits(m, []diff.TextEdit{{ Span: spn, NewText: "&", }}) } // 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(sig *types.Signature, matchScore float64) { snip := &snippet.Builder{} snip.WriteText("func(") seenParamNames := make(map[string]bool) for i := 0; i < sig.Params().Len(); i++ { if i > 0 { snip.WriteText(", ") } p := sig.Params().At(i) name := p.Name() // If the parameter has no name in the signature, we need to try // come up with a parameter name. if name == "" { // Our parameter names are guesses, so they must be placeholders // for easy correction. If placeholders are disabled, don't // offer the completion. if !c.opts.Placeholders { return } // Try abbreviating named types. If the type isn't named, or the // abbreviation duplicates a previous name, give up and use // "_". The user will have to provide a name for this parameter // in order to use it. if named, ok := deref(p.Type()).(*types.Named); ok { name = abbreviateCamel(named.Obj().Name()) if seenParamNames[name] { name = "_" } else { seenParamNames[name] = true } } else { 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 := types.TypeString(p.Type(), 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(types.TypeString(r.Type(), 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, }) } // abbreviateCamel abbreviates camel case identifiers into // abbreviations. For example, "fooBar" is abbreviated "fb". func abbreviateCamel(s string) string { var ( b strings.Builder useNextUpper bool ) for i, r := range s { 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) { 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, }) }