internal/lsp/source: improve type switch case completion

Now we will filter out the types already used in other case
statements:

    switch ast.Node(nil).(type) {
    case *ast.Ident:
    case *ast.I<> // don't offer "Ident" since it has been used
    }

Note that the implementation was not able to use a map to track the
seen types.Types because we build up types.Type entries dynamically
when searching for completions (e.g. types.NewPointer() to make a
pointer type). We must use types.Identical() instead of direct pointer
equality.

Change-Id: I316638bb48bfd6802e2caea671f297d640291010
Reviewed-on: https://go-review.googlesource.com/c/tools/+/247098
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go
index c0362be..5010e18 100644
--- a/internal/lsp/source/completion.go
+++ b/internal/lsp/source/completion.go
@@ -1576,6 +1576,10 @@
 
 	// wantComparable is true if we want a comparable type.
 	wantComparable bool
+
+	// seenTypeSwitchCases tracks types that have already been used by
+	// the containing type switch.
+	seenTypeSwitchCases []types.Type
 }
 
 // expectedCandidate returns information about the expected candidate
@@ -1885,10 +1889,11 @@
 // expectTypeName returns information about the expected type name at position.
 func expectTypeName(c *completer) typeNameInference {
 	var (
-		wantTypeName   bool
-		wantComparable bool
-		modifiers      []typeModifier
-		assertableFrom types.Type
+		wantTypeName        bool
+		wantComparable      bool
+		modifiers           []typeModifier
+		assertableFrom      types.Type
+		seenTypeSwitchCases []types.Type
 	)
 
 Nodes:
@@ -1914,6 +1919,23 @@
 					return true
 				})
 				wantTypeName = true
+
+				// Track the types that have already been used in this
+				// switch's case statements so we don't recommend them.
+				for _, e := range swtch.Body.List {
+					for _, typeExpr := range e.(*ast.CaseClause).List {
+						// Skip if type expression contains pos. We don't want to
+						// count it as already used if the user is completing it.
+						if typeExpr.Pos() < c.pos && c.pos <= typeExpr.End() {
+							continue
+						}
+
+						if t := c.pkg.GetTypesInfo().TypeOf(typeExpr); t != nil {
+							seenTypeSwitchCases = append(seenTypeSwitchCases, t)
+						}
+					}
+				}
+
 				break Nodes
 			}
 			return typeNameInference{}
@@ -1985,10 +2007,11 @@
 	}
 
 	return typeNameInference{
-		wantTypeName:   wantTypeName,
-		wantComparable: wantComparable,
-		modifiers:      modifiers,
-		assertableFrom: assertableFrom,
+		wantTypeName:        wantTypeName,
+		wantComparable:      wantComparable,
+		modifiers:           modifiers,
+		assertableFrom:      assertableFrom,
+		seenTypeSwitchCases: seenTypeSwitchCases,
 	}
 }
 
@@ -2070,6 +2093,7 @@
 }
 
 // matchingCandidate reports whether cand matches our type inferences.
+// It mutates cand's score in certain cases.
 func (c *completer) matchingCandidate(cand *candidate) bool {
 	if isTypeName(cand.obj) {
 		return c.matchingTypeName(cand)
@@ -2289,6 +2313,14 @@
 			return false
 		}
 
+		// Skip this type if it has already been used in another type
+		// switch case.
+		for _, seen := range c.inference.typeName.seenTypeSwitchCases {
+			if types.Identical(candType, seen) {
+				return false
+			}
+		}
+
 		// We can expect a type name and have an expected type in cases like:
 		//
 		//   var foo []int
@@ -2303,11 +2335,13 @@
 		return true
 	}
 
-	if typeMatches(cand.obj.Type()) {
+	t := cand.obj.Type()
+
+	if typeMatches(t) {
 		return true
 	}
 
-	if typeMatches(types.NewPointer(cand.obj.Type())) {
+	if !isInterface(t) && typeMatches(types.NewPointer(t)) {
 		cand.makePointer = true
 		return true
 	}
diff --git a/internal/lsp/testdata/lsp/primarymod/rank/type_switch_rank.go.in b/internal/lsp/testdata/lsp/primarymod/rank/type_switch_rank.go.in
index 293025f..1ed12b7 100644
--- a/internal/lsp/testdata/lsp/primarymod/rank/type_switch_rank.go.in
+++ b/internal/lsp/testdata/lsp/primarymod/rank/type_switch_rank.go.in
@@ -1,5 +1,10 @@
 package rank
 
+import (
+	"fmt"
+	"go/ast"
+)
+
 func _() {
 	type basket int   //@item(basket, "basket", "int", "type")
 	var banana string //@item(banana, "banana", "string", "var")
@@ -8,4 +13,19 @@
 	case b: //@complete(":", basket)
 		b //@complete(" //", banana, basket)
 	}
+
+	Ident  //@item(astIdent, "Ident", "struct{...}", "struct")
+	IfStmt //@item(astIfStmt, "IfStmt", "struct{...}", "struct")
+
+	switch ast.Node(nil).(type) {
+	case *ast.Ident:
+	case *ast.I: //@rank(":", astIfStmt, astIdent)
+	}
+
+	Stringer   //@item(fmtStringer, "Stringer", "interface{...}", "interface")
+	GoStringer //@item(fmtGoStringer, "GoStringer", "interface{...}", "interface")
+
+	switch interface{}(nil).(type) {
+	case fmt.Stringer: //@rank(":", fmtStringer, fmtGoStringer)
+	}
 }
diff --git a/internal/lsp/testdata/lsp/summary.txt.golden b/internal/lsp/testdata/lsp/summary.txt.golden
index 1b3e050..493b5f0 100644
--- a/internal/lsp/testdata/lsp/summary.txt.golden
+++ b/internal/lsp/testdata/lsp/summary.txt.golden
@@ -6,7 +6,7 @@
 UnimportedCompletionsCount = 6
 DeepCompletionsCount = 5
 FuzzyCompletionsCount = 8
-RankedCompletionsCount = 128
+RankedCompletionsCount = 130
 CaseSensitiveCompletionsCount = 4
 DiagnosticsCount = 44
 FoldingRangesCount = 2