internal/lsp: treat completion documentation errors as actual errors

Also, handle *ast.StarExpr in the identifier code. This fixes a specific
case with deep completions and documentation.

Change-Id: I630ae4e8f1c123ba1fdea85e6862ae93396e2cd4
Reviewed-on: https://go-review.googlesource.com/c/tools/+/194564
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index 4ca0b7d..1b4b684 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -151,7 +151,6 @@
 			}
 		}
 	}
-
 	modified.InsertTextFormat = protocol.SnippetTextFormat
 	for _, usePlaceholders := range []bool{true, false} {
 		for src, want := range snippets {
diff --git a/internal/lsp/source/completion_format.go b/internal/lsp/source/completion_format.go
index b43a59a..89d4f2a 100644
--- a/internal/lsp/source/completion_format.go
+++ b/internal/lsp/source/completion_format.go
@@ -115,26 +115,26 @@
 	if !c.opts.Documentation {
 		return item, nil
 	}
-	declRange, err := objToMappedRange(c.ctx, c.view, obj)
-	if err != nil {
-		return item, nil
-	}
-	pos := c.view.Session().Cache().FileSet().Position(declRange.spanRange.Start)
+	pos := c.view.Session().Cache().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.FileURI(pos.Filename)
 	_, file, pkg, err := c.pkg.FindFile(c.ctx, uri, obj.Pos())
-	if file == nil || pkg == nil {
-		return item, nil
+	if err != nil {
+		return CompletionItem{}, err
 	}
 	ident, err := findIdentifier(c.ctx, c.view, []Package{pkg}, file, obj.Pos())
 	if err != nil {
-		return item, nil
+		return CompletionItem{}, err
 	}
 	hover, err := ident.Hover(c.ctx)
 	if err != nil {
-		return item, nil
+		return CompletionItem{}, err
 	}
 	item.Documentation = hover.Synopsis
 	if c.opts.FullDocumentation {
diff --git a/internal/lsp/source/identifier.go b/internal/lsp/source/identifier.go
index 2e286b0..993b82e 100644
--- a/internal/lsp/source/identifier.go
+++ b/internal/lsp/source/identifier.go
@@ -104,18 +104,13 @@
 		}
 	}
 	result := &IdentifierInfo{
-		View: view,
-		File: ph,
-		qf:   qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo()),
-		pkgs: pkgs,
+		View:  view,
+		File:  ph,
+		qf:    qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo()),
+		pkgs:  pkgs,
+		ident: searchForIdent(path[0]),
 	}
-
-	switch node := path[0].(type) {
-	case *ast.Ident:
-		result.ident = node
-	case *ast.SelectorExpr:
-		result.ident = node.Sel
-	}
+	// No identifier at the given position.
 	if result.ident == nil {
 		return nil, nil
 	}
@@ -202,6 +197,18 @@
 	return result, nil
 }
 
+func searchForIdent(n ast.Node) *ast.Ident {
+	switch node := n.(type) {
+	case *ast.Ident:
+		return node
+	case *ast.SelectorExpr:
+		return node.Sel
+	case *ast.StarExpr:
+		return searchForIdent(node.X)
+	}
+	return nil
+}
+
 func typeToObject(typ types.Type) types.Object {
 	switch typ := typ.(type) {
 	case *types.Named:
diff --git a/internal/lsp/testdata/deepcomplete/deep_complete.go b/internal/lsp/testdata/deepcomplete/deep_complete.go
index 66d4859..61b8cd1 100644
--- a/internal/lsp/testdata/deepcomplete/deep_complete.go
+++ b/internal/lsp/testdata/deepcomplete/deep_complete.go
@@ -33,11 +33,12 @@
 }
 
 func _() {
+	// deepCircle is circular.
 	type deepCircle struct {
 		*deepCircle
 	}
 	var circle deepCircle   //@item(deepCircle, "circle", "deepCircle", "var")
-	circle.deepCircle       //@item(deepCircleField, "circle.deepCircle", "*deepCircle", "field")
+	circle.deepCircle       //@item(deepCircleField, "circle.deepCircle", "*deepCircle", "field", "deepCircle is circular.")
 	var _ deepCircle = circ //@complete(" //", deepCircle, deepCircleField)
 }