Better completion for static members: special behavior in case of imports from same class exist

This commit is contained in:
Valentin Kipyatkov
2015-11-02 21:21:11 +03:00
parent ca437e51c4
commit 06812aed11
20 changed files with 281 additions and 47 deletions
@@ -973,7 +973,7 @@ public class JetTypeMapper {
public JvmMethodSignature mapSignature(@NotNull FunctionDescriptor f, @NotNull OwnerKind kind,
List<ValueParameterDescriptor> valueParameters) {
if (f instanceof FunctionImportedFromObject) {
return mapSignature(((FunctionImportedFromObject) f).getFunctionFromObject());
return mapSignature(((FunctionImportedFromObject) f).getCallableFromObject());
}
BothSignatureWriter sw = new BothSignatureWriter(BothSignatureWriter.Mode.METHOD);
@@ -19,15 +19,17 @@ package org.jetbrains.kotlin.resolve
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.types.TypeSubstitutor
abstract class ImportedFromObjectCallableDescriptor(val containingObject: ClassDescriptor): CallableDescriptor
abstract class ImportedFromObjectCallableDescriptor<TCallable : CallableDescriptor>(val callableFromObject: TCallable) : CallableDescriptor {
val containingObject = callableFromObject.containingDeclaration as ClassDescriptor
}
// members imported from object should be wrapped to not require dispatch receiver
class FunctionImportedFromObject(val functionFromObject: FunctionDescriptor) :
class FunctionImportedFromObject(functionFromObject: FunctionDescriptor) :
FunctionDescriptor by functionFromObject,
ImportedFromObjectCallableDescriptor(functionFromObject.containingDeclaration as ClassDescriptor) {
ImportedFromObjectCallableDescriptor<FunctionDescriptor>(functionFromObject) {
override fun getDispatchReceiverParameter(): ReceiverParameterDescriptor? = null
override fun substitute(substitutor: TypeSubstitutor) = functionFromObject.substitute(substitutor).wrap()
override fun substitute(substitutor: TypeSubstitutor) = callableFromObject.substitute(substitutor).wrap()
private val _original by lazy {
functionFromObject.original.wrap()
@@ -43,12 +45,13 @@ class FunctionImportedFromObject(val functionFromObject: FunctionDescriptor) :
}
}
class PropertyImportedFromObject(val propertyFromObject: PropertyDescriptor) :
class PropertyImportedFromObject(propertyFromObject: PropertyDescriptor) :
PropertyDescriptor by propertyFromObject,
ImportedFromObjectCallableDescriptor(propertyFromObject.containingDeclaration as ClassDescriptor) {
ImportedFromObjectCallableDescriptor<PropertyDescriptor>(propertyFromObject) {
override fun getDispatchReceiverParameter(): ReceiverParameterDescriptor? = null
override fun substitute(substitutor: TypeSubstitutor) = propertyFromObject.substitute(substitutor)?.wrap()
override fun substitute(substitutor: TypeSubstitutor) = callableFromObject.substitute(substitutor)?.wrap()
private val _original by lazy {
propertyFromObject.original.wrap()
@@ -214,6 +214,11 @@ class BasicCompletionSession(
collector.addDescriptorElements(notImported, lookupElementFactory, notImported = true)
}
val staticMembersCompletion = StaticMembersCompletion(collector, prefixMatcher, resolutionFacade, lookupElementFactory, referenceVariants!!.imported)
if (callTypeAndReceiver is CallTypeAndReceiver.DEFAULT) {
staticMembersCompletion.completeFromImports(position.containingFile as KtFile)
}
completeNonImported(lookupElementFactory)
flushToResultSet()
@@ -229,7 +234,7 @@ class BasicCompletionSession(
}
if (configuration.completeStaticMembers && callTypeAndReceiver is CallTypeAndReceiver.DEFAULT && prefix.isNotEmpty()) {
StaticMembersCompletion(collector, prefixMatcher, indicesHelper(false), lookupElementFactory).complete()
staticMembersCompletion.completeFromIndices(indicesHelper(false))
}
}
}
@@ -16,10 +16,7 @@
package org.jetbrains.kotlin.idea.completion
import com.intellij.codeInsight.completion.CompletionProgressIndicator
import com.intellij.codeInsight.completion.CompletionService
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.completion.PrefixMatcher
import com.intellij.codeInsight.completion.*
import com.intellij.codeInsight.lookup.*
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.util.Key
@@ -31,6 +28,7 @@ import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.idea.completion.handlers.CastReceiverInsertHandler
import org.jetbrains.kotlin.idea.completion.handlers.WithTailInsertHandler
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.util.*
import org.jetbrains.kotlin.name.Name
@@ -70,6 +68,7 @@ enum class ItemPriority {
IMPLEMENT,
OVERRIDE,
NAMED_PARAMETER,
STATIC_MEMBER_FROM_IMPORTS,
STATIC_MEMBER
}
@@ -354,9 +353,14 @@ fun singleCharPattern(char: Char) = StandardPatterns.character().equalTo(char)
fun LookupElement.decorateAsStaticMember(
memberDescriptor: DeclarationDescriptor,
classDescriptor: ClassDescriptor,
classNameAsLookupString: Boolean
): LookupElement {
): LookupElement? {
var container = memberDescriptor.containingDeclaration as? ClassDescriptor ?: return null
var classDescriptor = if (container.isCompanionObject)
container.containingDeclaration as? ClassDescriptor ?: return null
else
container
val qualifierPresentation = classDescriptor.getName().asString()
val qualifierText = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(classDescriptor)
@@ -384,15 +388,34 @@ fun LookupElement.decorateAsStaticMember(
}
override fun handleInsert(context: InsertionContext) {
val prefix = qualifierText + "."
val psiDocumentManager = PsiDocumentManager.getInstance(context.project)
val file = context.file as KtFile
val offset = context.startOffset
context.document.insertString(offset, prefix)
context.offsetMap.addOffset(CompletionInitializationContext.START_OFFSET, offset + prefix.length)
val classImportableFqName = container.importableFqName
val useImport = classImportableFqName != null && file.importDirectives.any {
!it.isAllUnder && it.importPath?.fqnPart()?.parent() == classImportableFqName
}
shortenReferences(context, offset, offset + prefix.length)
PsiDocumentManager.getInstance(context.project).doPostponedOperationsAndUnblockDocument(context.document)
var insertQualifier = true
if (useImport) {
psiDocumentManager.commitAllDocuments()
if (ImportInsertHelper.getInstance(context.project).importDescriptor(file, memberDescriptor) != ImportInsertHelper.ImportDescriptorResult.FAIL) {
insertQualifier = false
}
}
if (insertQualifier) {
val prefix = qualifierText + "."
val offset = context.startOffset
context.document.insertString(offset, prefix)
context.offsetMap.addOffset(CompletionInitializationContext.START_OFFSET, offset + prefix.length)
shortenReferences(context, offset, offset + prefix.length)
}
psiDocumentManager.doPostponedOperationsAndUnblockDocument(context.document)
super.handleInsert(context)
}
}
@@ -20,41 +20,76 @@ import com.intellij.codeInsight.completion.PrefixMatcher
import com.intellij.codeInsight.lookup.LookupElement
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.core.KotlinIndicesHelper
import org.jetbrains.kotlin.idea.core.targetDescriptors
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.ImportedFromObjectCallableDescriptor
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindExclude
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
import java.util.*
class StaticMembersCompletion(
private val collector: LookupElementsCollector,
private val prefixMatcher: PrefixMatcher,
private val indicesHelper: KotlinIndicesHelper,
private val lookupElementFactory: LookupElementFactory
private val resolutionFacade: ResolutionFacade,
private val lookupElementFactory: LookupElementFactory,
alreadyAdded: Collection<DeclarationDescriptor>
) {
private val alreadyAdded = alreadyAdded.mapTo(HashSet()) {
if (it is ImportedFromObjectCallableDescriptor<*>) it.callableFromObject else it
}
//TODO: include it in smart completion?
fun completeFromImports(file: KtFile) {
val containers = file.importDirectives
.filter { !it.isAllUnder }
.map {
it.targetDescriptors(resolutionFacade).map { it.containingDeclaration }.distinct().singleOrNull() as? ClassDescriptor
}
.filterNotNull()
.toSet()
for (container in containers) {
val memberScope = if (container.kind == ClassKind.OBJECT) container.unsubstitutedMemberScope else container.staticScope
val members = memberScope.getDescriptorsFiltered(DescriptorKindFilter.CALLABLES exclude DescriptorKindExclude.Extensions, prefixMatcher.asNameFilter())
for (member in members) {
if (member is CallableDescriptor) {
addFromDescriptor(member, ItemPriority.STATIC_MEMBER_FROM_IMPORTS)
}
}
}
}
//TODO: SAM-adapters
//TODO: different priority&behaviour when static import from the same class exist
//TODO: filter out those that are visible from bases/imports etc
//TODO: filter out those that are accessible from SmartCompletion.additionalItems
fun complete() {
//TODO: what about enum members?
//TODO: filter out Kotlin functions from file facades
//TODO: better presentation for lookup elements from imports too
//TODO: better sorting
//TODO: from the same file
fun completeFromIndices(indicesHelper: KotlinIndicesHelper) {
val descriptorKindFilter = DescriptorKindFilter.CALLABLES exclude DescriptorKindExclude.Extensions
val nameFilter: (String) -> Boolean = { prefixMatcher.prefixMatches(it) }
for (member in indicesHelper.getJavaStaticMembers(descriptorKindFilter, nameFilter)) {
collector.addElement(createLookupElement(member) ?: continue)
}
indicesHelper.getJavaStaticMembers(descriptorKindFilter, nameFilter).forEach { addFromDescriptor(it, ItemPriority.STATIC_MEMBER) }
for (member in indicesHelper.getObjectMembers(descriptorKindFilter, nameFilter)) {
collector.addElement(createLookupElement(member) ?: continue)
indicesHelper.getObjectMembers(descriptorKindFilter, nameFilter).forEach { addFromDescriptor(it, ItemPriority.STATIC_MEMBER) }
}
private fun addFromDescriptor(member: CallableDescriptor, itemPriority: ItemPriority) {
if (member !in alreadyAdded) {
collector.addElement(createLookupElement(member, itemPriority) ?: return)
}
}
private fun createLookupElement(descriptor: CallableDescriptor): LookupElement? {
var classDescriptor = descriptor.containingDeclaration as? ClassDescriptor ?: return null
if (classDescriptor.isCompanionObject) {
classDescriptor = classDescriptor.containingDeclaration as? ClassDescriptor ?: return null
}
private fun createLookupElement(descriptor: CallableDescriptor, itemPriority: ItemPriority): LookupElement? {
return lookupElementFactory.createLookupElement(descriptor, useReceiverTypes = false)
.decorateAsStaticMember(descriptor, classDescriptor, classNameAsLookupString = false)
.assignPriority(ItemPriority.STATIC_MEMBER)
.suppressAutoInsertion()
.decorateAsStaticMember(descriptor, classNameAsLookupString = false)
?.assignPriority(itemPriority)
?.suppressAutoInsertion()
}
}
@@ -78,9 +78,7 @@ class StaticMembers(
return
}
collection.addLookupElements(descriptor, expectedInfos, matcher) {
descriptor -> createLookupElements(descriptor, classDescriptor)
}
collection.addLookupElements(descriptor, expectedInfos, matcher) { createLookupElements(it) }
}
classDescriptor.getStaticScope().getContributedDescriptors().forEach(::processMember)
@@ -99,10 +97,10 @@ class StaticMembers(
members.forEach(::processMember)
}
private fun createLookupElements(memberDescriptor: DeclarationDescriptor, classDescriptor: ClassDescriptor): Collection<LookupElement> {
private fun createLookupElements(memberDescriptor: DeclarationDescriptor): Collection<LookupElement> {
return lookupElementFactory.createLookupElementsInSmartCompletion(memberDescriptor, bindingContext, useReceiverTypes = false)
.map {
it.decorateAsStaticMember(memberDescriptor, classDescriptor, classNameAsLookupString = true)
it.decorateAsStaticMember(memberDescriptor, classNameAsLookupString = true)!!
.assignSmartCompletionPriority(SmartCompletionItemPriority.STATIC_MEMBER)
}
}
@@ -11,5 +11,5 @@ fun main() {
uniqueN<caret>
}
// EXIST: uniqueName
// EXIST: { allLookupStrings: "uniqueName", itemText: "uniqueName", tailText: "()", typeText: "Unit", attributes: "" }
// NUMBER: 1
@@ -0,0 +1,23 @@
package a
import a.O.xxxVal1
import a.O.xxxFun1
object O {
fun xxxFun1() { }
fun xxxFun2() { }
val xxxVal1 = 1
val xxxVal2 = 2
}
fun main() {
xxx<caret>
}
// INVOCATION_COUNT: 2
// EXIST: { allLookupStrings: "xxxFun1", itemText: "xxxFun1" }
// EXIST: { allLookupStrings: "xxxFun2", itemText: "O.xxxFun2" }
// EXIST: { allLookupStrings: "getXxxVal1, xxxVal1", itemText: "xxxVal1" }
// EXIST: { allLookupStrings: "getXxxVal2, xxxVal2", itemText: "O.xxxVal2" }
// NOTHING_ELSE
@@ -0,0 +1,9 @@
import java.awt.GridBagConstraints.NORTH
fun foo() {
SOUT<caret>
}
// INVOCATION_COUNT: 1
// EXIST_JAVA_ONLY: { allLookupStrings: "SOUTH, getSOUTH", itemText: "GridBagConstraints.SOUTH", tailText: " (java.awt)", typeText: "Int", attributes: "" }
// ABSENT: { itemText: "SwingConstants.SOUTH" }
@@ -0,0 +1,10 @@
import java.util.Collections.singletonList
fun foo() {
singl<caret>
}
// INVOCATION_COUNT: 1
// EXIST_JAVA_ONLY: { allLookupStrings: "singleton", itemText: "Collections.singleton", tailText: "(T!) (java.util)", typeText: "(Mutable)Set<T!>!", attributes: "" }
// ABSENT: { itemText: "Collections.singletonList" }
// EXIST_JAVA_ONLY: { itemText: "singletonList", tailText: "(T!)", typeText: "(Mutable)List<T!>!", attributes: "" }
@@ -0,0 +1,9 @@
import java.util.Collections.max
fun foo() {
lastIndexOfSub<caret>
}
// INVOCATION_COUNT: 2
// EXIST_JAVA_ONLY: { allLookupStrings: "lastIndexOfSubList", itemText: "Collections.lastIndexOfSubList" }
// NOTHING_ELSE
@@ -0,0 +1,11 @@
import javax.swing.SwingUtilities
class A : SwingUtilities {
fun foo() {
invoke<caret>
}
}
// INVOCATION_COUNT: 2
// ABSENT: { itemText: "SwingUtilities.invokeLater" }
// EXIST_JAVA_ONLY: { itemText: "invokeLater" }
@@ -0,0 +1,8 @@
import kotlin.text.Regex.Companion.fromLiteral
fun foo() {
escape<caret>
}
// INVOCATION_COUNT: 1
// ELEMENT_TEXT: "Regex.escapeReplacement"
@@ -0,0 +1,9 @@
import kotlin.text.Regex.Companion.escapeReplacement
import kotlin.text.Regex.Companion.fromLiteral
fun foo() {
escapeReplacement(<caret>)
}
// INVOCATION_COUNT: 1
// ELEMENT_TEXT: "Regex.escapeReplacement"
@@ -0,0 +1,9 @@
import javax.swing.SwingUtilities.invokeLater
fun foo() {
invoke<caret>
}
// INVOCATION_COUNT: 1
// ELEMENT_TEXT: "SwingUtilities.invokeAndWait"
// TAIL_TEXT: " {...} ((() -> Unit)!) (javax.swing)"
@@ -0,0 +1,10 @@
import javax.swing.SwingUtilities.invokeAndWait
import javax.swing.SwingUtilities.invokeLater
fun foo() {
invokeAndWait { <caret> }
}
// INVOCATION_COUNT: 1
// ELEMENT_TEXT: "SwingUtilities.invokeAndWait"
// TAIL_TEXT: " {...} ((() -> Unit)!) (javax.swing)"
@@ -2103,17 +2103,47 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/staticMembers"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("ImportsFromObjectNoDuplicates.kt")
public void testImportsFromObjectNoDuplicates() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/staticMembers/ImportsFromObjectNoDuplicates.kt");
doTest(fileName);
}
@TestMetadata("JavaStaticFields.kt")
public void testJavaStaticFields() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/staticMembers/JavaStaticFields.kt");
doTest(fileName);
}
@TestMetadata("JavaStaticFieldsFromImports.kt")
public void testJavaStaticFieldsFromImports() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/staticMembers/JavaStaticFieldsFromImports.kt");
doTest(fileName);
}
@TestMetadata("JavaStaticMethods.kt")
public void testJavaStaticMethods() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/staticMembers/JavaStaticMethods.kt");
doTest(fileName);
}
@TestMetadata("JavaStaticMethodsFromImports.kt")
public void testJavaStaticMethodsFromImports() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/staticMembers/JavaStaticMethodsFromImports.kt");
doTest(fileName);
}
@TestMetadata("JavaStaticMethodsNoDuplicates.kt")
public void testJavaStaticMethodsNoDuplicates() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/staticMembers/JavaStaticMethodsNoDuplicates.kt");
doTest(fileName);
}
@TestMetadata("JavaStaticMethodsNoDuplicatesFromBases.kt")
public void testJavaStaticMethodsNoDuplicatesFromBases() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/staticMembers/JavaStaticMethodsNoDuplicatesFromBases.kt");
doTest(fileName);
}
}
@TestMetadata("idea/idea-completion/testData/basic/common/substitutedSignature")
@@ -2103,17 +2103,47 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/staticMembers"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("ImportsFromObjectNoDuplicates.kt")
public void testImportsFromObjectNoDuplicates() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/staticMembers/ImportsFromObjectNoDuplicates.kt");
doTest(fileName);
}
@TestMetadata("JavaStaticFields.kt")
public void testJavaStaticFields() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/staticMembers/JavaStaticFields.kt");
doTest(fileName);
}
@TestMetadata("JavaStaticFieldsFromImports.kt")
public void testJavaStaticFieldsFromImports() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/staticMembers/JavaStaticFieldsFromImports.kt");
doTest(fileName);
}
@TestMetadata("JavaStaticMethods.kt")
public void testJavaStaticMethods() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/staticMembers/JavaStaticMethods.kt");
doTest(fileName);
}
@TestMetadata("JavaStaticMethodsFromImports.kt")
public void testJavaStaticMethodsFromImports() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/staticMembers/JavaStaticMethodsFromImports.kt");
doTest(fileName);
}
@TestMetadata("JavaStaticMethodsNoDuplicates.kt")
public void testJavaStaticMethodsNoDuplicates() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/staticMembers/JavaStaticMethodsNoDuplicates.kt");
doTest(fileName);
}
@TestMetadata("JavaStaticMethodsNoDuplicatesFromBases.kt")
public void testJavaStaticMethodsNoDuplicatesFromBases() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/staticMembers/JavaStaticMethodsNoDuplicatesFromBases.kt");
doTest(fileName);
}
}
@TestMetadata("idea/idea-completion/testData/basic/common/substitutedSignature")
@@ -544,6 +544,18 @@ public class BasicCompletionHandlerTestGenerated extends AbstractBasicCompletion
doTest(fileName);
}
@TestMetadata("ImportFromCompanionObject.kt")
public void testImportFromCompanionObject() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/staticMembers/ImportFromCompanionObject.kt");
doTest(fileName);
}
@TestMetadata("ImportJavaStaticMethod.kt")
public void testImportJavaStaticMethod() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/staticMembers/ImportJavaStaticMethod.kt");
doTest(fileName);
}
@TestMetadata("JavaStaticMethod.kt")
public void testJavaStaticMethod() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/staticMembers/JavaStaticMethod.kt");
@@ -97,9 +97,9 @@ public fun ThisReceiver.asExpression(resolutionScope: LexicalScope, psiFactory:
return expressionFactory.createExpression(psiFactory)
}
public fun KtImportDirective.targetDescriptors(): Collection<DeclarationDescriptor> {
public fun KtImportDirective.targetDescriptors(resolutionFacade: ResolutionFacade = this.getResolutionFacade()): Collection<DeclarationDescriptor> {
val nameExpression = importedReference?.getQualifiedElementSelector() as? KtSimpleNameExpression ?: return emptyList()
return nameExpression.mainReference.resolveToDescriptors(nameExpression.analyze())
return nameExpression.mainReference.resolveToDescriptors(resolutionFacade.analyze(nameExpression))
}
public fun Call.resolveCandidates(