Skip to content

Commit b1f6efb

Browse files
committed
add infix function completions
1 parent 0f12d0a commit b1f6efb

File tree

3 files changed

+69
-1
lines changed

3 files changed

+69
-1
lines changed

server/src/main/kotlin/org/javacs/kt/completion/Completions.kt

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,8 @@ private fun completableElement(file: CompiledFile, cursor: Int): KtElement? {
249249
?: el.parent?.parent as? KtQualifiedExpression
250250
// ?
251251
?: el as? KtNameReferenceExpression
252+
// x ? y (infix)
253+
?: el.parent as? KtBinaryExpression
252254
}
253255

254256
private fun elementCompletions(file: CompiledFile, cursor: Int, surroundingElement: KtElement): Sequence<DeclarationDescriptor> {
@@ -319,13 +321,27 @@ private fun elementCompletions(file: CompiledFile, cursor: Int, surroundingEleme
319321
val scope = file.scopeAtPoint(surroundingElement.startOffset) ?: return noResult("No scope at ${file.describePosition(cursor)}", emptySequence())
320322
identifiers(scope)
321323
}
324+
// x ? y (infix)
325+
is KtBinaryExpression -> {
326+
if (surroundingElement.operationToken == KtTokens.IDENTIFIER) {
327+
completeMembers(file, cursor, surroundingElement.left!!)
328+
} else emptySequence()
329+
}
322330
else -> {
323331
LOG.info("{} {} didn't look like a type, a member, or an identifier", surroundingElement::class.simpleName, surroundingElement.text)
324332
emptySequence()
325333
}
326334
}
327335
}
328336

337+
private fun receiverDescriptors(exp: KtExpression, vararg descriptors: Sequence<DeclarationDescriptor>): Sequence<DeclarationDescriptor> {
338+
val seq = sequenceOf(*descriptors).flatten()
339+
if (exp.parent !is KtBinaryExpression) return seq
340+
341+
// filter if infix call
342+
return seq.filter { declarationIsInfix(it) }
343+
}
344+
329345
private fun completeMembers(file: CompiledFile, cursor: Int, receiverExpr: KtExpression, unwrapNullable: Boolean = false): Sequence<DeclarationDescriptor> {
330346
// thingWithType.?
331347
var descriptors = emptySequence<DeclarationDescriptor>()
@@ -341,7 +357,7 @@ private fun completeMembers(file: CompiledFile, cursor: Int, receiverExpr: KtExp
341357
LOG.debug("Completing members of instance '{}'", receiverType)
342358
val members = receiverType.memberScope.getContributedDescriptors().asSequence()
343359
val extensions = extensionFunctions(lexicalScope).filter { isExtensionFor(receiverType, it) }
344-
descriptors = members + extensions
360+
descriptors = receiverDescriptors(receiverExpr, members, extensions)
345361

346362
if (!isCompanionOfEnum(receiverType) && !isCompanionOfSealed(receiverType)) {
347363
return descriptors
@@ -370,6 +386,11 @@ private fun ClassDescriptor.getDescriptors(): Sequence<DeclarationDescriptor> {
370386

371387
}
372388

389+
private fun declarationIsInfix(declaration: DeclarationDescriptor): Boolean {
390+
val functionDescriptor = declaration as? FunctionDescriptor ?: return false
391+
return functionDescriptor.isInfix
392+
}
393+
373394
private fun isCompanionOfEnum(kotlinType: KotlinType): Boolean {
374395
val classDescriptor = TypeUtils.getClassDescriptor(kotlinType)
375396
val isCompanion = DescriptorUtils.isCompanionObject(classDescriptor)

server/src/test/kotlin/org/javacs/kt/CompletionsTest.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,32 @@ class InstanceMemberTest : SingleFileTestFixture("completions", "InstanceMember.
6565
}
6666
}
6767

68+
class InfixMethodTest : SingleFileTestFixture("completions", "InfixFunctions.kt") {
69+
@Test fun `complete member function`() {
70+
val completions = languageServer.textDocumentService.completion(completionParams(file, 10, 11)).get().right!!
71+
val labels = completions.items.map { it.label }
72+
73+
assertThat(labels, hasItem(startsWith("cmpB")))
74+
assertThat(labels, not(hasItem(startsWith("cmpA"))))
75+
}
76+
77+
@Test fun `includes completion for stdlib`() {
78+
val completions = languageServer.textDocumentService.completion(completionParams(file, 15, 9)).get().right!!
79+
val labels = completions.items.map { it.label }
80+
81+
assertThat(labels, hasSize(2))
82+
assertThat(labels, hasItem(startsWith("and")))
83+
assertThat(labels, hasItem("andTo"))
84+
}
85+
86+
@Test fun `complete extension function`() {
87+
val completions = languageServer.textDocumentService.completion(completionParams(file, 19, 9)).get().right!!
88+
val labels = completions.items.map { it.label }
89+
90+
assertThat(labels, hasItem("funcA"))
91+
}
92+
}
93+
6894
class InstanceMembersJava : SingleFileTestFixture("completions", "InstanceMembersJava.kt") {
6995
@Test fun `convert getFileName to fileName`() {
7096
val completions = languageServer.textDocumentService.completion(completionParams(file, 4, 14)).get().right!!
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
class Q {
2+
fun cmpA(): Double = 1.0
3+
infix fun cmpB(x: Int): Int { return 1 }
4+
}
5+
6+
infix fun Int.funcA(x: Int): Boolean = x == this
7+
infix fun Int.andTo(v: Int) = v
8+
9+
private fun memberFunc() {
10+
Q() cm
11+
}
12+
13+
private fun stdlibFunc() {
14+
val v = 1
15+
v and
16+
}
17+
18+
private fun extensionFunc() {
19+
2 fu 3
20+
}
21+

0 commit comments

Comments
 (0)