internal/lsp: fix hover for implicit type switch variable declarations

There was a bug in the hover for type switch variables. For example:

var x interface{}
switch y := x.(type) {
    case string:
    case int:
}

Hovering over y would previously show "var y string", because y's object
would be mapped to the first types.Object in the type switch. Now we
show the hover for y as "var y interface{}", since it's not yet in the
cases.

Change-Id: Ia9bd0afc4ddbb9d33bbd0c78fa32ffa75836a326
Reviewed-on: https://go-review.googlesource.com/c/tools/+/244497
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/internal/lsp/source/hover.go b/internal/lsp/source/hover.go
index a3ddc89..e19dda1 100644
--- a/internal/lsp/source/hover.go
+++ b/internal/lsp/source/hover.go
@@ -96,7 +96,13 @@
 		}
 		h.Signature = b.String()
 	case types.Object:
-		h.Signature = objectString(x, i.qf)
+		// If the variable is implicitly declared in a type switch, we need to
+		// manually generate its object string.
+		if v, ok := x.(*types.Var); ok && i.Declaration.typeSwitchImplicit {
+			h.Signature = fmt.Sprintf("var %s %s", v.Name(), types.TypeString(i.enclosing, i.qf))
+		} else {
+			h.Signature = objectString(x, i.qf)
+		}
 	}
 	if obj := i.Declaration.obj; obj != nil {
 		h.SingleLine = objectString(obj, i.qf)
diff --git a/internal/lsp/source/identifier.go b/internal/lsp/source/identifier.go
index 2d49c1d..25ea2d0 100644
--- a/internal/lsp/source/identifier.go
+++ b/internal/lsp/source/identifier.go
@@ -43,6 +43,10 @@
 	MappedRange []mappedRange
 	node        ast.Node
 	obj         types.Object
+
+	// typeSwitchImplicit indicates that the declaration is in an implicit
+	// type switch.
+	typeSwitchImplicit bool
 }
 
 // Identifier returns identifier information for a position
@@ -152,9 +156,10 @@
 
 	result.Declaration.obj = pkg.GetTypesInfo().ObjectOf(result.ident)
 	if result.Declaration.obj == nil {
-		// If there was no types.Object for the declaration, there might be an implicit local variable
-		// declaration in a type switch.
+		// If there was no types.Object for the declaration, there might be an
+		// implicit local variable declaration in a type switch.
 		if objs := typeSwitchImplicits(pkg, path); len(objs) > 0 {
+			result.Declaration.typeSwitchImplicit = true
 			// There is no types.Object for the declaration of an implicit local variable,
 			// but all of the types.Objects associated with the usages of this variable can be
 			// used to connect it back to the declaration.
@@ -244,6 +249,15 @@
 					return t.Type()
 				}
 			}
+		case *ast.TypeSwitchStmt:
+			// The right-hand side of a type switch should only have one
+			// element, and we need to track its type in order to generate
+			// hover information for implicit type switch variables.
+			if assign, ok := n.Assign.(*ast.AssignStmt); ok && len(assign.Rhs) == 1 {
+				if rhs := assign.Rhs[0].(*ast.TypeAssertExpr); ok {
+					return pkg.GetTypesInfo().TypeOf(rhs.X)
+				}
+			}
 		}
 	}
 	return nil
@@ -269,12 +283,10 @@
 	if err != nil {
 		return nil, err
 	}
-
 	posToDecl, err := ph.PosToDecl(ctx)
 	if err != nil {
 		return nil, err
 	}
-
 	return posToDecl[obj.Pos()], nil
 }
 
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index b1bc10e..417e1e6 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -514,7 +514,7 @@
 			return []byte(hover), nil
 		}))
 		if hover != expectHover {
-			t.Errorf("for %v got %q want %q", d.Src, hover, expectHover)
+			t.Errorf("hover for %s failed:\n%s", d.Src, tests.Diff(expectHover, hover))
 		}
 	}
 	if !d.OnlyHover {
diff --git a/internal/lsp/testdata/lsp/primarymod/godef/a/f.go b/internal/lsp/testdata/lsp/primarymod/godef/a/f.go
index d1b8c23..2d3eefc 100644
--- a/internal/lsp/testdata/lsp/primarymod/godef/a/f.go
+++ b/internal/lsp/testdata/lsp/primarymod/godef/a/f.go
@@ -5,11 +5,11 @@
 func TypeStuff() { //@Stuff
 	var x string
 
-	switch y := interface{}(x).(type) { //@mark(switchY, "y"),mark(switchStringY,"y"),godef("y", switchY)
-	case int:
-		fmt.Printf("%v", y) //@godef("y", switchY)
-	case string:
-		fmt.Printf("%v", y) //@godef("y", switchStringY)
+	switch y := interface{}(x).(type) { //@mark(switchY, "y"),godef("y", switchY)
+	case int: //@mark(intY, "int")
+		fmt.Printf("%v", y) //@hover("y", intY)
+	case string: //@mark(stringY, "string")
+		fmt.Printf("%v", y) //@hover("y", stringY)
 	}
 
 }
diff --git a/internal/lsp/testdata/lsp/primarymod/godef/a/f.go.golden b/internal/lsp/testdata/lsp/primarymod/godef/a/f.go.golden
index 1a220e0..6c84b4d 100644
--- a/internal/lsp/testdata/lsp/primarymod/godef/a/f.go.golden
+++ b/internal/lsp/testdata/lsp/primarymod/godef/a/f.go.golden
@@ -1,32 +1,14 @@
--- switchStringY-definition --
-godef/a/f.go:8:9-10: defined here as ```go
-var y string
+-- intY-hover --
+```go
+var y int
 ```
--- switchStringY-definition-json --
-{
-	"span": {
-		"uri": "file://godef/a/f.go",
-		"start": {
-			"line": 8,
-			"column": 9,
-			"offset": 76
-		},
-		"end": {
-			"line": 8,
-			"column": 10,
-			"offset": 77
-		}
-	},
-	"description": "```go\nvar y string\n```"
-}
-
--- switchStringY-hover --
+-- stringY-hover --
 ```go
 var y string
 ```
 -- switchY-definition --
 godef/a/f.go:8:9-10: defined here as ```go
-var y int
+var y interface{}
 ```
 -- switchY-definition-json --
 {
@@ -43,10 +25,10 @@
 			"offset": 77
 		}
 	},
-	"description": "```go\nvar y int\n```"
+	"description": "```go\nvar y interface{}\n```"
 }
 
 -- switchY-hover --
 ```go
-var y int
+var y interface{}
 ```
diff --git a/internal/lsp/testdata/lsp/primarymod/godef/b/e.go b/internal/lsp/testdata/lsp/primarymod/godef/b/e.go
index dc8fe6e..92037ed 100644
--- a/internal/lsp/testdata/lsp/primarymod/godef/b/e.go
+++ b/internal/lsp/testdata/lsp/primarymod/godef/b/e.go
@@ -19,3 +19,13 @@
 godef(bVar, Other)
 godef(bFunc, Things)
 */
+
+func _() {
+	var x interface{}      //@mark(eInterface, "interface{}")
+	switch x := x.(type) { //@hover("x", eInterface)
+	case string: //@mark(eString, "string")
+		fmt.Println(x) //@hover("x", eString)
+	case int: //@mark(eInt, "int")
+		fmt.Println(x) //@hover("x", eInt)
+	}
+}
diff --git a/internal/lsp/testdata/lsp/primarymod/godef/b/e.go.golden b/internal/lsp/testdata/lsp/primarymod/godef/b/e.go.golden
index f0bfe97..1e11a0f 100644
--- a/internal/lsp/testdata/lsp/primarymod/godef/b/e.go.golden
+++ b/internal/lsp/testdata/lsp/primarymod/godef/b/e.go.golden
@@ -130,3 +130,15 @@
 ```
 
 [`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)
+-- eInt-hover --
+```go
+var x int
+```
+-- eInterface-hover --
+```go
+var x interface{}
+```
+-- eString-hover --
+```go
+var x string
+```
diff --git a/internal/lsp/testdata/lsp/summary.txt.golden b/internal/lsp/testdata/lsp/summary.txt.golden
index 2009987..4c64565 100644
--- a/internal/lsp/testdata/lsp/summary.txt.golden
+++ b/internal/lsp/testdata/lsp/summary.txt.golden
@@ -13,7 +13,7 @@
 ImportCount = 8
 SuggestedFixCount = 31
 FunctionExtractionCount = 5
-DefinitionsCount = 53
+DefinitionsCount = 56
 TypeDefinitionsCount = 2
 HighlightsCount = 69
 ReferencesCount = 11