assorted Find Usages fixes: add missing read actions, better cancellation, fix search of componentN() usages for data class primary constructor parameter, remove workaround for usage of IDEA API which is now public

This commit is contained in:
Dmitry Jemerov
2015-09-04 18:07:08 +02:00
parent ccf78b6fa4
commit 55640ac885
9 changed files with 125 additions and 91 deletions
@@ -24,6 +24,7 @@ import com.intellij.psi.search.*
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.util.Processor
import org.jetbrains.kotlin.asJava.*
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.JetFileType
import org.jetbrains.kotlin.idea.references.JetSimpleNameReference
import org.jetbrains.kotlin.idea.search.KOTLIN_NAMED_ARGUMENT_SEARCH_CONTEXT
@@ -47,7 +48,7 @@ data class KotlinReferencesSearchOptions(val acceptCallableOverrides: Boolean =
}
public class KotlinReferencesSearchParameters(elementToSearch: PsiElement,
scope: SearchScope = elementToSearch.project.allScope(),
scope: SearchScope = runReadAction { elementToSearch.project.allScope() },
ignoreAccessScope: Boolean = false,
optimizer: SearchRequestCollector? = null,
val kotlinOptions: KotlinReferencesSearchOptions = KotlinReferencesSearchOptions.Empty)
@@ -61,8 +62,8 @@ public class KotlinReferencesSearcher : QueryExecutorBase<PsiReference, Referenc
val unwrappedElement = element.namedUnwrappedElement ?: return
val classNameForCompanionObject = unwrappedElement.getClassNameForCompanionObject()
val words = runReadAction {
val classNameForCompanionObject = unwrappedElement.getClassNameForCompanionObject()
unwrappedElement.getSpecialNamesToSearch() +
(if (classNameForCompanionObject != null) listOf(classNameForCompanionObject) else emptyList())
}
@@ -185,17 +186,19 @@ public class KotlinReferencesSearcher : QueryExecutorBase<PsiReference, Referenc
val kotlinReferencesSearchOptions = (queryParameters as? KotlinReferencesSearchParameters)?.kotlinOptions
if (kotlinReferencesSearchOptions?.acceptCompanionObjectMembers == true) {
val originClass = element.getStrictParentOfType<JetClass>()
val originLightClass = LightClassUtil.getPsiClass(originClass)
if (originLightClass != null) {
val lightDeclarations: List<KotlinLightElement<*, *>?> =
originLightClass.methods.map { it as? KotlinLightMethod } +
originLightClass.fields.map { it as? KotlinLightFieldForDeclaration }
runReadAction {
val originClass = element.getStrictParentOfType<JetClass>()
val originLightClass = LightClassUtil.getPsiClass(originClass)
if (originLightClass != null) {
val lightDeclarations: List<KotlinLightElement<*, *>?> =
originLightClass.methods.map { it as? KotlinLightMethod } +
originLightClass.fields.map { it as? KotlinLightFieldForDeclaration }
for (declaration in element.declarations) {
val lightDeclaration = lightDeclarations.find { it?.getOrigin() == declaration }
if (lightDeclaration != null) {
searchNamedElement(queryParameters, lightDeclaration)
for (declaration in element.declarations) {
val lightDeclaration = lightDeclarations.find { it?.getOrigin() == declaration }
if (lightDeclaration != null) {
searchNamedElement(queryParameters, lightDeclaration)
}
}
}
}
@@ -224,6 +227,17 @@ public class KotlinReferencesSearcher : QueryExecutorBase<PsiReference, Referenc
searchNamedElement(queryParameters, propertyMethods.setter)
}
private fun searchDataClassComponentUsages(queryParameters: ReferencesSearch.SearchParameters,
containingClass: PsiClass?,
componentFunctionDescriptor: FunctionDescriptor) {
val componentFunction = containingClass?.methods?.find {
it.name == componentFunctionDescriptor.name.asString() && it.parameterList.parametersCount == 0
}
if (componentFunction != null) {
searchNamedElement(queryParameters, componentFunction)
}
}
private fun searchLightElements(queryParameters: ReferencesSearch.SearchParameters, element: PsiElement) {
when (element) {
is JetClassOrObject -> processJetClassOrObject(element, queryParameters)
@@ -235,7 +249,7 @@ public class KotlinReferencesSearcher : QueryExecutorBase<PsiReference, Referenc
searchNamedElement(queryParameters, method)
}
val staticFromCompanionObject = findStaticMethodFromCompanionObject(element)
val staticFromCompanionObject = runReadAction { findStaticMethodFromCompanionObject(element) }
if (staticFromCompanionObject != null) {
searchNamedElement(queryParameters, staticFromCompanionObject)
}
@@ -246,10 +260,18 @@ public class KotlinReferencesSearcher : QueryExecutorBase<PsiReference, Referenc
searchNamedElement(queryParameters, propertyMethods.getter)
searchNamedElement(queryParameters, propertyMethods.setter)
searchNamedElement(queryParameters, propertyMethods.backingField)
}
is JetParameter -> {
searchPropertyMethods(queryParameters, element)
runReadAction {
val componentFunctionDescriptor = element.dataClassComponentFunction()
if (componentFunctionDescriptor != null) {
val containingClass = LightClassUtil.getPsiClass(element.getStrictParentOfType<JetClassOrObject>())
searchDataClassComponentUsages(queryParameters, containingClass, componentFunctionDescriptor)
}
}
}
is KotlinLightMethod -> {
@@ -262,7 +284,7 @@ public class KotlinReferencesSearcher : QueryExecutorBase<PsiReference, Referenc
searchNamedElement(queryParameters, property)
}
else if (declaration is JetFunction) {
val staticFromCompanionObject = findStaticMethodFromCompanionObject(declaration)
val staticFromCompanionObject = runReadAction { findStaticMethodFromCompanionObject(declaration) }
if (staticFromCompanionObject != null) {
searchNamedElement(queryParameters, staticFromCompanionObject)
}
@@ -271,24 +293,21 @@ public class KotlinReferencesSearcher : QueryExecutorBase<PsiReference, Referenc
is KotlinLightParameter -> {
val origin = element.getOrigin() ?: return
val componentFunctionDescriptor = origin.dataClassComponentFunction()
if (componentFunctionDescriptor != null) {
val containingClass = element.method.containingClass
val componentFunction = containingClass?.methods?.find {
it.name == componentFunctionDescriptor.name.asString() && it.parameterList.parametersCount == 0
}
if (componentFunction != null) {
searchNamedElement(queryParameters, componentFunction)
runReadAction {
val componentFunctionDescriptor = origin.dataClassComponentFunction()
if (componentFunctionDescriptor != null) {
searchDataClassComponentUsages(queryParameters, element.method.containingClass, componentFunctionDescriptor)
}
}
searchPropertyMethods(queryParameters, origin)
}
}
}
private fun isOnlyKotlinSearch(searchScope: SearchScope) =
searchScope is LocalSearchScope && searchScope.getScope().all { it.getContainingFile().getFileType() == JetFileType.INSTANCE }
searchScope is LocalSearchScope && runReadAction {
searchScope.getScope().all { it.getContainingFile().getFileType() == JetFileType.INSTANCE }
}
private fun searchNamedElement(queryParameters: ReferencesSearch.SearchParameters,
element: PsiNamedElement?,
@@ -106,34 +106,34 @@ private fun JetElement.getConstructorCallDescriptor(): DeclarationDescriptor? {
return null
}
public fun PsiElement.processDelegationCallConstructorUsages(scope: SearchScope, process: (JetCallElement) -> Unit) {
processDelegationCallKotlinConstructorUsages(scope, process)
processDelegationCallJavaConstructorUsages(scope, process)
public fun PsiElement.processDelegationCallConstructorUsages(scope: SearchScope, process: (JetCallElement) -> Boolean): Boolean {
if (!processDelegationCallKotlinConstructorUsages(scope, process)) return false
return processDelegationCallJavaConstructorUsages(scope, process)
}
private fun PsiElement.processDelegationCallKotlinConstructorUsages(scope: SearchScope, process: (JetCallElement) -> Unit) {
private fun PsiElement.processDelegationCallKotlinConstructorUsages(scope: SearchScope, process: (JetCallElement) -> Boolean): Boolean {
val element = unwrapped
val klass = when (element) {
is JetConstructor<*> -> element.getContainingClassOrObject()
is JetClass -> element
else -> return
else -> return true
}
if (klass !is JetClass || element !is JetDeclaration) return
val descriptor = element.constructor ?: return
if (klass !is JetClass || element !is JetDeclaration) return true
val descriptor = element.constructor ?: return true
processClassDelegationCallsToSpecifiedConstructor(klass, descriptor, process)
processInheritorsDelegatingCallToSpecifiedConstructor(klass, scope, descriptor, process)
if (!processClassDelegationCallsToSpecifiedConstructor(klass, descriptor, process)) return false
return processInheritorsDelegatingCallToSpecifiedConstructor(klass, scope, descriptor, process)
}
private fun PsiElement.processDelegationCallJavaConstructorUsages(scope: SearchScope, process: (JetCallElement) -> Unit) {
if (this is KotlinLightElement<*, *>) return
private fun PsiElement.processDelegationCallJavaConstructorUsages(scope: SearchScope, process: (JetCallElement) -> Boolean): Boolean {
if (this is KotlinLightElement<*, *>) return true
// TODO: Temporary hack to avoid NPE while KotlinNoOriginLightMethod is around
if (this is KotlinNoOriginLightMethod) return
if (!(this is PsiMethod && isConstructor())) return
val klass = getContainingClass() ?: return
val descriptor = getJavaMethodDescriptor() as? ConstructorDescriptor ?: return
processInheritorsDelegatingCallToSpecifiedConstructor(klass, scope, descriptor, process)
if (this is KotlinNoOriginLightMethod) return true
if (!(this is PsiMethod && isConstructor())) return true
val klass = getContainingClass() ?: return true
val descriptor = getJavaMethodDescriptor() as? ConstructorDescriptor ?: return true
return processInheritorsDelegatingCallToSpecifiedConstructor(klass, scope, descriptor, process)
}
@@ -141,34 +141,36 @@ private fun processInheritorsDelegatingCallToSpecifiedConstructor(
klass: PsiElement,
scope: SearchScope,
descriptor: ConstructorDescriptor,
process: (JetCallElement) -> Unit
) {
HierarchySearchRequest(klass, scope, false).searchInheritors().forEach() {
process: (JetCallElement) -> Boolean
): Boolean {
return HierarchySearchRequest(klass, scope, false).searchInheritors().all {
val unwrapped = it.unwrapped
if (unwrapped is JetClass) {
processClassDelegationCallsToSpecifiedConstructor(unwrapped, descriptor, process)
}
} else
true
}
}
private fun processClassDelegationCallsToSpecifiedConstructor(
klass: JetClass, constructor: DeclarationDescriptor, process: (JetCallElement) -> Unit
) {
klass: JetClass, constructor: DeclarationDescriptor, process: (JetCallElement) -> Boolean
): Boolean {
for (secondaryConstructor in klass.getSecondaryConstructors()) {
val delegationCallDescriptor = secondaryConstructor.getDelegationCall().getConstructorCallDescriptor()
if (constructor == delegationCallDescriptor) {
process(secondaryConstructor.getDelegationCall())
if (!process(secondaryConstructor.getDelegationCall())) return false
}
}
if (!klass.isEnum()) return
if (!klass.isEnum()) return true
for (declaration in klass.declarations) {
if (declaration is JetEnumEntry) {
val delegationCall = declaration.getDelegationSpecifiers().firstOrNull()
if (delegationCall is JetDelegatorToSuperCall && constructor == delegationCall.calleeExpression.getConstructorCallDescriptor()) {
process(delegationCall)
if (!process(delegationCall)) return false
}
}
}
return true
}
// Check if reference resolves to extension function whose receiver is the same as declaration's parent (or its superclass)
@@ -16,10 +16,7 @@
package org.jetbrains.kotlin.idea.findUsages.handlers
import com.intellij.find.findUsages.AbstractFindUsagesDialog
import com.intellij.find.findUsages.FindUsagesOptions
import com.intellij.find.findUsages.JavaFindUsagesHandler
import com.intellij.find.findUsages.JavaFindUsagesHandlerFactory
import com.intellij.find.findUsages.*
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
@@ -47,6 +44,7 @@ import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
import org.jetbrains.kotlin.idea.search.usagesSearch.isConstructorUsage
import org.jetbrains.kotlin.idea.search.usagesSearch.isImportUsage
import org.jetbrains.kotlin.idea.search.usagesSearch.processDelegationCallConstructorUsages
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.effectiveDeclarations
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
@@ -110,13 +108,15 @@ public class KotlinFindClassUsagesHandler(
}
if (kotlinOptions.searchConstructorUsages) {
val constructors = classOrObject.toLightClass()?.getConstructors() ?: PsiMethod.EMPTY_ARRAY
for (constructor in constructors) {
if (constructor !is KotlinLightMethod) continue
constructor.processDelegationCallConstructorUsages(constructor.getUseScope()) {
it.getCalleeExpression()?.mainReference?.let { referenceProcessor.process(it) }
val result = runReadAction {
val constructors = classOrObject.toLightClass()?.getConstructors() ?: PsiMethod.EMPTY_ARRAY
constructors.filterIsInstance<KotlinLightMethod>().all { constructor ->
constructor.processDelegationCallConstructorUsages(constructor.getUseScope()) {
it.getCalleeExpression()?.mainReference?.let { referenceProcessor.process(it) } ?: false
}
}
}
if (!result) return false
}
if (options.isDerivedClasses || options.isDerivedInterfaces) {
@@ -148,27 +148,29 @@ public class KotlinFindClassUsagesHandler(
private fun processCompanionObjectInternalReferences(companionObject: JetObjectDeclaration,
processor: Processor<PsiReference>): Boolean {
val klass = companionObject.getStrictParentOfType<JetClass>() ?: return true
val companionObjectDescriptor = companionObject.descriptor
var stop: Boolean = false
klass.acceptChildren(object : JetVisitorVoid() {
override fun visitJetElement(element: JetElement) {
if (element == companionObject) return // skip companion object itself
if (stop) return
element.acceptChildren(this)
runReadAction {
val klass = companionObject.getStrictParentOfType<JetClass>() ?: return@runReadAction
val companionObjectDescriptor = companionObject.descriptor
klass.acceptChildren(object : JetVisitorVoid() {
override fun visitJetElement(element: JetElement) {
if (element == companionObject) return // skip companion object itself
if (stop) return
element.acceptChildren(this)
val bindingContext = element.analyze()
val resolvedCall = bindingContext[BindingContext.CALL, element]?.getResolvedCall(bindingContext) ?: return
if ((resolvedCall.getDispatchReceiver() as? ClassReceiver)?.getDeclarationDescriptor() == companionObjectDescriptor
|| (resolvedCall.getExtensionReceiver() as? ClassReceiver)?.getDeclarationDescriptor() == companionObjectDescriptor) {
element.getReferences().forEach {
if (!stop && !processor.process(it)) {
stop = true
val bindingContext = element.analyze()
val resolvedCall = bindingContext[BindingContext.CALL, element]?.getResolvedCall(bindingContext) ?: return
if ((resolvedCall.getDispatchReceiver() as? ClassReceiver)?.getDeclarationDescriptor() == companionObjectDescriptor
|| (resolvedCall.getExtensionReceiver() as? ClassReceiver)?.getDeclarationDescriptor() == companionObjectDescriptor) {
element.getReferences().forEach {
if (!stop && !processor.process(it)) {
stop = true
}
}
}
}
}
})
})
}
return !stop
}
@@ -191,15 +193,7 @@ public class KotlinFindClassUsagesHandler(
else -> null
} ?: return Collections.emptyList()
// Work around the protected method in JavaFindUsagesHandler
// todo: Use JavaFindUsagesHelper.getElementNames() when it becomes public in IDEA
var stringsToSearch: Collection<String>
object: JavaFindUsagesHandler(psiClass, JavaFindUsagesHandlerFactory.getInstance(element.getProject())) {
init {
stringsToSearch = getStringsToSearch(psiClass)!!
}
}
return stringsToSearch
return JavaFindUsagesHelper.getElementNames(psiClass)
}
protected override fun isSearchForTextOccurencesAvailable(psiElement: PsiElement, isSingleFile: Boolean): Boolean {
@@ -39,7 +39,6 @@ import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashSet;
import com.intellij.util.containers.MultiMap;
import kotlin.KotlinPackage;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -330,9 +329,9 @@ public class JetChangeSignatureUsageProcessor implements ChangeSignatureUsagePro
UsagesSearchPackage.processDelegationCallConstructorUsages(
functionPsi,
functionPsi.getUseScope(),
new Function1<JetCallElement, Unit>() {
new Function1<JetCallElement, Boolean>() {
@Override
public Unit invoke(JetCallElement element) {
public Boolean invoke(JetCallElement element) {
if (element instanceof JetConstructorDelegationCall) {
result.add(new JetConstructorDelegationCallUsage((JetConstructorDelegationCall) element, changeInfo));
}
@@ -340,7 +339,7 @@ public class JetChangeSignatureUsageProcessor implements ChangeSignatureUsagePro
result.add(new JetFunctionCallUsage(element, functionUsageInfo));
}
return null;
return true;
}
}
);
@@ -481,13 +480,13 @@ public class JetChangeSignatureUsageProcessor implements ChangeSignatureUsagePro
UsagesSearchPackage.processDelegationCallConstructorUsages(
psiMethod,
psiMethod.getUseScope(),
new Function1<JetCallElement, Unit>() {
new Function1<JetCallElement, Boolean>() {
@Override
public Unit invoke(JetCallElement element) {
public Boolean invoke(JetCallElement element) {
if (element instanceof JetConstructorDelegationCall) {
result.add(new JavaConstructorDeferredUsageInDelegationCall((JetConstructorDelegationCall) element));
}
return null;
return true;
}
}
);
@@ -28,7 +28,7 @@ public class KotlinConstructorDelegationCallReferenceSearcher() : QueryExecutorB
if (!method.isConstructor()) return
method.processDelegationCallConstructorUsages(method.getUseScope()) {
it.getCalleeExpression()?.getReference()?.let { consumer.process(it) }
it.getCalleeExpression()?.getReference()?.let { consumer.process(it) } ?: true
}
}
}
@@ -0,0 +1,6 @@
// PSI_ELEMENT: org.jetbrains.kotlin.psi.JetParameter
// OPTIONS: usages
package test
public data class KotlinDataClass(val <caret>foo: Int, val bar: String) {
}
@@ -0,0 +1,7 @@
package test;
public class JavaUser {
int use(KotlinDataClass dataClass) {
return dataClass.component1();
}
}
@@ -0,0 +1 @@
Unclassified usage (5: 26) return dataClass.component1();
@@ -735,6 +735,12 @@ public class JetFindUsagesTestGenerated extends AbstractJetFindUsagesTest {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/findUsages/kotlin/findParameterUsages"), Pattern.compile("^(.+)\\.0\\.kt$"), true);
}
@TestMetadata("kotlinComponentFunctionParameterUsages.0.kt")
public void testKotlinComponentFunctionParameterUsages() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/findParameterUsages/kotlinComponentFunctionParameterUsages.0.kt");
doTest(fileName);
}
@TestMetadata("kotlinConstructorParameterUsages.0.kt")
public void testKotlinConstructorParameterUsages() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/findParameterUsages/kotlinConstructorParameterUsages.0.kt");