internal/lsp/source: improve completion in append()
In the following example:
var foo []someStruct
foo = append(foo, <>)
we now downrank "foo" as a candidate at "<>". You very rarely append a
slice to itself, so having "foo" ranked highly was counterproductive.
Fixes golang/go#40535.
Change-Id: Ic01366aeded4ba2b6b64bfddd33415499b35a323
Reviewed-on: https://go-review.googlesource.com/c/tools/+/247519
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/completion_test.go b/internal/lsp/completion_test.go
index 340dd1c..c8992f5 100644
--- a/internal/lsp/completion_test.go
+++ b/internal/lsp/completion_test.go
@@ -96,6 +96,7 @@
opts.DeepCompletion = true
opts.Matcher = source.Fuzzy
opts.UnimportedCompletion = false
+ opts.LiteralCompletions = true
})
want := expected(t, test, items)
if msg := tests.CheckCompletionOrder(want, got, true); msg != "" {
diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go
index 64b63ec..bcf6c28 100644
--- a/internal/lsp/source/completion.go
+++ b/internal/lsp/source/completion.go
@@ -331,15 +331,8 @@
if c.matchingCandidate(&cand) {
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
+ if p := c.penalty(&cand); p > 0 {
+ cand.score *= (1 - p)
}
} else if isTypeName(obj) {
// If obj is a *types.TypeName that didn't otherwise match, check
@@ -387,16 +380,17 @@
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
+// penalty reports a score penalty for cand in the range (0, 1).
+// For example, a candidate is penalized if it has already been used
+// in another switch case statement.
+func (c *completer) penalty(cand *candidate) float64 {
+ for _, p := range c.inference.penalized {
+ if c.objChainMatches(cand.obj, p.objChain) {
+ return p.penalty
}
}
- return false
+ return 0
}
// candidate represents a completion candidate.
@@ -1556,6 +1550,16 @@
kindFunc
)
+// penalizedObj represents an object that should be disfavored as a
+// completion candidate.
+type penalizedObj struct {
+ // objChain is the full "chain", e.g. "foo.bar().baz" becomes
+ // []types.Object{foo, bar, baz}.
+ objChain []types.Object
+ // penalty is score penalty in the range (0, 1).
+ penalty float64
+}
+
// candidateInference holds information we have inferred about a type that can be
// used at the current position.
type candidateInference struct {
@@ -1602,13 +1606,12 @@
// foo(bar, baz<>) // variadicAssignees=false
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
+ // penalized holds expressions that should be disfavored as
+ // candidates. For example, it tracks expressions already used in a
+ // switch statement's other cases. Each expression is tracked using
+ // its entire object "chain" allowing differentiation between
+ // "a.foo" and "b.foo" when "a" and "b" are the same type.
+ penalized []penalizedObj
// objChain contains the chain of objects representing the
// surrounding *ast.SelectorExpr. For example, if we are completing
@@ -1798,7 +1801,7 @@
}
if objs := objChain(c.pkg.GetTypesInfo(), caseExpr); len(objs) > 0 {
- inf.seenSwitchCases = append(inf.seenSwitchCases, objs)
+ inf.penalized = append(inf.penalized, penalizedObj{objChain: objs, penalty: 0.1})
}
}
}
diff --git a/internal/lsp/source/completion_builtin.go b/internal/lsp/source/completion_builtin.go
index 781cec3..cfb004a 100644
--- a/internal/lsp/source/completion_builtin.go
+++ b/internal/lsp/source/completion_builtin.go
@@ -72,10 +72,19 @@
inf.objType = parentInf.objType
- if exprIdx > 0 {
- inf.objType = deslice(inf.objType)
- // Check if we are completing the variadic append() param.
- inf.variadic = exprIdx == 1 && len(call.Args) <= 2
+ if exprIdx <= 0 {
+ break
+ }
+
+ inf.objType = deslice(inf.objType)
+
+ // Check if we are completing the variadic append() param.
+ inf.variadic = exprIdx == 1 && len(call.Args) <= 2
+
+ // Penalize the first append() argument as a candidate. You
+ // don't normally append a slice to itself.
+ if sliceChain := objChain(c.pkg.GetTypesInfo(), call.Args[0]); len(sliceChain) > 0 {
+ inf.penalized = append(inf.penalized, penalizedObj{objChain: sliceChain, penalty: 0.9})
}
case "delete":
if exprIdx > 0 && len(call.Args) > 0 {
diff --git a/internal/lsp/testdata/lsp/primarymod/append/append.go b/internal/lsp/testdata/lsp/primarymod/append/append.go
index 68cae67..f8e64c5 100644
--- a/internal/lsp/testdata/lsp/primarymod/append/append.go
+++ b/internal/lsp/testdata/lsp/primarymod/append/append.go
@@ -16,5 +16,14 @@
// Don't add "..." to append() argument.
bar(append()) //@snippet("))", appendStrings, "aStrings", "aStrings")
-}
+
+ type baz struct{}
+ baz{} //@item(appendBazLiteral, "baz{}", "", "var")
+ var bazzes []baz //@item(appendBazzes, "bazzes", "[]baz", "var")
+ var bazzy baz //@item(appendBazzy, "bazzy", "baz", "var")
+ bazzes = append(bazzes, ba) //@rank(")", appendBazzy, appendBazLiteral, appendBazzes)
+
+ var b struct{ b []baz }
+ b.b //@item(appendNestedBaz, "b.b", "[]baz", "field")
+ b.b = append(b.b, b) //@rank(")", appendBazzy, appendBazLiteral, appendNestedBaz)
}
diff --git a/internal/lsp/testdata/lsp/summary.txt.golden b/internal/lsp/testdata/lsp/summary.txt.golden
index 2ea9450..a76174d 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 = 137
+RankedCompletionsCount = 139
CaseSensitiveCompletionsCount = 4
DiagnosticsCount = 44
FoldingRangesCount = 2