1
0
mirror of https://github.com/golang/go synced 2024-11-18 20:44:45 -07:00

internal/lsp/source: propose exports for unimported packages

When a user completes rand.<>, propose rand.Seed (from math/rand) and
rand.Prime (from crypto/rand), etc.

Because we don't necessarily have type checking information for
unimported packages, I had to add shortcut cases to a number of
functions around the completion code. Better suggestions welcome.

Change-Id: I7822dc75c86b24156963e7bdd959443f4f2748b1
Reviewed-on: https://go-review.googlesource.com/c/tools/+/204819
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Muir Manders <muir@mnd.rs>
This commit is contained in:
Heschi Kreinick 2019-11-01 13:50:21 -04:00
parent 8266eea4ea
commit 88c5938121
7 changed files with 84 additions and 20 deletions

View File

@ -577,11 +577,24 @@ func (c *completer) selector(sel *ast.SelectorExpr) error {
// Invariant: sel is a true selector. // Invariant: sel is a true selector.
tv, ok := c.pkg.GetTypesInfo().Types[sel.X] tv, ok := c.pkg.GetTypesInfo().Types[sel.X]
if !ok { if ok {
return errors.Errorf("cannot resolve %s", sel.X) return c.methodsAndFields(tv.Type, tv.Addressable(), nil)
} }
return c.methodsAndFields(tv.Type, tv.Addressable(), nil) // Try unimported packages.
if id, ok := sel.X.(*ast.Ident); ok {
pkgExports, err := PackageExports(c.ctx, c.view, id.Name, c.filename)
if err != nil {
return err
}
for _, pkgExport := range pkgExports {
pkg := types.NewPackage(pkgExport.Fix.StmtInfo.ImportPath, pkgExport.Fix.IdentName)
for _, export := range pkgExport.Exports {
c.found(types.NewVar(0, pkg, export, nil), 0.07, &pkgExport.Fix.StmtInfo)
}
}
}
return nil
} }
func (c *completer) packageMembers(pkg *types.Package, imp *imports.ImportInfo) { func (c *completer) packageMembers(pkg *types.Package, imp *imports.ImportInfo) {
@ -1347,6 +1360,9 @@ func (c *completer) matchingCandidate(cand *candidate) bool {
} }
objType := cand.obj.Type() objType := cand.obj.Type()
if objType == nil {
return true
}
// Default to invoking *types.Func candidates. This is so function // Default to invoking *types.Func candidates. This is so function
// completions in an empty statement (or other cases with no expected type) // completions in an empty statement (or other cases with no expected type)

View File

@ -39,6 +39,9 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
snip *snippet.Builder snip *snippet.Builder
protocolEdits []protocol.TextEdit protocolEdits []protocol.TextEdit
) )
if obj.Type() == nil {
detail = ""
}
// expandFuncCall mutates the completion label, detail, and snippet // expandFuncCall mutates the completion label, detail, and snippet
// to that of an invocation of sig. // to that of an invocation of sig.
@ -64,6 +67,9 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
} else { } else {
kind = protocol.VariableCompletion kind = protocol.VariableCompletion
} }
if obj.Type() == nil {
break
}
if sig, ok := obj.Type().Underlying().(*types.Signature); ok && cand.expandFuncCall { if sig, ok := obj.Type().Underlying().(*types.Signature); ok && cand.expandFuncCall {
expandFuncCall(sig) expandFuncCall(sig)
@ -91,11 +97,20 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
// If this candidate needs an additional import statement, // If this candidate needs an additional import statement,
// add the additional text edits needed. // add the additional text edits needed.
if cand.imp != nil {
addlEdits, err := c.importEdits(cand.imp) addlEdits, err := c.importEdits(cand.imp)
if err != nil { if err != nil {
return CompletionItem{}, err return CompletionItem{}, err
} }
protocolEdits = append(protocolEdits, addlEdits...) protocolEdits = append(protocolEdits, addlEdits...)
if kind != protocol.ModuleCompletion {
if detail != "" {
detail += " "
}
detail += fmt.Sprintf("(from %q)", cand.imp.ImportPath)
}
}
detail = strings.TrimPrefix(detail, "untyped ") detail = strings.TrimPrefix(detail, "untyped ")
item := CompletionItem{ item := CompletionItem{
@ -146,7 +161,7 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
return item, nil return item, nil
} }
// importEdits produces the text eddits necessary to add the given import to the current file. // importEdits produces the text edits necessary to add the given import to the current file.
func (c *completer) importEdits(imp *imports.ImportInfo) ([]protocol.TextEdit, error) { func (c *completer) importEdits(imp *imports.ImportInfo) ([]protocol.TextEdit, error) {
if imp == nil { if imp == nil {
return nil, nil return nil, nil

View File

@ -158,6 +158,10 @@ func (c *completer) deepSearch(obj types.Object, imp *imports.ImportInfo) {
return return
} }
if obj.Type() == nil {
return
}
// Don't search embedded fields because they were already included in their // Don't search embedded fields because they were already included in their
// parent's fields. // parent's fields.
if v, ok := obj.(*types.Var); ok && v.Embedded() { if v, ok := obj.(*types.Var); ok && v.Embedded() {

View File

@ -240,11 +240,8 @@ func AllImportsFixes(ctx context.Context, view View, f File) (edits []protocol.T
return edits, editsPerFix, nil return edits, editsPerFix, nil
} }
// AllImportsFixes formats f for each possible fix to the imports. // CandidateImports returns every import that could be added to filename.
// In addition to returning the result of applying all edits, func CandidateImports(ctx context.Context, view View, filename string) ([]imports.ImportFix, error) {
// it returns a list of fixes that could be applied to the file, with the
// corresponding TextEdits that would be needed to apply that fix.
func CandidateImports(ctx context.Context, view View, filename string) (pkgs []imports.ImportFix, err error) {
ctx, done := trace.StartSpan(ctx, "source.CandidateImports") ctx, done := trace.StartSpan(ctx, "source.CandidateImports")
defer done() defer done()
@ -257,16 +254,41 @@ func CandidateImports(ctx context.Context, view View, filename string) (pkgs []i
TabIndent: true, TabIndent: true,
TabWidth: 8, TabWidth: 8,
} }
var imps []imports.ImportFix
importFn := func(opts *imports.Options) error { importFn := func(opts *imports.Options) error {
pkgs, err = imports.GetAllCandidates(filename, opts) var err error
imps, err = imports.GetAllCandidates(filename, opts)
return err return err
} }
err = view.RunProcessEnvFunc(ctx, importFn, options) err := view.RunProcessEnvFunc(ctx, importFn, options)
if err != nil { return imps, err
return nil, err
} }
return pkgs, nil // PackageExports returns all the packages named pkg that could be imported by
// filename, and their exports.
func PackageExports(ctx context.Context, view View, pkg, filename string) ([]imports.PackageExport, error) {
ctx, done := trace.StartSpan(ctx, "source.PackageExports")
defer done()
options := &imports.Options{
// Defaults.
AllErrors: true,
Comments: true,
Fragment: true,
FormatOnly: false,
TabIndent: true,
TabWidth: 8,
}
var pkgs []imports.PackageExport
importFn := func(opts *imports.Options) error {
var err error
pkgs, err = imports.GetPackageExports(pkg, filename, opts)
return err
}
err := view.RunProcessEnvFunc(ctx, importFn, options)
return pkgs, err
} }
// hasParseErrors returns true if the given file has parse errors. // hasParseErrors returns true if the given file has parse errors.

View File

@ -1,7 +1,7 @@
-- summary -- -- summary --
CompletionsCount = 213 CompletionsCount = 213
CompletionSnippetCount = 40 CompletionSnippetCount = 40
UnimportedCompletionsCount = 1 UnimportedCompletionsCount = 2
DeepCompletionsCount = 5 DeepCompletionsCount = 5
FuzzyCompletionsCount = 7 FuzzyCompletionsCount = 7
RankedCompletionsCount = 8 RankedCompletionsCount = 8

View File

@ -2,6 +2,7 @@ package unimported
func _() { func _() {
//@unimported("", bytes, context, cryptoslashrand, time, unsafe, externalpackage) //@unimported("", bytes, context, cryptoslashrand, time, unsafe, externalpackage)
bytes. //@unimported(" /", bytesbuffer)
} }
// Create markers for unimported std lib packages. Only for use by this test. // Create markers for unimported std lib packages. Only for use by this test.
@ -11,3 +12,5 @@ func _() {
/* time */ //@item(time, "time", "\"time\"", "package") /* time */ //@item(time, "time", "\"time\"", "package")
/* unsafe */ //@item(unsafe, "unsafe", "\"unsafe\"", "package") /* unsafe */ //@item(unsafe, "unsafe", "\"unsafe\"", "package")
/* pkg */ //@item(externalpackage, "pkg", "\"example.com/extramodule/pkg\"", "package" ) /* pkg */ //@item(externalpackage, "pkg", "\"example.com/extramodule/pkg\"", "package" )
/* bytes.Buffer */ //@item(bytesbuffer, "Buffer", "(from \"bytes\")", "var" )

View File

@ -19,7 +19,7 @@ func ToProtocolCompletionItems(items []source.CompletionItem) []protocol.Complet
} }
func ToProtocolCompletionItem(item source.CompletionItem) protocol.CompletionItem { func ToProtocolCompletionItem(item source.CompletionItem) protocol.CompletionItem {
return protocol.CompletionItem{ pItem := protocol.CompletionItem{
Label: item.Label, Label: item.Label,
Kind: item.Kind, Kind: item.Kind,
Detail: item.Detail, Detail: item.Detail,
@ -29,6 +29,10 @@ func ToProtocolCompletionItem(item source.CompletionItem) protocol.CompletionIte
NewText: item.Snippet(), NewText: item.Snippet(),
}, },
} }
if pItem.InsertText == "" {
pItem.InsertText = pItem.Label
}
return pItem
} }
func FilterBuiltins(items []protocol.CompletionItem) []protocol.CompletionItem { func FilterBuiltins(items []protocol.CompletionItem) []protocol.CompletionItem {