Static members completion works in smart completion + fixed incorrect finding of java method descriptor that caused duplicated items

This commit is contained in:
Valentin Kipyatkov
2015-11-05 23:02:28 +03:00
parent 1283f5f4e1
commit 3cda866af0
23 changed files with 272 additions and 42 deletions
@@ -20,6 +20,7 @@ package org.jetbrains.kotlin.idea.caches.resolve
import com.intellij.psi.*
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.java.sources.JavaSourceElement
@@ -47,6 +48,16 @@ fun PsiMethod.getJavaMethodDescriptor(): FunctionDescriptor? {
}
}
fun PsiMethod.getJavaMethodDescriptor(resolutionFacade: ResolutionFacade): FunctionDescriptor? {
val method = originalElement as? PsiMethod ?: return null
if (method.containingClass == null || !Name.isValidIdentifier(method.name)) return null
val resolver = resolutionFacade.getFrontendService(method, JavaDescriptorResolver::class.java)
return when {
method.isConstructor -> resolver.resolveConstructor(JavaConstructorImpl(method))
else -> resolver.resolveMethod(JavaMethodImpl(method))
}
}
fun PsiClass.getJavaClassDescriptor(): ClassDescriptor? {
val psiClass = originalElement as? PsiClass ?: return null
return psiClass.getJavaDescriptorResolver()?.resolveClass(JavaClassImpl(psiClass))
@@ -215,9 +215,9 @@ class BasicCompletionSession(
}
val staticMembersCompletion = StaticMembersCompletion(
collector, prefixMatcher, resolutionFacade, lookupElementFactory, referenceVariants!!.imported, isJvmModule)
prefixMatcher, resolutionFacade, lookupElementFactory, referenceVariants!!.imported, isJvmModule)
if (callTypeAndReceiver is CallTypeAndReceiver.DEFAULT) {
staticMembersCompletion.completeFromImports(position.containingFile as KtFile)
staticMembersCompletion.completeFromImports(file, collector)
}
completeNonImported(lookupElementFactory)
@@ -235,7 +235,7 @@ class BasicCompletionSession(
}
if (configuration.completeStaticMembers && callTypeAndReceiver is CallTypeAndReceiver.DEFAULT && prefix.isNotEmpty()) {
staticMembersCompletion.completeFromIndices(indicesHelper(false))
staticMembersCompletion.completeFromIndices(indicesHelper(false), collector)
}
}
}
@@ -75,7 +75,7 @@ abstract class CompletionSession(
resultSet: CompletionResultSet
) {
protected val position = parameters.getPosition()
private val file = position.getContainingFile() as KtFile
protected val file = position.getContainingFile() as KtFile
protected val resolutionFacade = file.getResolutionFacade()
protected val moduleDescriptor = resolutionFacade.moduleDescriptor
protected val project = position.getProject()
@@ -42,6 +42,18 @@ import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
import org.jetbrains.kotlin.utils.addIfNotNull
interface AbstractLookupElementFactory {
fun createStandardLookupElementsForDescriptor(descriptor: DeclarationDescriptor, useReceiverTypes: Boolean): Collection<LookupElement>
fun createLookupElement(
descriptor: DeclarationDescriptor,
useReceiverTypes: Boolean,
qualifyNestedClasses: Boolean = false,
includeClassTypeArguments: Boolean = true,
parametersAndTypeGrayed: Boolean = false
): LookupElement?
}
data /* we need copy() */
class LookupElementFactory(
val basicFactory: BasicLookupElementFactory,
@@ -50,7 +62,7 @@ class LookupElementFactory(
private val inDescriptor: DeclarationDescriptor,
private val contextVariablesProvider: ContextVariablesProvider,
private val standardLookupElementsPostProcessor: (LookupElement) -> LookupElement = { it }
) {
) : AbstractLookupElementFactory {
companion object {
fun hasSingleFunctionTypeParameter(descriptor: FunctionDescriptor): Boolean {
val parameter = descriptor.original.valueParameters.singleOrNull() ?: return false
@@ -69,7 +81,7 @@ class LookupElementFactory(
.toSet()
}
public fun createStandardLookupElementsForDescriptor(descriptor: DeclarationDescriptor, useReceiverTypes: Boolean): Collection<LookupElement> {
override fun createStandardLookupElementsForDescriptor(descriptor: DeclarationDescriptor, useReceiverTypes: Boolean): Collection<LookupElement> {
val result = SmartList<LookupElement>()
val isNormalCall = callType == CallType.DEFAULT || callType == CallType.DOT || callType == CallType.SAFE || callType == CallType.SUPER_MEMBERS
@@ -197,12 +209,12 @@ class LookupElementFactory(
}
}
public fun createLookupElement(
override fun createLookupElement(
descriptor: DeclarationDescriptor,
useReceiverTypes: Boolean,
qualifyNestedClasses: Boolean = false,
includeClassTypeArguments: Boolean = true,
parametersAndTypeGrayed: Boolean = false
qualifyNestedClasses: Boolean,
includeClassTypeArguments: Boolean,
parametersAndTypeGrayed: Boolean
): LookupElement {
var element = basicFactory.createLookupElement(descriptor, qualifyNestedClasses, includeClassTypeArguments, parametersAndTypeGrayed)
@@ -30,10 +30,10 @@ 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 org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptyList
import java.util.*
class StaticMembersCompletion(
private val collector: LookupElementsCollector,
private val prefixMatcher: PrefixMatcher,
private val resolutionFacade: ResolutionFacade,
private val lookupElementFactory: LookupElementFactory,
@@ -44,8 +44,24 @@ class StaticMembersCompletion(
if (it is ImportedFromObjectCallableDescriptor<*>) it.callableFromObject else it
}
//TODO: include it in smart completion?
fun completeFromImports(file: KtFile) {
fun decoratedLookupElementFactory(itemPriority: ItemPriority): AbstractLookupElementFactory {
return object : AbstractLookupElementFactory {
override fun createStandardLookupElementsForDescriptor(descriptor: DeclarationDescriptor, useReceiverTypes: Boolean): Collection<LookupElement> {
if (!useReceiverTypes) return emptyList()
return lookupElementFactory.createLookupElement(descriptor, useReceiverTypes = false)
.decorateAsStaticMember(descriptor, classNameAsLookupString = false)
?.assignPriority(itemPriority)
?.suppressAutoInsertion()
.singletonOrEmptyList()
}
override fun createLookupElement(descriptor: DeclarationDescriptor, useReceiverTypes: Boolean,
qualifyNestedClasses: Boolean, includeClassTypeArguments: Boolean,
parametersAndTypeGrayed: Boolean) = null
}
}
fun membersFromImports(file: KtFile): Collection<DeclarationDescriptor> {
val containers = file.importDirectives
.filter { !it.isAllUnder }
.map {
@@ -54,42 +70,46 @@ class StaticMembersCompletion(
.filterNotNull()
.toSet()
val result = ArrayList<DeclarationDescriptor>()
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)
}
}
members.filterTo(result) { it is CallableDescriptor && it !in alreadyAdded } //TODO: substitution
}
return result
}
//TODO: filter out those that are accessible from SmartCompletion.additionalItems
//TODO: what about enum members?
//TODO: better presentation for lookup elements from imports too
//TODO: from the same file
fun completeFromIndices(indicesHelper: KotlinIndicesHelper) {
fun membersFromIndices(indicesHelper: KotlinIndicesHelper): Collection<DeclarationDescriptor> {
val descriptorKindFilter = DescriptorKindFilter.CALLABLES exclude DescriptorKindExclude.Extensions
val nameFilter: (String) -> Boolean = { prefixMatcher.prefixMatches(it) }
val result = ArrayList<DeclarationDescriptor>()
if (isJvmModule) {
indicesHelper.getJavaStaticMembers(descriptorKindFilter, nameFilter).forEach { addFromDescriptor(it, ItemPriority.STATIC_MEMBER) }
indicesHelper.getJavaStaticMembers(descriptorKindFilter, nameFilter).filterTo(result) { it !in alreadyAdded } //TODO: substitution
}
indicesHelper.getObjectMembers(descriptorKindFilter, nameFilter).forEach { addFromDescriptor(it, ItemPriority.STATIC_MEMBER) }
indicesHelper.getObjectMembers(descriptorKindFilter, nameFilter).filterTo(result) { it !in alreadyAdded } //TODO: substitution
return result
}
private fun addFromDescriptor(member: CallableDescriptor, itemPriority: ItemPriority) {
if (member !in alreadyAdded) { //TODO: substitution
collector.addElement(createLookupElement(member, itemPriority) ?: return)
}
fun completeFromImports(file: KtFile, collector: LookupElementsCollector) {
val factory = decoratedLookupElementFactory(ItemPriority.STATIC_MEMBER_FROM_IMPORTS)
membersFromImports(file)
.flatMap { factory.createStandardLookupElementsForDescriptor(it, useReceiverTypes = true) }
.forEach { collector.addElement(it) }
}
private fun createLookupElement(descriptor: CallableDescriptor, itemPriority: ItemPriority): LookupElement? {
return lookupElementFactory.createLookupElement(descriptor, useReceiverTypes = false)
.decorateAsStaticMember(descriptor, classNameAsLookupString = false)
?.assignPriority(itemPriority)
?.suppressAutoInsertion()
fun completeFromIndices(indicesHelper: KotlinIndicesHelper, collector: LookupElementsCollector) {
val factory = decoratedLookupElementFactory(ItemPriority.STATIC_MEMBER)
membersFromIndices(indicesHelper)
.flatMap { factory.createStandardLookupElementsForDescriptor(it, useReceiverTypes = true) }
.forEach { collector.addElement(it) }
}
}
@@ -83,9 +83,9 @@ class SmartCompletion(
SmartCastCalculator(bindingContext, resolutionFacade.moduleDescriptor, expression, resolutionFacade)
}
public val descriptorFilter: ((DeclarationDescriptor, LookupElementFactory) -> Collection<LookupElement>)? =
{ descriptor: DeclarationDescriptor, lookupElementFactory: LookupElementFactory ->
filterDescriptor(descriptor, lookupElementFactory).map { postProcess(it) }
public val descriptorFilter: ((DeclarationDescriptor, AbstractLookupElementFactory) -> Collection<LookupElement>)? =
{ descriptor: DeclarationDescriptor, factory: AbstractLookupElementFactory ->
filterDescriptor(descriptor, factory).map { postProcess(it) }
}.check { expectedInfos.isNotEmpty() }
public fun additionalItems(lookupElementFactory: LookupElementFactory): Pair<Collection<LookupElement>, InheritanceItemsSearcher?> {
@@ -154,7 +154,7 @@ class SmartCompletion(
return@lazy emptySet()
}
private fun filterDescriptor(descriptor: DeclarationDescriptor, lookupElementFactory: LookupElementFactory): Collection<LookupElement> {
private fun filterDescriptor(descriptor: DeclarationDescriptor, lookupElementFactory: AbstractLookupElementFactory): Collection<LookupElement> {
val callType = callTypeAndReceiver.callType
if (descriptor in descriptorsToSkip) return emptyList()
@@ -322,7 +322,7 @@ class SmartCompletion(
return null
}
private fun MutableCollection<LookupElement>.addCallableReferenceLookupElements(descriptor: DeclarationDescriptor, lookupElementFactory: LookupElementFactory) {
private fun MutableCollection<LookupElement>.addCallableReferenceLookupElements(descriptor: DeclarationDescriptor, lookupElementFactory: AbstractLookupElementFactory) {
if (callableTypeExpectedInfo.isEmpty()) return
fun toLookupElement(descriptor: CallableDescriptor): LookupElement? {
@@ -107,6 +107,18 @@ class SmartCompletionSession(configuration: CompletionSessionConfiguration, para
flushToResultSet()
if (filter != null) {
val staticMembersCompletion: StaticMembersCompletion?
if (callTypeAndReceiver is CallTypeAndReceiver.DEFAULT) {
staticMembersCompletion = StaticMembersCompletion(prefixMatcher, resolutionFacade, lookupElementFactory, referenceVariants!!.imported, isJvmModule)
val decoratedFactory = staticMembersCompletion.decoratedLookupElementFactory(ItemPriority.STATIC_MEMBER_FROM_IMPORTS)
staticMembersCompletion.membersFromImports(file)
.flatMap { filter(it, decoratedFactory) }
.forEach { collector.addElement(it) }
}
else {
staticMembersCompletion = null
}
if (shouldCompleteTopLevelCallablesFromIndex()) {
getTopLevelCallables().forEach { collector.addElements(filter(it, lookupElementFactory), notImported = true) }
flushToResultSet()
@@ -122,6 +134,13 @@ class SmartCompletionSession(configuration: CompletionSessionConfiguration, para
flushToResultSet()
}
}
if (staticMembersCompletion != null && configuration.completeStaticMembers) {
val decoratedFactory = staticMembersCompletion.decoratedLookupElementFactory(ItemPriority.STATIC_MEMBER)
staticMembersCompletion.membersFromIndices(indicesHelper(false))
.flatMap { filter(it, decoratedFactory) }
.forEach { collector.addElement(it) }
}
}
}
@@ -0,0 +1,9 @@
fun foo(r: Runnable){}
fun bar(){
foo(<caret>)
}
// INVOCATION_COUNT: 2
// ELEMENT_TEXT: Thread.currentThread
@@ -0,0 +1,9 @@
fun foo(r: Runnable){}
fun bar(){
foo(Thread.currentThread())<caret>
}
// INVOCATION_COUNT: 2
// ELEMENT_TEXT: Thread.currentThread
@@ -0,0 +1,9 @@
package dependency1
import test.C
object O1 {
fun foo(): C = C()
fun bar(): C = C()
fun x(): String = ""
}
@@ -0,0 +1,7 @@
package dependency2
import test.C
object O2 {
fun foo(): C = C()
}
@@ -0,0 +1,15 @@
import test.C
public class JavaClass1 {
public static C f() {
return new C();
}
public static C g() {
return new C();
}
public static int h() {
return 0;
}
}
@@ -0,0 +1,11 @@
import test.C
public class JavaClass2 {
public static C f() {
return new C();
}
public static C g() {
return new C();
}
}
@@ -0,0 +1,24 @@
package test
import dependency1.O1.foo
import JavaClass1.f
class C
fun foo(): C {
return <caret>
}
// INVOCATION_COUNT: 1
// EXIST: { allLookupStrings: "bar", itemText: "O1.bar", tailText: "() (dependency1)", attributes: "" }
// EXIST: { itemText: "foo", tailText: "()", attributes: "" }
// ABSENT: { itemText: "O1.foo" }
// ABSENT: { itemText: "O2.foo" }
// EXIST: { allLookupStrings: "g", itemText: "JavaClass1.g", tailText: "() (<root>)", attributes: "" }
// EXIST: { itemText: "f", tailText: "()", attributes: "" }
// ABSENT: { itemText: "JavaClass1.f" }
// ABSENT: { itemText: "JavaClass1.h" }
// ABSENT: { itemText: "JavaClass2.f" }
// ABSENT: { itemText: "JavaClass2.g" }
@@ -0,0 +1,9 @@
package dependency1
import test.C
object O1 {
fun foo(): C = C()
fun bar(): C = C()
fun x(): String = ""
}
@@ -0,0 +1,7 @@
package dependency2
import test.C
object O2 {
fun foo(): C = C()
}
@@ -0,0 +1,15 @@
import test.C
public class JavaClass1 {
public static C f() {
return new C();
}
public static C g() {
return new C();
}
public static int h() {
return 0;
}
}
@@ -0,0 +1,11 @@
import test.C
public class JavaClass2 {
public static C f() {
return new C();
}
public static int g() {
return 0;
}
}
@@ -0,0 +1,24 @@
package test
import dependency1.O1.foo
import JavaClass1.f
class C
fun foo(): C {
return <caret>
}
// INVOCATION_COUNT: 2
// EXIST: { allLookupStrings: "bar", itemText: "O1.bar", tailText: "() (dependency1)", attributes: "" }
// EXIST: { itemText: "foo", tailText: "()", attributes: "" }
// ABSENT: { itemText: "O1.foo" }
// EXIST: { allLookupStrings: "foo", itemText: "O2.foo", tailText: "() (dependency2)", attributes: "" }
// EXIST: { allLookupStrings: "g", itemText: "JavaClass1.g", tailText: "() (<root>)", attributes: "" }
// EXIST: { itemText: "f", tailText: "()", attributes: "" }
// ABSENT: { itemText: "JavaClass1.f" }
// ABSENT: { itemText: "JavaClass1.h" }
// EXIST: { itemText: "JavaClass2.f" }
// ABSENT: { itemText: "JavaClass2.g" }
@@ -136,4 +136,16 @@ public class MultiFileSmartCompletionTestGenerated extends AbstractMultiFileSmar
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smartMultiFile/NoObjectDuplication/");
doTest(fileName);
}
@TestMetadata("StaticMembers1")
public void testStaticMembers1() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smartMultiFile/StaticMembers1/");
doTest(fileName);
}
@TestMetadata("StaticMembers2")
public void testStaticMembers2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smartMultiFile/StaticMembers2/");
doTest(fileName);
}
}
@@ -491,6 +491,12 @@ public class SmartCompletionHandlerTestGenerated extends AbstractSmartCompletion
doTest(fileName);
}
@TestMetadata("JavaStaticMethod2.kt")
public void testJavaStaticMethod2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/smart/JavaStaticMethod2.kt");
doTest(fileName);
}
@TestMetadata("JavaStaticMethodInsertsImport.kt")
public void testJavaStaticMethodInsertsImport() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/smart/JavaStaticMethodInsertsImport.kt");
@@ -149,18 +149,18 @@ public class BasicCompletionWeigherTestGenerated extends AbstractBasicCompletion
doTest(fileName);
}
@TestMetadata("SuperMembers.kt")
public void testSuperMembers() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/weighers/basic/SuperMembers.kt");
doTest(fileName);
}
@TestMetadata("StaticMembers.kt")
public void testStaticMembers() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/weighers/basic/StaticMembers.kt");
doTest(fileName);
}
@TestMetadata("SuperMembers.kt")
public void testSuperMembers() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/weighers/basic/SuperMembers.kt");
doTest(fileName);
}
@TestMetadata("idea/idea-completion/testData/weighers/basic/expectedInfo")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -230,7 +230,7 @@ public class KotlinIndicesHelper(
for (method in shortNamesCache.getMethodsByName(name, scope)) {
if (!method.hasModifierProperty(PsiModifier.STATIC)) continue
if (!visibilityFilterMayIncludeAccessible && method.hasModifierProperty(PsiModifier.PRIVATE)) continue
val descriptor = method.getJavaMethodDescriptor() ?: continue
val descriptor = method.getJavaMethodDescriptor(resolutionFacade) ?: continue
val container = descriptor.containingDeclaration as? ClassDescriptor ?: continue
if (descriptorKindFilter.accepts(descriptor) && descriptorFilter(descriptor)) {
result.add(descriptor)