mirror of
https://github.com/golang/go
synced 2024-11-05 16:56:16 -07:00
internal/lsp/source: improve completion in switch cases
Now we downrank candidates that have already been used in other switch cases. For example: switch time.Now().Weekday() { case time.Monday: case time.<> // downrank time.Monday } It wasn't quite as simple as tracking the seen types.Objects. Consider this example: type foo struct { i int } var a, b foo switch 123 { case a.i: case <> } At <> we don't want to downrank "b.i" even though "i" is represented as the same types.Object for "a.i" and "b.i". To accommodate this, we track having seen ["a", "i"] together. We will downrank "a.i", but not "b.i". I applied only a minor 0.9 downranking when the candidate has already been used in another case clause. It is hard to know what the user is planning. For instance, in the preceding example the user could intend to write "a.i + 1", so we mustn't downrank "a.i" too much. Change-Id: I62debc5be3d5d310deb69d11770cf5f8bd9add1d Reviewed-on: https://go-review.googlesource.com/c/tools/+/247337 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:
parent
5bf02b21f1
commit
6f4f008689
@ -330,6 +330,17 @@ func (c *completer) found(ctx context.Context, cand candidate) {
|
|||||||
|
|
||||||
if c.matchingCandidate(&cand) {
|
if c.matchingCandidate(&cand) {
|
||||||
cand.score *= highScore
|
cand.score *= highScore
|
||||||
|
|
||||||
|
if c.seenInSwitchCase(&cand) {
|
||||||
|
// If this candidate matches an expression already used in a
|
||||||
|
// different case clause, downrank. We only downrank a little
|
||||||
|
// because the user could write something like:
|
||||||
|
//
|
||||||
|
// switch foo {
|
||||||
|
// case bar:
|
||||||
|
// case ba<>: // they may want "bar|baz" or "bar.baz()"
|
||||||
|
cand.score *= 0.9
|
||||||
|
}
|
||||||
} else if isTypeName(obj) {
|
} else if isTypeName(obj) {
|
||||||
// If obj is a *types.TypeName that didn't otherwise match, check
|
// If obj is a *types.TypeName that didn't otherwise match, check
|
||||||
// if a literal object of this type makes a good candidate.
|
// if a literal object of this type makes a good candidate.
|
||||||
@ -376,6 +387,18 @@ func (c *completer) found(ctx context.Context, cand candidate) {
|
|||||||
c.deepSearch(ctx, cand)
|
c.deepSearch(ctx, cand)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// seenInSwitchCase reports whether cand has already been seen in
|
||||||
|
// another switch case statement.
|
||||||
|
func (c *completer) seenInSwitchCase(cand *candidate) bool {
|
||||||
|
for _, chain := range c.inference.seenSwitchCases {
|
||||||
|
if c.objChainMatches(cand.obj, chain) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// candidate represents a completion candidate.
|
// candidate represents a completion candidate.
|
||||||
type candidate struct {
|
type candidate struct {
|
||||||
// obj is the types.Object to complete to.
|
// obj is the types.Object to complete to.
|
||||||
@ -835,6 +858,8 @@ const (
|
|||||||
|
|
||||||
// selector finds completions for the specified selector expression.
|
// selector finds completions for the specified selector expression.
|
||||||
func (c *completer) selector(ctx context.Context, sel *ast.SelectorExpr) error {
|
func (c *completer) selector(ctx context.Context, sel *ast.SelectorExpr) error {
|
||||||
|
c.inference.objChain = objChain(c.pkg.GetTypesInfo(), sel.X)
|
||||||
|
|
||||||
// Is sel a qualified identifier?
|
// Is sel a qualified identifier?
|
||||||
if id, ok := sel.X.(*ast.Ident); ok {
|
if id, ok := sel.X.(*ast.Ident); ok {
|
||||||
if pkgName, ok := c.pkg.GetTypesInfo().Uses[id].(*types.PkgName); ok {
|
if pkgName, ok := c.pkg.GetTypesInfo().Uses[id].(*types.PkgName); ok {
|
||||||
@ -1559,6 +1584,19 @@ type candidateInference struct {
|
|||||||
// foo(bar<>) // variadicAssignees=true
|
// foo(bar<>) // variadicAssignees=true
|
||||||
// foo(bar, baz<>) // variadicAssignees=false
|
// foo(bar, baz<>) // variadicAssignees=false
|
||||||
variadicAssignees bool
|
variadicAssignees bool
|
||||||
|
|
||||||
|
// seenSwitchCases tracks the expressions already used in the
|
||||||
|
// containing switch statement's cases. Each expression is tracked
|
||||||
|
// as a slice of objects. For example, "case foo.bar().baz:" is
|
||||||
|
// tracked as []types.Object{foo, bar, baz}. Tracking the entire
|
||||||
|
// "chain" allows us to differentiate "a.foo" and "b.foo" when "a"
|
||||||
|
// and "b" are the same type.
|
||||||
|
seenSwitchCases [][]types.Object
|
||||||
|
|
||||||
|
// objChain contains the chain of objects representing the
|
||||||
|
// surrounding *ast.SelectorExpr. For example, if we are completing
|
||||||
|
// "foo.bar.ba<>", objChain will contain []types.Object{foo, bar}.
|
||||||
|
objChain []types.Object
|
||||||
}
|
}
|
||||||
|
|
||||||
// typeNameInference holds information about the expected type name at
|
// typeNameInference holds information about the expected type name at
|
||||||
@ -1732,6 +1770,21 @@ Nodes:
|
|||||||
if swtch, ok := findSwitchStmt(c.path[i+1:], c.pos, node).(*ast.SwitchStmt); ok {
|
if swtch, ok := findSwitchStmt(c.path[i+1:], c.pos, node).(*ast.SwitchStmt); ok {
|
||||||
if tv, ok := c.pkg.GetTypesInfo().Types[swtch.Tag]; ok {
|
if tv, ok := c.pkg.GetTypesInfo().Types[swtch.Tag]; ok {
|
||||||
inf.objType = tv.Type
|
inf.objType = tv.Type
|
||||||
|
|
||||||
|
// Record which objects have already been used in the case
|
||||||
|
// statements so we don't suggest them again.
|
||||||
|
for _, cc := range swtch.Body.List {
|
||||||
|
for _, caseExpr := range cc.(*ast.CaseClause).List {
|
||||||
|
// Don't record the expression we are currently completing.
|
||||||
|
if caseExpr.Pos() < c.pos && c.pos <= caseExpr.End() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if objs := objChain(c.pkg.GetTypesInfo(), caseExpr); len(objs) > 0 {
|
||||||
|
inf.seenSwitchCases = append(inf.seenSwitchCases, objs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return inf
|
return inf
|
||||||
@ -1794,6 +1847,46 @@ Nodes:
|
|||||||
return inf
|
return inf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// objChain decomposes e into a chain of objects if possible. For
|
||||||
|
// example, "foo.bar().baz" will yield []types.Object{foo, bar, baz}.
|
||||||
|
// If any part can't be turned into an object, return nil.
|
||||||
|
func objChain(info *types.Info, e ast.Expr) []types.Object {
|
||||||
|
var objs []types.Object
|
||||||
|
|
||||||
|
for e != nil {
|
||||||
|
switch n := e.(type) {
|
||||||
|
case *ast.Ident:
|
||||||
|
obj := info.ObjectOf(n)
|
||||||
|
if obj == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
objs = append(objs, obj)
|
||||||
|
e = nil
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
obj := info.ObjectOf(n.Sel)
|
||||||
|
if obj == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
objs = append(objs, obj)
|
||||||
|
e = n.X
|
||||||
|
case *ast.CallExpr:
|
||||||
|
if len(n.Args) > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
e = n.Fun
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse order so the layout matches the syntactic order.
|
||||||
|
for i := 0; i < len(objs)/2; i++ {
|
||||||
|
objs[i], objs[len(objs)-1-i] = objs[len(objs)-1-i], objs[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return objs
|
||||||
|
}
|
||||||
|
|
||||||
// applyTypeModifiers applies the list of type modifiers to a type.
|
// applyTypeModifiers applies the list of type modifiers to a type.
|
||||||
// It returns nil if the modifiers could not be applied.
|
// It returns nil if the modifiers could not be applied.
|
||||||
func (ci candidateInference) applyTypeModifiers(typ types.Type, addressable bool) types.Type {
|
func (ci candidateInference) applyTypeModifiers(typ types.Type, addressable bool) types.Type {
|
||||||
@ -2127,6 +2220,40 @@ func (c *completer) matchingCandidate(cand *candidate) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// objChainMatches reports whether cand combined with the surrounding
|
||||||
|
// object prefix matches chain.
|
||||||
|
func (c *completer) objChainMatches(cand types.Object, chain []types.Object) bool {
|
||||||
|
// For example, when completing:
|
||||||
|
//
|
||||||
|
// foo.ba<>
|
||||||
|
//
|
||||||
|
// If we are considering the deep candidate "bar.baz", cand is baz,
|
||||||
|
// objChain is [foo] and deepChain is [bar]. We would match the
|
||||||
|
// chain [foo, bar, baz].
|
||||||
|
|
||||||
|
if len(chain) != len(c.inference.objChain)+len(c.deepState.chain)+1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if chain[len(chain)-1] != cand {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, o := range c.inference.objChain {
|
||||||
|
if chain[i] != o {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, o := range c.deepState.chain {
|
||||||
|
if chain[i+len(c.inference.objChain)] != o {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// candTypeMatches reports whether cand makes a good completion
|
// candTypeMatches reports whether cand makes a good completion
|
||||||
// candidate given the candidate inference. cand's score may be
|
// candidate given the candidate inference. cand's score may be
|
||||||
// mutated to downrank the candidate in certain situations.
|
// mutated to downrank the candidate in certain situations.
|
||||||
|
@ -1,12 +1,29 @@
|
|||||||
package rank
|
package rank
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
func _() {
|
func _() {
|
||||||
switch pear {
|
switch pear {
|
||||||
case : //@complete(":", pear, apple)
|
case _: //@rank("_", pear, apple)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch pear {
|
time.Monday //@item(timeMonday, "time.Monday", "time.Weekday", "const"),item(monday ,"Monday", "time.Weekday", "const")
|
||||||
case "hi":
|
time.Friday //@item(timeFriday, "time.Friday", "time.Weekday", "const"),item(friday ,"Friday", "time.Weekday", "const")
|
||||||
//@complete("", apple, pear)
|
|
||||||
|
now := time.Now()
|
||||||
|
now.Weekday //@item(nowWeekday, "now.Weekday", "func() time.Weekday", "method")
|
||||||
|
|
||||||
|
then := time.Now()
|
||||||
|
then.Weekday //@item(thenWeekday, "then.Weekday", "func() time.Weekday", "method")
|
||||||
|
|
||||||
|
switch time.Weekday(0) {
|
||||||
|
case time.Monday, time.Tuesday:
|
||||||
|
case time.Wednesday, time.Thursday:
|
||||||
|
case time.Saturday, time.Sunday:
|
||||||
|
case t: //@rank(":", timeFriday, timeMonday)
|
||||||
|
case time.: //@rank(":", friday, monday)
|
||||||
|
|
||||||
|
case now.Weekday():
|
||||||
|
case week: //@rank(":", thenWeekday, nowWeekday)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
4
internal/lsp/testdata/lsp/summary.txt.golden
vendored
4
internal/lsp/testdata/lsp/summary.txt.golden
vendored
@ -1,12 +1,12 @@
|
|||||||
-- summary --
|
-- summary --
|
||||||
CallHierarchyCount = 1
|
CallHierarchyCount = 1
|
||||||
CodeLensCount = 4
|
CodeLensCount = 4
|
||||||
CompletionsCount = 241
|
CompletionsCount = 239
|
||||||
CompletionSnippetCount = 81
|
CompletionSnippetCount = 81
|
||||||
UnimportedCompletionsCount = 6
|
UnimportedCompletionsCount = 6
|
||||||
DeepCompletionsCount = 5
|
DeepCompletionsCount = 5
|
||||||
FuzzyCompletionsCount = 8
|
FuzzyCompletionsCount = 8
|
||||||
RankedCompletionsCount = 130
|
RankedCompletionsCount = 134
|
||||||
CaseSensitiveCompletionsCount = 4
|
CaseSensitiveCompletionsCount = 4
|
||||||
DiagnosticsCount = 44
|
DiagnosticsCount = 44
|
||||||
FoldingRangesCount = 2
|
FoldingRangesCount = 2
|
||||||
|
Loading…
Reference in New Issue
Block a user