Skip to content

Commit 49797d0

Browse files
muirdmstamblerre
authored andcommitted
internal/lsp/source: improve completion in type assertions
In cases like: var foo *someType = bar.(some<>) We will now complete "some" to "*someType". This involved two changes: 1. Properly detect expected type as *someType in above example. To do this I just removed *ast.TypeAssertExpr from breaksExpectedTypeInference() so we continue searching up the AST for the expected type. 2. If the given type name T doesn't match, also try *T. If *T does match, we mark the candidate as "makePointer=true" so we know to prepend the "*" when formatting the candidate. Change-Id: I05859c68082a798141755b614673a1483d864e3e Reviewed-on: https://go-review.googlesource.com/c/tools/+/212717 Run-TryBot: Muir Manders <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Rebecca Stambler <[email protected]>
1 parent 96555e0 commit 49797d0

File tree

9 files changed

+74
-64
lines changed

9 files changed

+74
-64
lines changed

internal/lsp/completion.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,9 @@ func toProtocolCompletionItems(candidates []source.CompletionItem, rng protocol.
127127
// https://github.com/Microsoft/language-server-protocol/issues/348.
128128
SortText: fmt.Sprintf("%05d", i),
129129

130-
// Trim address operator (VSCode doesn't like weird characters
131-
// in filterText).
132-
FilterText: strings.TrimLeft(candidate.InsertText, "&"),
130+
// Trim operators (VSCode doesn't like weird characters in
131+
// filterText).
132+
FilterText: strings.TrimLeft(candidate.InsertText, "&*"),
133133

134134
Preselect: i == 0,
135135
Documentation: candidate.Documentation,

internal/lsp/source/completion.go

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,9 @@ type candidate struct {
379379
// addressable is true if a pointer can be taken to the candidate.
380380
addressable bool
381381

382+
// makePointer is true if the candidate type name T should be made into *T.
383+
makePointer bool
384+
382385
// imp is the import that needs to be added to this package in order
383386
// for this candidate to be valid. nil if no import needed.
384387
imp *importInfo
@@ -1535,7 +1538,7 @@ func findSwitchStmt(path []ast.Node, pos token.Pos, c *ast.CaseClause) ast.Stmt
15351538
// expect a function argument, not a composite literal value.
15361539
func breaksExpectedTypeInference(n ast.Node) bool {
15371540
switch n.(type) {
1538-
case *ast.FuncLit, *ast.CallExpr, *ast.TypeAssertExpr, *ast.IndexExpr, *ast.SliceExpr, *ast.CompositeLit:
1541+
case *ast.FuncLit, *ast.CallExpr, *ast.IndexExpr, *ast.SliceExpr, *ast.CompositeLit:
15391542
return true
15401543
default:
15411544
return false
@@ -1758,39 +1761,52 @@ func (c *completer) matchingTypeName(cand *candidate) bool {
17581761
return false
17591762
}
17601763

1761-
// Take into account any type name modifier prefixes.
1762-
actual := c.expectedType.applyTypeNameModifiers(cand.obj.Type())
1764+
typeMatches := func(candType types.Type) bool {
1765+
// Take into account any type name modifier prefixes.
1766+
candType = c.expectedType.applyTypeNameModifiers(candType)
1767+
1768+
if from := c.expectedType.typeName.assertableFrom; from != nil {
1769+
// Don't suggest the starting type in type assertions. For example,
1770+
// if "foo" is an io.Writer, don't suggest "foo.(io.Writer)".
1771+
if types.Identical(from, candType) {
1772+
return false
1773+
}
1774+
1775+
if intf, ok := from.Underlying().(*types.Interface); ok {
1776+
if !types.AssertableTo(intf, candType) {
1777+
return false
1778+
}
1779+
}
1780+
}
17631781

1764-
if c.expectedType.typeName.assertableFrom != nil {
1765-
// Don't suggest the starting type in type assertions. For example,
1766-
// if "foo" is an io.Writer, don't suggest "foo.(io.Writer)".
1767-
if types.Identical(c.expectedType.typeName.assertableFrom, actual) {
1782+
if c.expectedType.typeName.wantComparable && !types.Comparable(candType) {
17681783
return false
17691784
}
17701785

1771-
if intf, ok := c.expectedType.typeName.assertableFrom.Underlying().(*types.Interface); ok {
1772-
if !types.AssertableTo(intf, actual) {
1773-
return false
1774-
}
1786+
// We can expect a type name and have an expected type in cases like:
1787+
//
1788+
// var foo []int
1789+
// foo = []i<>
1790+
//
1791+
// Where our expected type is "[]int", and we expect a type name.
1792+
if c.expectedType.objType != nil {
1793+
return types.AssignableTo(candType, c.expectedType.objType)
17751794
}
1795+
1796+
// Default to saying any type name is a match.
1797+
return true
17761798
}
17771799

1778-
if c.expectedType.typeName.wantComparable && !types.Comparable(actual) {
1779-
return false
1800+
if typeMatches(cand.obj.Type()) {
1801+
return true
17801802
}
17811803

1782-
// We can expect a type name and have an expected type in cases like:
1783-
//
1784-
// var foo []int
1785-
// foo = []i<>
1786-
//
1787-
// Where our expected type is "[]int", and we expect a type name.
1788-
if c.expectedType.objType != nil {
1789-
return types.AssignableTo(actual, c.expectedType.objType)
1804+
if typeMatches(types.NewPointer(cand.obj.Type())) {
1805+
cand.makePointer = true
1806+
return true
17901807
}
17911808

1792-
// Default to saying any type name is a match.
1793-
return true
1809+
return false
17941810
}
17951811

17961812
// candKind returns the objKind of candType, if any.

internal/lsp/source/completion_format.go

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -130,30 +130,29 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
130130
}
131131
}
132132

133-
// Prepend "&" operator if our candidate needs address taken.
133+
// Prepend "&" or "*" operator as appropriate.
134+
var prefixOp string
134135
if cand.takeAddress {
135-
var (
136-
sel *ast.SelectorExpr
137-
ok bool
138-
)
139-
if sel, ok = c.path[0].(*ast.SelectorExpr); !ok && len(c.path) > 1 {
140-
sel, _ = c.path[1].(*ast.SelectorExpr)
141-
}
136+
prefixOp = "&"
137+
} else if cand.makePointer {
138+
prefixOp = "*"
139+
}
142140

143-
// If we are in a selector, add an edit to place "&" before selector node.
144-
if sel != nil {
145-
edits, err := referenceEdit(c.snapshot.View().Session().Cache().FileSet(), c.mapper, sel)
141+
if prefixOp != "" {
142+
// If we are in a selector, add an edit to place prefix before selector.
143+
if sel := enclosingSelector(c.path, c.pos); sel != nil {
144+
edits, err := prependEdit(c.snapshot.View().Session().Cache().FileSet(), c.mapper, sel, prefixOp)
146145
if err != nil {
147-
log.Error(c.ctx, "error generating reference edit", err)
146+
log.Error(c.ctx, "error generating prefix edit", err)
148147
} else {
149148
protocolEdits = append(protocolEdits, edits...)
150149
}
151150
} else {
152-
// If there is no selector, just stick the "&" at the start.
153-
insert = "&" + insert
151+
// If there is no selector, just stick the prefix at the start.
152+
insert = prefixOp + insert
154153
}
155154

156-
label = "&" + label
155+
label = prefixOp + label
157156
}
158157

159158
detail = strings.TrimPrefix(detail, "untyped ")

internal/lsp/source/completion_literal.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ func (c *completer) literal(literalType types.Type, imp *importInfo) {
116116
// If we are in a selector we must place the "&" before the selector.
117117
// For example, "foo.B<>" must complete to "&foo.Bar{}", not
118118
// "foo.&Bar{}".
119-
edits, err := referenceEdit(c.snapshot.View().Session().Cache().FileSet(), c.mapper, sel)
119+
edits, err := prependEdit(c.snapshot.View().Session().Cache().FileSet(), c.mapper, sel, "&")
120120
if err != nil {
121121
log.Error(c.ctx, "error making edit for literal pointer completion", err)
122122
return
@@ -173,17 +173,17 @@ func (c *completer) literal(literalType types.Type, imp *importInfo) {
173173
}
174174
}
175175

176-
// referenceEdit produces text edits that prepend a "&" operator to the
177-
// specified node.
178-
func referenceEdit(fset *token.FileSet, m *protocol.ColumnMapper, node ast.Node) ([]protocol.TextEdit, error) {
176+
// prependEdit produces text edits that preprend the specified prefix
177+
// to the specified node.
178+
func prependEdit(fset *token.FileSet, m *protocol.ColumnMapper, node ast.Node, prefix string) ([]protocol.TextEdit, error) {
179179
rng := newMappedRange(fset, m, node.Pos(), node.Pos())
180180
spn, err := rng.Span()
181181
if err != nil {
182182
return nil, err
183183
}
184184
return ToProtocolEdits(m, []diff.TextEdit{{
185185
Span: spn,
186-
NewText: "&",
186+
NewText: prefix,
187187
}})
188188
}
189189

internal/lsp/source/source_test.go

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,20 +102,13 @@ func (r *runner) Completion(t *testing.T, src span.Span, test tests.Completion,
102102
for _, pos := range test.CompletionItems {
103103
want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
104104
}
105-
prefix, list := r.callCompletion(t, src, func(opts *source.Options) {
106-
opts.Matcher = source.Fuzzy
105+
_, got := r.callCompletion(t, src, func(opts *source.Options) {
106+
opts.Matcher = source.CaseInsensitive
107107
opts.Literal = strings.Contains(string(src.URI()), "literal")
108108
opts.DeepCompletion = false
109109
})
110110
if !strings.Contains(string(src.URI()), "builtins") {
111-
list = tests.FilterBuiltins(list)
112-
}
113-
var got []protocol.CompletionItem
114-
for _, item := range list {
115-
if !strings.HasPrefix(strings.ToLower(item.Label), prefix) {
116-
continue
117-
}
118-
got = append(got, item)
111+
got = tests.FilterBuiltins(got)
119112
}
120113
if diff := tests.DiffCompletionItems(want, got); diff != "" {
121114
t.Errorf("%s: %s", src, diff)

internal/lsp/testdata/func_rank/func_rank.go.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func _() {
2929
fnInt([]int{}[s.A]) //@complete("])", rankAA, rankAC, rankAB)
3030
fnInt([]int{}[:s.A]) //@complete("])", rankAA, rankAC, rankAB)
3131

32-
fnInt(s.A.(int)) //@complete(".(", rankAA, rankAB, rankAC)
32+
fnInt(s.A.(int)) //@complete(".(", rankAA, rankAC, rankAB)
3333

3434
fnPtr := func(*string) {}
3535
fnPtr(&s.A) //@complete(")", rankAB, rankAA, rankAC)

internal/lsp/testdata/maps/maps.go.in

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ func _() {
66
// not comparabale
77
type aSlice []int //@item(mapSliceType, "aSlice", "[]int", "type")
88

9+
*aSlice //@item(mapSliceTypePtr, "*aSlice", "[]int", "type")
10+
911
// comparable
1012
type aStruct struct{} //@item(mapStructType, "aStruct", "struct{...}", "struct")
1113

12-
map[]a{} //@complete("]", mapStructType, mapSliceType, mapVar)
14+
map[]a{} //@complete("]", mapSliceTypePtr, mapStructType, mapVar)
1315

14-
map[a]a{} //@complete("]", mapStructType, mapSliceType, mapVar)
16+
map[a]a{} //@complete("]", mapSliceTypePtr, mapStructType, mapVar)
1517
map[a]a{} //@complete("{", mapSliceType, mapStructType, mapVar)
16-
17-
map[]a{} //@rank("]", int, mapSliceType)
1818
}

internal/lsp/testdata/summary.txt.golden

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ CompletionSnippetCount = 63
44
UnimportedCompletionsCount = 9
55
DeepCompletionsCount = 5
66
FuzzyCompletionsCount = 8
7-
RankedCompletionsCount = 56
7+
RankedCompletionsCount = 55
88
CaseSensitiveCompletionsCount = 4
99
DiagnosticsCount = 35
1010
FoldingRangesCount = 2

internal/lsp/testdata/typeassert/type_assert.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ func (*abcPtrImpl) abc()
1313
type abcNotImpl struct{} //@item(abcNotImpl, "abcNotImpl", "struct{...}", "struct")
1414

1515
func _() {
16+
*abcPtrImpl //@item(abcPtrImplPtr, "*abcPtrImpl", "struct{...}", "struct")
17+
1618
var a abc
1719
switch a.(type) {
18-
case ab: //@complete(":", abcImpl, abcIntf, abcNotImpl, abcPtrImpl)
20+
case ab: //@complete(":", abcPtrImplPtr, abcImpl, abcIntf, abcNotImpl)
1921
case *ab: //@complete(":", abcImpl, abcPtrImpl, abcIntf, abcNotImpl)
2022
}
2123

22-
a.(ab) //@complete(")", abcImpl, abcIntf, abcNotImpl, abcPtrImpl)
24+
a.(ab) //@complete(")", abcPtrImplPtr, abcImpl, abcIntf, abcNotImpl)
2325
a.(*ab) //@complete(")", abcImpl, abcPtrImpl, abcIntf, abcNotImpl)
2426
}

0 commit comments

Comments
 (0)