internal/lsp: fix unimported completions with -mod=readonly

When -mod=readonly and GOPROXY=off are set, the newly imported package
is not type-checked with the new import until it is reflected in the
go.mod file. In such cases, we can continue treating the package as
unimported and get symbols through unimported completions.

Fixes golang/go#43339

Change-Id: I864c2c6738b537093c0670a266e9030af33f2d36
Reviewed-on: https://go-review.googlesource.com/c/tools/+/280095
Trust: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
This commit is contained in:
Rebecca Stambler 2020-12-23 22:04:13 -05:00
Родитель 0661ca7ea1
Коммит 84d76fe320
4 изменённых файлов: 96 добавлений и 0 удалений

Просмотреть файл

@ -213,3 +213,60 @@ func compareCompletionResults(want []string, gotItems []protocol.CompletionItem)
return ""
}
func TestUnimportedCompletion(t *testing.T) {
testenv.NeedsGo1Point(t, 14)
const mod = `
-- go.mod --
module mod.com
go 1.12
-- main.go --
package main
func main() {
_ = blah
}
`
withOptions(
ProxyFiles(proxy),
).run(t, mod, func(t *testing.T, env *Env) {
// Explicitly download example.com so it's added to the module cache
// and offered as an unimported completion.
env.RunGoCommand("get", "example.com@v1.2.3")
env.RunGoCommand("mod", "tidy")
// Trigger unimported completions for the example.com/blah package.
env.OpenFile("main.go")
pos := env.RegexpSearch("main.go", "ah")
completions := env.Completion("main.go", pos)
if len(completions.Items) == 0 {
t.Fatalf("no completion items")
}
env.AcceptCompletion("main.go", pos, completions.Items[0])
// Trigger completions once again for the blah.<> selector.
env.RegexpReplace("main.go", "_ = blah", "_ = blah.")
env.Await(
CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChange), 2),
)
pos = env.RegexpSearch("main.go", "\n}")
completions = env.Completion("main.go", pos)
if len(completions.Items) != 1 {
t.Fatalf("expected 1 completion item, got %v", len(completions.Items))
}
item := completions.Items[0]
if item.Label != "Name" {
t.Fatalf("expected completion item blah.Name, got %v", item.Label)
}
env.AcceptCompletion("main.go", pos, item)
// Await the diagnostics to add example.com/blah to the go.mod file.
env.SaveBufferWithoutActions("main.go")
env.Await(
env.DiagnosticAtRegexp("go.mod", "module mod.com"),
env.DiagnosticAtRegexp("main.go", `"example.com/blah"`),
)
})
}

Просмотреть файл

@ -331,6 +331,15 @@ func (e *Env) Completion(path string, pos fake.Pos) *protocol.CompletionList {
return completions
}
// AcceptCompletion accepts a completion for the given item at the given
// position.
func (e *Env) AcceptCompletion(path string, pos fake.Pos, item protocol.CompletionItem) {
e.T.Helper()
if err := e.Editor.AcceptCompletion(e.Ctx, path, pos, item); err != nil {
e.T.Fatal(err)
}
}
// CodeAction calls testDocument/codeAction for the given path, and calls
// t.Fatal if there are errors.
func (e *Env) CodeAction(path string) []protocol.CodeAction {

Просмотреть файл

@ -907,6 +907,23 @@ func (e *Editor) Completion(ctx context.Context, path string, pos Pos) (*protoco
return completions, nil
}
// AcceptCompletion accepts a completion for the given item at the given
// position.
func (e *Editor) AcceptCompletion(ctx context.Context, path string, pos Pos, item protocol.CompletionItem) error {
if e.Server == nil {
return nil
}
e.mu.Lock()
defer e.mu.Unlock()
_, ok := e.buffers[path]
if !ok {
return fmt.Errorf("buffer %q is not open", path)
}
return e.editBufferLocked(ctx, path, convertEdits(append([]protocol.TextEdit{
*item.TextEdit,
}, item.AdditionalTextEdits...)))
}
// References executes a reference request on the server.
func (e *Editor) References(ctx context.Context, path string, pos Pos) ([]protocol.Location, error) {
if e.Server == nil {

Просмотреть файл

@ -1070,6 +1070,19 @@ func (c *completer) selector(ctx context.Context, sel *ast.SelectorExpr) error {
// 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
}
}
candidates := c.packageMembers(pkgName.Imported(), stdScore, nil)
for _, cand := range candidates {
c.deepState.enqueue(cand)