Skip to content

Commit 78522d5

Browse files
authored
Merge pull request #325 from DonnieWest/fix-when-is-statement
Fix when is statement
2 parents d5b0aab + 30ce531 commit 78522d5

File tree

3 files changed

+77
-23
lines changed

3 files changed

+77
-23
lines changed

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

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,7 @@ import org.jetbrains.kotlin.lexer.KtTokens
3636
import org.jetbrains.kotlin.lexer.KtKeywordToken
3737
import org.jetbrains.kotlin.resolve.BindingContext
3838
import org.jetbrains.kotlin.resolve.DescriptorUtils
39-
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
40-
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
41-
import org.jetbrains.kotlin.resolve.descriptorUtil.parentsWithSelf
39+
import org.jetbrains.kotlin.resolve.descriptorUtil.*
4240
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
4341
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter.Companion
4442
import org.jetbrains.kotlin.resolve.scopes.HierarchicalScope
@@ -63,12 +61,17 @@ fun completions(file: CompiledFile, cursor: Int, index: SymbolIndex, config: Com
6361
val partial = findPartialIdentifier(file, cursor)
6462
LOG.debug("Looking for completions that match '{}'", partial)
6563

66-
val (elementItems, isExhaustive, receiver) = elementCompletionItems(file, cursor, config, partial)
64+
val (elementItems, element) = elementCompletionItems(file, cursor, config, partial)
6765
val elementItemList = elementItems.toList()
6866
val elementItemLabels = elementItemList.mapNotNull { it.label }.toSet()
67+
68+
val isExhaustive = element !is KtNameReferenceExpression
69+
&& element !is KtTypeElement
70+
&& element !is KtQualifiedExpression
71+
6972
val items = (
7073
elementItemList.asSequence()
71-
+ (if (!isExhaustive) indexCompletionItems(file, cursor, receiver, index, partial).filter { it.label !in elementItemLabels } else emptySequence())
74+
+ (if (!isExhaustive) indexCompletionItems(file, cursor, element, index, partial).filter { it.label !in elementItemLabels } else emptySequence())
7275
+ (if (elementItemList.isEmpty()) keywordCompletionItems(partial) else emptySequence())
7376
)
7477
val itemList = items
@@ -80,8 +83,13 @@ fun completions(file: CompiledFile, cursor: Int, index: SymbolIndex, config: Com
8083
return CompletionList(isIncomplete, itemList)
8184
}
8285

86+
private fun getQueryNameFromExpression(receiver: KtExpression?, cursor: Int, file: CompiledFile): FqName? {
87+
val receiverType = receiver?.let { expr -> file.scopeAtPoint(cursor)?.let { file.typeOfExpression(expr, it) } }
88+
return receiverType?.constructor?.declarationDescriptor?.fqNameSafe
89+
}
90+
8391
/** Finds completions in the global symbol index, for potentially unimported symbols. */
84-
private fun indexCompletionItems(file: CompiledFile, cursor: Int, receiver: KtExpression?, index: SymbolIndex, partial: String): Sequence<CompletionItem> {
92+
private fun indexCompletionItems(file: CompiledFile, cursor: Int, element: KtElement?, index: SymbolIndex, partial: String): Sequence<CompletionItem> {
8593
val parsedFile = file.parse
8694
val imports = parsedFile.importDirectives
8795
// TODO: Deal with alias imports
@@ -93,11 +101,23 @@ private fun indexCompletionItems(file: CompiledFile, cursor: Int, receiver: KtEx
93101
val importedNames = imports
94102
.mapNotNull { it.importedFqName?.shortName() }
95103
.toSet()
96-
val receiverType = receiver?.let { expr -> file.scopeAtPoint(cursor)?.let { file.typeOfExpression(expr, it) } }
97-
val receiverTypeFqName = receiverType?.constructor?.declarationDescriptor?.fqNameSafe
104+
105+
val queryName = when (element) {
106+
is KtQualifiedExpression -> getQueryNameFromExpression(element.receiverExpression, element.receiverExpression.startOffset, file)
107+
is KtSimpleNameExpression -> {
108+
val receiver = element.getReceiverExpression()
109+
when {
110+
receiver != null -> getQueryNameFromExpression(receiver, receiver.startOffset, file)
111+
else -> null
112+
}
113+
}
114+
is KtUserType -> file.referenceAtPoint(element.qualifier?.startOffset ?: cursor)?.second?.fqNameSafe
115+
is KtTypeElement -> file.referenceAtPoint(element.startOffsetInParent)?.second?.fqNameOrNull()
116+
else -> null
117+
}
98118

99119
return index
100-
.query(partial, receiverTypeFqName, limit = MAX_COMPLETION_ITEMS)
120+
.query(partial, queryName, limit = MAX_COMPLETION_ITEMS)
101121
.asSequence()
102122
.filter { it.kind != Symbol.Kind.MODULE } // Ignore global module/package name completions for now, since they cannot be 'imported'
103123
.filter { it.fqName.shortName() !in importedNames && it.fqName.parent() !in wildcardPackages }
@@ -157,24 +177,19 @@ private fun keywordCompletionItems(partial: String): Sequence<CompletionItem> =
157177
kind = CompletionItemKind.Keyword
158178
} }
159179

160-
data class ElementCompletionItems(val items: Sequence<CompletionItem>, val isExhaustive: Boolean, val receiver: KtExpression? = null)
180+
data class ElementCompletionItems(val items: Sequence<CompletionItem>, val element: KtElement? = null)
161181

162182
/** Finds completions based on the element around the user's cursor. */
163183
private fun elementCompletionItems(file: CompiledFile, cursor: Int, config: CompletionConfiguration, partial: String): ElementCompletionItems {
164-
val surroundingElement = completableElement(file, cursor) ?: return ElementCompletionItems(emptySequence(), isExhaustive = true)
184+
val surroundingElement = completableElement(file, cursor) ?: return ElementCompletionItems(emptySequence())
165185
val completions = elementCompletions(file, cursor, surroundingElement)
166186

167187
val matchesName = completions.filter { containsCharactersInOrder(name(it), partial, caseSensitive = false) }
168188
val sorted = matchesName.takeIf { partial.length >= MIN_SORT_LENGTH }?.sortedBy { stringDistance(name(it), partial) }
169189
?: matchesName.sortedBy { if (name(it).startsWith(partial)) 0 else 1 }
170190
val visible = sorted.filter(isVisible(file, cursor))
171191

172-
val isExhaustive = surroundingElement !is KtNameReferenceExpression
173-
&& surroundingElement !is KtTypeElement
174-
&& surroundingElement !is KtQualifiedExpression
175-
val receiver = (surroundingElement as? KtQualifiedExpression)?.receiverExpression
176-
177-
return ElementCompletionItems(visible.map { completionItem(it, surroundingElement, file, config) }, isExhaustive, receiver)
192+
return ElementCompletionItems(visible.map { completionItem(it, surroundingElement, file, config) }, surroundingElement)
178193
}
179194

180195
private val callPattern = Regex("(.*)\\((?:\\$\\d+)?\\)(?:\\$0)?")
@@ -244,6 +259,7 @@ private fun completableElement(file: CompiledFile, cursor: Int): KtElement? {
244259
// package x.y.?
245260
?: el.findParent<KtPackageDirective>()
246261
// :?
262+
?: el as? KtUserType
247263
?: el.parent as? KtTypeElement
248264
// .?
249265
?: el as? KtQualifiedExpression
@@ -289,7 +305,7 @@ private fun elementCompletions(file: CompiledFile, cursor: Int, surroundingEleme
289305
val referenceTarget = file.referenceAtPoint(surroundingElement.qualifier!!.startOffset)?.second
290306
if (referenceTarget is ClassDescriptor) {
291307
LOG.info("Completing members of {}", referenceTarget.fqNameSafe)
292-
return referenceTarget.unsubstitutedInnerClassesScope.getContributedDescriptors().asSequence()
308+
return referenceTarget.getDescriptors()
293309
} else {
294310
LOG.warn("No type reference in '{}'", surroundingElement.text)
295311
return emptySequence()
@@ -349,7 +365,7 @@ private fun completeMembers(file: CompiledFile, cursor: Int, receiverExpr: KtExp
349365
val extensions = extensionFunctions(lexicalScope).filter { isExtensionFor(receiverType, it) }
350366
descriptors = members + extensions
351367

352-
if (!isCompanionOfEnum(receiverType)) {
368+
if (!isCompanionOfEnum(receiverType) && !isCompanionOfSealed(receiverType)) {
353369
return descriptors
354370
}
355371
}
@@ -358,16 +374,24 @@ private fun completeMembers(file: CompiledFile, cursor: Int, receiverExpr: KtExp
358374
// JavaClass.?
359375
val referenceTarget = file.referenceAtPoint(receiverExpr.endOffset - 1)?.second
360376
if (referenceTarget is ClassDescriptor) {
361-
LOG.debug("Completing static members of '{}'", referenceTarget.fqNameSafe)
362-
val statics = referenceTarget.staticScope.getContributedDescriptors().asSequence()
363-
val classes = referenceTarget.unsubstitutedInnerClassesScope.getContributedDescriptors().asSequence()
364-
return descriptors + statics + classes
377+
LOG.debug("Completing members of '{}'", referenceTarget.fqNameSafe)
378+
return descriptors + referenceTarget.getDescriptors()
365379
}
366380

367381
LOG.debug("Can't find member scope for {}", receiverExpr.text)
368382
return emptySequence()
369383
}
370384

385+
private fun ClassDescriptor.getDescriptors(): Sequence<DeclarationDescriptor> {
386+
val statics = staticScope.getContributedDescriptors().asSequence()
387+
val classes = unsubstitutedInnerClassesScope.getContributedDescriptors().asSequence()
388+
val types = unsubstitutedMemberScope.getContributedDescriptors().asSequence()
389+
val companionDescriptors = if (hasCompanionObject && companionObjectDescriptor != null) companionObjectDescriptor!!.getDescriptors() else emptySequence()
390+
391+
return (statics + classes + types + companionDescriptors).toSet().asSequence()
392+
393+
}
394+
371395
private fun isCompanionOfEnum(kotlinType: KotlinType): Boolean {
372396
val classDescriptor = TypeUtils.getClassDescriptor(kotlinType)
373397
val isCompanion = DescriptorUtils.isCompanionObject(classDescriptor)
@@ -377,6 +401,16 @@ private fun isCompanionOfEnum(kotlinType: KotlinType): Boolean {
377401
return DescriptorUtils.isEnumClass(classDescriptor?.containingDeclaration)
378402
}
379403

404+
private fun isCompanionOfSealed(kotlinType: KotlinType): Boolean {
405+
val classDescriptor = TypeUtils.getClassDescriptor(kotlinType)
406+
val isCompanion = DescriptorUtils.isCompanionObject(classDescriptor)
407+
if (!isCompanion) {
408+
return false
409+
}
410+
411+
return DescriptorUtils.isSealedClass(classDescriptor?.containingDeclaration)
412+
}
413+
380414
private fun findPartialIdentifier(file: CompiledFile, cursor: Int): String {
381415
val line = file.lineBefore(cursor)
382416
if (line.matches(Regex(".*\\."))) return ""

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,15 @@ class EnumWithCompanionObjectTest : SingleFileTestFixture("completions", "Enum.k
265265
}
266266
}
267267

268+
class WhenTest : SingleFileTestFixture("completions", "When.kt") {
269+
@Test fun `nested classes completion with is`() {
270+
val completions = languageServer.textDocumentService.completion(completionParams(file, 9, 24)).get().right!!
271+
val labels = completions.items.map { it.label }
272+
273+
assertThat(labels, hasItem("Test"))
274+
}
275+
}
276+
268277
class TrailingLambdaTest : SingleFileTestFixture("completions", "TrailingLambda.kt") {
269278
@Test fun `complete function with single lambda parameter`() {
270279
val completions = languageServer.textDocumentService.completion(completionParams(file, 6, 9)).get().right!!
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
sealed class SealedClass {
2+
class Test: SealedClass() {}
3+
}
4+
5+
fun sealedWhenFunc() {
6+
val value: SealedClass = SealedClass.Test()
7+
8+
when (value) {
9+
is SealedClass.
10+
}
11+
}

0 commit comments

Comments
 (0)