1
0
mirror of https://github.com/golang/go synced 2024-11-18 10:54:40 -07:00

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>
This commit is contained in:
Muir Manders 2020-08-05 22:17:55 -07:00 committed by Rebecca Stambler
parent 383b97c0b5
commit 19738be007
3 changed files with 65 additions and 11 deletions

View File

@ -1576,6 +1576,10 @@ type typeNameInference struct {
// 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 @@ func breaksExpectedTypeInference(n ast.Node) bool {
// 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 @@ Nodes:
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 @@ Nodes:
}
return typeNameInference{
wantTypeName: wantTypeName,
wantComparable: wantComparable,
modifiers: modifiers,
assertableFrom: assertableFrom,
wantTypeName: wantTypeName,
wantComparable: wantComparable,
modifiers: modifiers,
assertableFrom: assertableFrom,
seenTypeSwitchCases: seenTypeSwitchCases,
}
}
@ -2070,6 +2093,7 @@ func (c *candidate) anyCandType(f func(t types.Type, addressable bool) bool) boo
}
// 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 @@ func (c *completer) matchingTypeName(cand *candidate) bool {
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 @@ func (c *completer) matchingTypeName(cand *candidate) bool {
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
}

View File

@ -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 @@ func _() {
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)
}
}

View File

@ -6,7 +6,7 @@ CompletionSnippetCount = 81
UnimportedCompletionsCount = 6
DeepCompletionsCount = 5
FuzzyCompletionsCount = 8
RankedCompletionsCount = 128
RankedCompletionsCount = 130
CaseSensitiveCompletionsCount = 4
DiagnosticsCount = 44
FoldingRangesCount = 2