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