gopls/internal/lsp/source: fix panic in quickParse
Fix a panic in quickParse resulting from a malformed synthetic selector
node created during completion.
Add a completion test in the new marker framework.
Fixes golang/go#59096
Change-Id: Icc3c3cec3084fb76aaecf88574bbd5cbee2a8afa
Reviewed-on: https://go-review.googlesource.com/c/tools/+/477255
gopls-CI: kokoro <noreply+kokoro@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Alan Donovan <adonovan@google.com>
diff --git a/gopls/internal/lsp/regtest/marker.go b/gopls/internal/lsp/regtest/marker.go
index fb7454d..8b4c61e 100644
--- a/gopls/internal/lsp/regtest/marker.go
+++ b/gopls/internal/lsp/regtest/marker.go
@@ -22,6 +22,7 @@
"strings"
"testing"
+ "github.com/google/go-cmp/cmp"
"golang.org/x/tools/go/expect"
"golang.org/x/tools/gopls/internal/hooks"
"golang.org/x/tools/gopls/internal/lsp/cache"
@@ -115,6 +116,9 @@
//
// The following markers are supported within marker tests:
//
+// - complete(location, ...labels): specifies expected completion results at
+// the given location.
+//
// - diag(location, regexp): specifies an expected diagnostic matching the
// given regexp at the given location. The test runner requires
// a 1:1 correspondence between observed diagnostics and diag annotations
@@ -455,6 +459,7 @@
// Marker funcs should not mutate the test environment (e.g. via opening files
// or applying edits in the editor).
var markerFuncs = map[string]markerFunc{
+ "complete": makeMarkerFunc(completeMarker),
"def": makeMarkerFunc(defMarker),
"diag": makeMarkerFunc(diagMarker),
"hover": makeMarkerFunc(hoverMarker),
@@ -1091,6 +1096,26 @@
// ---- marker functions ----
+// completeMarker implements the @complete marker, running
+// textDocument/completion at the given src location and asserting that the
+// results match the expected results.
+//
+// TODO(rfindley): for now, this is just a quick check against the expected
+// completion labels. We could do more by assembling richer completion items,
+// as is done in the old marker tests. Does that add value? If so, perhaps we
+// should support a variant form of the argument, labelOrItem, which allows the
+// string form or item form.
+func completeMarker(mark marker, src protocol.Location, want ...string) {
+ list := mark.run.env.Completion(src)
+ var got []string
+ for _, item := range list.Items {
+ got = append(got, item.Label)
+ }
+ if diff := cmp.Diff(want, got); diff != "" {
+ mark.errorf("Completion(...) returned unexpect results (-want +got):\n%s", diff)
+ }
+}
+
// defMarker implements the @godef marker, running textDocument/definition at
// the given src location and asserting that there is exactly one resulting
// location, matching dst.
diff --git a/gopls/internal/lsp/source/completion/completion.go b/gopls/internal/lsp/source/completion/completion.go
index 315541e..e95c9eb 100644
--- a/gopls/internal/lsp/source/completion/completion.go
+++ b/gopls/internal/lsp/source/completion/completion.go
@@ -658,7 +658,11 @@
// recv.‸(arg)
case *ast.TypeAssertExpr:
// Create a fake selector expression.
- return c.selector(ctx, &ast.SelectorExpr{X: n.X})
+ //
+ // The name "_" is the convention used by go/parser to represent phantom
+ // selectors.
+ sel := &ast.Ident{NamePos: n.X.End() + token.Pos(len(".")), Name: "_"}
+ return c.selector(ctx, &ast.SelectorExpr{X: n.X, Sel: sel})
case *ast.SelectorExpr:
return c.selector(ctx, n)
// At the file scope, only keywords are allowed.
diff --git a/gopls/internal/regtest/marker/testdata/completion/issue59096.txt b/gopls/internal/regtest/marker/testdata/completion/issue59096.txt
new file mode 100644
index 0000000..44bb10a
--- /dev/null
+++ b/gopls/internal/regtest/marker/testdata/completion/issue59096.txt
@@ -0,0 +1,18 @@
+This test exercises the panic in golang/go#59096: completing at a syntactic
+type-assert expression was panicking because gopls was translating it into
+a (malformed) selector expr.
+
+-- go.mod --
+module example.com
+
+-- a/a.go --
+package a
+
+func _() {
+ b.(foo) //@complete(re"b.()", "B"), diag("b", re"(undefined|undeclared name): b")
+}
+
+-- b/b.go --
+package b
+
+const B = 0