From 4e0c3fcee74a7ce9789a816235eb103ce5b300e0 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Sun, 4 Sep 2016 14:17:43 +0300 Subject: [PATCH] Refactored reference searches to take read-actions when necessary --- .../classInheritorsSearch.kt | 12 +- .../KotlinReferencesSearcher.kt | 355 +++++++++--------- .../ExpressionsOfTypeProcessor.kt | 128 ++++--- .../usagesSearch/OperatorReferenceSearch.kt | 37 +- .../kotlin/idea/search/usagesSearch/utils.kt | 52 +-- .../handlers/KotlinFindClassUsagesHandler.kt | 174 ++++----- .../handlers/KotlinFindMemberUsagesHandler.kt | 105 +++--- .../handlers/KotlinFindUsagesHandler.kt | 22 +- .../KotlinTypeParameterFindUsagesHandler.kt | 11 +- .../KotlinChangeSignatureUsageProcessor.kt | 6 +- .../safeDelete/KotlinSafeDeleteProcessor.kt | 6 +- ...structorDelegationCallReferenceSearcher.kt | 6 +- .../findUsages/AbstractFindUsagesTest.kt | 15 +- 13 files changed, 509 insertions(+), 420 deletions(-) diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/declarationsSearch/classInheritorsSearch.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/declarationsSearch/classInheritorsSearch.kt index dcd1864fe6b..f3468285f14 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/declarationsSearch/classInheritorsSearch.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/declarationsSearch/classInheritorsSearch.kt @@ -23,15 +23,15 @@ import com.intellij.psi.search.searches.ClassInheritorsSearch import com.intellij.util.EmptyQuery import com.intellij.util.Query import org.jetbrains.kotlin.asJava.toLightClass +import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.psi.KtClassOrObject fun HierarchySearchRequest<*>.searchInheritors(): Query { - val psiClass: PsiClass? = when (originalElement) { - is KtClassOrObject -> originalElement.toLightClass() - is PsiClass -> originalElement - else -> null - } - if (psiClass == null) return EmptyQuery.getEmptyQuery() + val psiClass: PsiClass = when (originalElement) { + is KtClassOrObject -> runReadAction { originalElement.toLightClass() } + is PsiClass -> originalElement + else -> null + } ?: return EmptyQuery.getEmptyQuery() return ClassInheritorsSearch.search( psiClass, diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinReferencesSearcher.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinReferencesSearcher.kt index ff58aa60197..e2d9a90d196 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinReferencesSearcher.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinReferencesSearcher.kt @@ -31,7 +31,6 @@ import org.jetbrains.kotlin.asJava.namedUnwrappedElement import org.jetbrains.kotlin.asJava.toLightClass import org.jetbrains.kotlin.asJava.toLightElements import org.jetbrains.kotlin.descriptors.FunctionDescriptor -import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.references.KtSimpleNameReference import org.jetbrains.kotlin.idea.search.KOTLIN_NAMED_ARGUMENT_SEARCH_CONTEXT import org.jetbrains.kotlin.idea.search.allScope @@ -46,6 +45,8 @@ import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.psi.psiUtil.parents +import org.jetbrains.kotlin.utils.singletonOrEmptyList +import java.util.* data class KotlinReferencesSearchOptions(val acceptCallableOverrides: Boolean = false, val acceptOverloads: Boolean = false, @@ -70,255 +71,245 @@ class KotlinReferencesSearchParameters(elementToSearch: PsiElement, } class KotlinReferencesSearcher : QueryExecutorBase() { - override fun processQuery(queryParameters: ReferencesSearch.SearchParameters, consumer: Processor) { - val element = queryParameters.elementToSearch + val processor = QueryProcessor(queryParameters, consumer) + runReadAction { processor.processInReadAction() } + processor.executeLongRunningTasks() + } - val unwrappedElement = element.namedUnwrappedElement ?: return + private class QueryProcessor(val queryParameters: ReferencesSearch.SearchParameters, val consumer: Processor) { - val kotlinOptions = (queryParameters as? KotlinReferencesSearchParameters)?.kotlinOptions - ?: KotlinReferencesSearchOptions.Empty + private val kotlinOptions = (queryParameters as? KotlinReferencesSearchParameters)?.kotlinOptions + ?: KotlinReferencesSearchOptions.Empty - val specialSymbols = runReadAction { unwrappedElement.getSpecialNamesToSearch(kotlinOptions) } - val words = runReadAction { - val classNameForCompanionObject = unwrappedElement.getClassNameForCompanionObject() - (specialSymbols?.first ?: emptyList()) + - (if (classNameForCompanionObject != null) listOf(classNameForCompanionObject) else emptyList()) + private val longTasks = ArrayList<() -> Unit>() + + fun executeLongRunningTasks() { + longTasks.forEach { it() } } - val effectiveSearchScope = runReadAction { + fun processInReadAction() { + val element = queryParameters.elementToSearch + if (!element.isValid) return + + val unwrappedElement = element.namedUnwrappedElement ?: return + + val specialSymbols = unwrappedElement.getSpecialNamesToSearch(kotlinOptions) + val words = (specialSymbols?.first ?: emptyList()) + unwrappedElement.getClassNameForCompanionObject().singletonOrEmptyList() + val elements = if (unwrappedElement is KtDeclaration) unwrappedElement.toLightElements() else listOf(unwrappedElement) - elements.fold(queryParameters.effectiveSearchScope) { scope, e -> scope.unionSafe(queryParameters.effectiveSearchScope(e)) } - } - - val refFilter: (PsiReference) -> Boolean = when { - unwrappedElement is KtParameter -> ({ ref: PsiReference -> !ref.isNamedArgumentReference()/* they are processed later*/ }) - specialSymbols != null -> { ref -> ref.javaClass == specialSymbols.second } - else -> ({true}) - } - - val resultProcessor = KotlinRequestResultProcessor(unwrappedElement, filter = refFilter, options = kotlinOptions) - - val name = runReadAction { unwrappedElement.name } - if (kotlinOptions.anyEnabled()) { - if (name != null) { - queryParameters.optimizer.searchWord(name, effectiveSearchScope, UsageSearchContext.IN_CODE, true, unwrappedElement, - resultProcessor) + val effectiveSearchScope = elements.fold(queryParameters.effectiveSearchScope) { scope, e -> + scope.unionSafe(queryParameters.effectiveSearchScope(e)) } - } - words.forEach { word -> - queryParameters.optimizer.searchWord(word, effectiveSearchScope, - UsageSearchContext.ANY, true, unwrappedElement, - resultProcessor) - } - if (unwrappedElement is KtParameter && kotlinOptions.searchNamedArguments) { - runReadAction { searchNamedArguments(unwrappedElement, queryParameters) } - } + val refFilter: (PsiReference) -> Boolean = when { + unwrappedElement is KtParameter -> ({ ref: PsiReference -> !ref.isNamedArgumentReference()/* they are processed later*/ }) + specialSymbols != null -> { ref -> ref.javaClass == specialSymbols.second } + else -> ({true}) + } - if (!(unwrappedElement is KtElement && isOnlyKotlinSearch(effectiveSearchScope))) { - searchLightElements(queryParameters, element, consumer) - } + val resultProcessor = KotlinRequestResultProcessor(unwrappedElement, filter = refFilter, options = kotlinOptions) - if (element is KtFunction || element is PsiMethod) { - val referenceSearcher = OperatorReferenceSearcher.create( - element, effectiveSearchScope, consumer, queryParameters.optimizer, kotlinOptions) - referenceSearcher?.run() - } - - if (kotlinOptions.searchForComponentConventions) { - when (element) { - is KtParameter -> { - val componentFunctionDescriptor = runReadAction { element.dataClassComponentFunction() } - if (componentFunctionDescriptor != null) { - val containingClass = element.getStrictParentOfType()?.toLightClass() - searchDataClassComponentUsages(queryParameters, containingClass, componentFunctionDescriptor, consumer, kotlinOptions) - } - } - - is KtLightParameter -> { - val componentFunctionDescriptor = runReadAction { element.kotlinOrigin?.dataClassComponentFunction() } - if (componentFunctionDescriptor != null) { - searchDataClassComponentUsages(queryParameters, element.method.containingClass, componentFunctionDescriptor, consumer, kotlinOptions) - } + val name = unwrappedElement.name + if (kotlinOptions.anyEnabled()) { + if (name != null) { + queryParameters.optimizer.searchWord( + name, effectiveSearchScope, UsageSearchContext.IN_CODE, true, unwrappedElement, resultProcessor) } } - } - } + words.forEach { word -> + queryParameters.optimizer.searchWord( + word, effectiveSearchScope, UsageSearchContext.ANY, true, unwrappedElement, resultProcessor) + } - private fun searchNamedArguments(parameter: KtParameter, queryParameters: ReferencesSearch.SearchParameters) { - val parameterName = parameter.name ?: return - val function = parameter.ownerFunction ?: return - if (function.nameAsName?.isSpecial ?: true) return - val project = function.project - var namedArgsScope = function.useScope.intersectWith(queryParameters.scopeDeterminedByUser) + if (unwrappedElement is KtParameter && kotlinOptions.searchNamedArguments) { + searchNamedArguments(unwrappedElement) + } - if (namedArgsScope is GlobalSearchScope) { - namedArgsScope = KotlinSourceFilterScope.sourcesAndLibraries(namedArgsScope, project) + if (!(unwrappedElement is KtElement && isOnlyKotlinSearch(effectiveSearchScope))) { + searchLightElements(element) + } - val filesWithFunctionName = CacheManager.SERVICE.getInstance(project).getVirtualFilesWithWord( - function.name!!, UsageSearchContext.IN_CODE, namedArgsScope, true) - namedArgsScope = GlobalSearchScope.filesScope(project, filesWithFunctionName.asList()) - } + if (element is KtFunction || element is PsiMethod) { + val referenceSearcher = OperatorReferenceSearcher.create( + element, effectiveSearchScope, consumer, queryParameters.optimizer, kotlinOptions) + if (referenceSearcher != null) { + longTasks.add { referenceSearcher.run() } + } - val processor = KotlinRequestResultProcessor(parameter, filter = { it.isNamedArgumentReference() }) - queryParameters.optimizer.searchWord(parameterName, - namedArgsScope, - KOTLIN_NAMED_ARGUMENT_SEARCH_CONTEXT, - true, - parameter, - processor) - } + } - private fun PsiReference.isNamedArgumentReference(): Boolean { - return this is KtSimpleNameReference && expression.parent is KtValueArgumentName - } - - companion object { - fun processKtClassOrObject(element: KtClassOrObject, queryParameters: ReferencesSearch.SearchParameters) { - val className = runReadAction { element.name } - if (className != null) { - val lightClass = runReadAction { element.toLightClass() } - if (lightClass != null) { - searchNamedElement(queryParameters, lightClass, className) - - if (element is KtObjectDeclaration && element.isCompanion()) { - val fieldForCompanionObject = runReadAction { LightClassUtil.getLightFieldForCompanionObject(element) } - if (fieldForCompanionObject != null) { - searchNamedElement(queryParameters, fieldForCompanionObject) + if (kotlinOptions.searchForComponentConventions) { + when (element) { + is KtParameter -> { + val componentFunctionDescriptor = element.dataClassComponentFunction() + if (componentFunctionDescriptor != null) { + val containingClass = element.getStrictParentOfType()?.toLightClass() + searchDataClassComponentUsages(containingClass, componentFunctionDescriptor, kotlinOptions) } + } - val kotlinReferencesSearchOptions = (queryParameters as? KotlinReferencesSearchParameters)?.kotlinOptions - if (kotlinReferencesSearchOptions?.acceptCompanionObjectMembers == true) { - runReadAction { - val originClass = element.getStrictParentOfType() - val originLightClass = originClass?.toLightClass() - if (originLightClass != null) { - val lightDeclarations: List?> = - originLightClass.methods.map { it as? KtLightMethod } + - originLightClass.fields.map { it as? KtLightField } - - for (declaration in element.declarations) { - val lightDeclaration = lightDeclarations.find { it?.kotlinOrigin == declaration } - if (lightDeclaration != null) { - searchNamedElement(queryParameters, lightDeclaration) - } - } - } - } + is KtLightParameter -> { + val componentFunctionDescriptor = element.kotlinOrigin?.dataClassComponentFunction() + if (componentFunctionDescriptor != null) { + searchDataClassComponentUsages(element.method.containingClass, componentFunctionDescriptor, kotlinOptions) } } } } } - private fun findStaticMethodsFromCompanionObject(declaration: KtDeclaration): List { - val originObject = declaration.parents - .dropWhile { it is KtClassBody } - .firstOrNull() as? KtObjectDeclaration ?: return emptyList() - if (originObject.isCompanion()) { - val originClass = originObject.getStrictParentOfType() - val originLightClass = originClass?.toLightClass() ?: return emptyList() - val allMethods = originLightClass.allMethods - return allMethods.filter { it is KtLightMethod && it.kotlinOrigin == declaration } + private fun searchNamedArguments(parameter: KtParameter) { + val parameterName = parameter.name ?: return + val function = parameter.ownerFunction ?: return + if (function.nameAsName?.isSpecial ?: true) return + val project = function.project + var namedArgsScope = function.useScope.intersectWith(queryParameters.scopeDeterminedByUser) + + if (namedArgsScope is GlobalSearchScope) { + namedArgsScope = KotlinSourceFilterScope.sourcesAndLibraries(namedArgsScope, project) + + val filesWithFunctionName = CacheManager.SERVICE.getInstance(project).getVirtualFilesWithWord( + function.name!!, UsageSearchContext.IN_CODE, namedArgsScope, true) + namedArgsScope = GlobalSearchScope.filesScope(project, filesWithFunctionName.asList()) } - return emptyList() + + val processor = KotlinRequestResultProcessor(parameter, filter = { it.isNamedArgumentReference() }) + queryParameters.optimizer.searchWord(parameterName, + namedArgsScope, + KOTLIN_NAMED_ARGUMENT_SEARCH_CONTEXT, + true, + parameter, + processor) } - private fun processStaticsFromCompanionObject(element: KtDeclaration, queryParameters: ReferencesSearch.SearchParameters) { - val staticsFromCompanionObject = runReadAction { findStaticMethodsFromCompanionObject(element) } - staticsFromCompanionObject.forEach { searchNamedElement(queryParameters, it) } - } - - private fun searchPropertyMethods(queryParameters: ReferencesSearch.SearchParameters, parameter: KtParameter) { - val lightElements = runReadAction { parameter.toLightElements() } - lightElements.forEach { searchNamedElement(queryParameters, it) } - } - - private fun searchDataClassComponentUsages(queryParameters: ReferencesSearch.SearchParameters, - containingClass: PsiClass?, - componentFunctionDescriptor: FunctionDescriptor, - consumer: Processor, - kotlinOptions: KotlinReferencesSearchOptions - ) { - val componentFunction = containingClass?.methods?.find { - it.name == componentFunctionDescriptor.name.asString() && it.parameterList.parametersCount == 0 - } - if (componentFunction != null) { - searchNamedElement(queryParameters, componentFunction) - val searcher = OperatorReferenceSearcher.create( - componentFunction, queryParameters.effectiveSearchScope, consumer, queryParameters.optimizer, kotlinOptions) - searcher!!.run() - } - } - - private fun searchLightElements(queryParameters: ReferencesSearch.SearchParameters, element: PsiElement, consumer: Processor) { + private fun searchLightElements(element: PsiElement) { when (element) { - is KtClassOrObject -> processKtClassOrObject(element, queryParameters) + is KtClassOrObject -> { + processKtClassOrObject(element) + } + is KtNamedFunction, is KtSecondaryConstructor -> { - val function = element as KtFunction - val name = runReadAction { function.name } + val name = (element as KtFunction).name if (name != null) { - val methods = runReadAction { LightClassUtil.getLightClassMethods(function) } + val methods = LightClassUtil.getLightClassMethods(element) for (method in methods) { - searchNamedElement(queryParameters, method) + searchNamedElement(method) } } - processStaticsFromCompanionObject(element, queryParameters) + processStaticsFromCompanionObject(element) } is KtProperty -> { - val propertyMethods = runReadAction { LightClassUtil.getLightClassPropertyMethods(element) } - propertyMethods.allDeclarations.forEach { searchNamedElement(queryParameters, it) } - processStaticsFromCompanionObject(element, queryParameters) + val propertyMethods = LightClassUtil.getLightClassPropertyMethods(element) + propertyMethods.allDeclarations.forEach { searchNamedElement(it) } + processStaticsFromCompanionObject(element) } is KtParameter -> { - searchPropertyMethods(queryParameters, element) + searchPropertyAccessorMethods(element) } is KtLightMethod -> { val declaration = element.kotlinOrigin if (declaration is KtProperty || (declaration is KtParameter && declaration.hasValOrVar())) { - searchNamedElement(queryParameters, declaration as PsiNamedElement) - processStaticsFromCompanionObject(declaration, queryParameters) + searchNamedElement(declaration as PsiNamedElement) + processStaticsFromCompanionObject(declaration) } else if (declaration is KtPropertyAccessor) { val property = declaration.getStrictParentOfType() - searchNamedElement(queryParameters, property) + searchNamedElement(property) } else if (declaration is KtFunction) { - processStaticsFromCompanionObject(declaration, queryParameters) + processStaticsFromCompanionObject(declaration) } } is KtLightParameter -> { val origin = element.kotlinOrigin ?: return - searchPropertyMethods(queryParameters, origin) + searchPropertyAccessorMethods(origin) } } } - private fun isOnlyKotlinSearch(searchScope: SearchScope) = - searchScope is LocalSearchScope && runReadAction { - searchScope.scope.all { it.containingFile.fileType == KotlinFileType.INSTANCE } - } + private fun searchPropertyAccessorMethods(origin: KtParameter) { + origin.toLightElements().forEach { searchNamedElement(it) } + } - private fun searchNamedElement(queryParameters: ReferencesSearch.SearchParameters, - element: PsiNamedElement?, - name: String? = element?.name) { + private fun processKtClassOrObject(element: KtClassOrObject) { + val className = element.name ?: return + val lightClass = element.toLightClass() ?: return + searchNamedElement(lightClass, className) + + if (element is KtObjectDeclaration && element.isCompanion()) { + LightClassUtil.getLightFieldForCompanionObject(element)?.let { searchNamedElement(it) } + + if (kotlinOptions.acceptCompanionObjectMembers) { + val originLightClass = element.getStrictParentOfType()?.toLightClass() + if (originLightClass != null) { + val lightDeclarations: List?> = + originLightClass.methods.map { it as? KtLightMethod } + + originLightClass.fields.map { it as? KtLightField } + + for (declaration in element.declarations) { + lightDeclarations + .firstOrNull { it?.kotlinOrigin == declaration } + ?.let { searchNamedElement(it) } + } + } + } + } + } + + private fun searchDataClassComponentUsages(containingClass: PsiClass?, + componentFunctionDescriptor: FunctionDescriptor, + kotlinOptions: KotlinReferencesSearchOptions + ) { + val componentFunction = containingClass?.methods?.firstOrNull { + it.name == componentFunctionDescriptor.name.asString() && it.parameterList.parametersCount == 0 + } + if (componentFunction != null) { + searchNamedElement(componentFunction) + + val searcher = OperatorReferenceSearcher.create( + componentFunction, queryParameters.effectiveSearchScope, consumer, queryParameters.optimizer, kotlinOptions) + longTasks.add { searcher!!.run() } + } + } + + private fun isOnlyKotlinSearch(searchScope: SearchScope): Boolean { + return searchScope is LocalSearchScope && searchScope.scope.all { it.containingFile is KtFile } + } + + private fun processStaticsFromCompanionObject(element: KtDeclaration) { + findStaticMethodsFromCompanionObject(element).forEach { searchNamedElement(it) } + } + + private fun findStaticMethodsFromCompanionObject(declaration: KtDeclaration): List { + val originObject = declaration.parents + .dropWhile { it is KtClassBody } + .firstOrNull() as? KtObjectDeclaration ?: return emptyList() + if (!originObject.isCompanion()) return emptyList() + val originClass = originObject.getStrictParentOfType() + val originLightClass = originClass?.toLightClass() ?: return emptyList() + val allMethods = originLightClass.allMethods + return allMethods.filter { it is KtLightMethod && it.kotlinOrigin == declaration } + } + + private fun searchNamedElement(element: PsiNamedElement?, name: String? = element?.name) { if (name != null && element != null) { - val scope = runReadAction { queryParameters.effectiveSearchScope(element) } + val scope = queryParameters.effectiveSearchScope(element) val context = UsageSearchContext.IN_CODE + UsageSearchContext.IN_FOREIGN_LANGUAGES + UsageSearchContext.IN_COMMENTS - val kotlinOptions = (queryParameters as? KotlinReferencesSearchParameters)?.kotlinOptions - ?: KotlinReferencesSearchOptions.Empty val resultProcessor = KotlinRequestResultProcessor(element, queryParameters.elementToSearch.namedUnwrappedElement ?: element, options = kotlinOptions) - queryParameters.optimizer.searchWord(name, scope, context.toShort(), true, element, - resultProcessor) + queryParameters.optimizer.searchWord(name, scope, context.toShort(), true, element, resultProcessor) } } + + private fun PsiReference.isNamedArgumentReference(): Boolean { + return this is KtSimpleNameReference && expression.parent is KtValueArgumentName + } } } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/ExpressionsOfTypeProcessor.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/ExpressionsOfTypeProcessor.kt index 0d01e9d7583..d5d5d1a21df 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/ExpressionsOfTypeProcessor.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/ExpressionsOfTypeProcessor.kt @@ -47,6 +47,7 @@ import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchPar import org.jetbrains.kotlin.idea.search.restrictToKotlinSources import org.jetbrains.kotlin.idea.util.FuzzyType import org.jetbrains.kotlin.idea.util.ProjectRootsUtil +import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.kdoc.psi.impl.KDocName import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.load.java.sam.SingleAbstractMethodUtils @@ -78,18 +79,20 @@ class ExpressionsOfTypeProcessor( var testLog: MutableList? = null fun logPresentation(element: PsiElement): String? { - if (element !is KtDeclaration && element !is PsiMember) return element.text - val fqName = element.getKotlinFqName()?.asString() - ?: (element as? KtNamedDeclaration)?.name - return when (element) { - is PsiMethod -> fqName + element.parameterList.text - is KtFunction -> fqName + element.valueParameterList!!.text - is KtParameter -> { - val owner = element.ownerFunction?.let { logPresentation(it) } ?: element.parent.toString() - "parameter ${element.name} of $owner" + return runReadAction { + if (element !is KtDeclaration && element !is PsiMember) return@runReadAction element.text + val fqName = element.getKotlinFqName()?.asString() + ?: (element as? KtNamedDeclaration)?.name + when (element) { + is PsiMethod -> fqName + element.parameterList.text + is KtFunction -> fqName + element.valueParameterList!!.text + is KtParameter -> { + val owner = element.ownerFunction?.let { logPresentation(it) } ?: element.parent.toString() + "parameter ${element.name} of $owner" + } + is KtDestructuringDeclaration -> element.entries.joinToString(", ", prefix = "(", postfix = ")") { it.text } + else -> fqName } - is KtDestructuringDeclaration -> element.entries.joinToString(", ", prefix = "(", postfix = ")") { it.text } - else -> fqName } } @@ -118,18 +121,10 @@ class ExpressionsOfTypeProcessor( private val scopesToUsePlainSearch = LinkedHashMap>() fun run() { - if (searchScope.restrictToKotlinSources().isEmpty()) return // optimization - - val classDescriptor = typeToSearch.type.constructor.declarationDescriptor ?: return - val classDeclaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, classDescriptor) - val psiClass = when (classDeclaration) { - is PsiClass -> classDeclaration - is KtClassOrObject -> classDeclaration.toLightClass() ?: return - else -> return - } + val psiClass = runReadAction { detectClassToSearch() } ?: return // for class from library always use plain search because we cannot search usages in compiled code (we could though) - if (!ProjectRootsUtil.isInProjectSource(psiClass)) { + if (!runReadAction { psiClass.isValid && ProjectRootsUtil.isInProjectSource (psiClass) }) { suspiciousScopeHandler(searchScope) return } @@ -138,9 +133,26 @@ class ExpressionsOfTypeProcessor( processTasks() - val scopeElements = scopesToUsePlainSearch.values.flatMap { it }.toTypedArray() - if (scopeElements.isNotEmpty()) { - suspiciousScopeHandler(LocalSearchScope(scopeElements)) + runReadAction { + val scopeElements = scopesToUsePlainSearch.values + .flatMap { it } + .filter { it.isValid } + .toTypedArray() + if (scopeElements.isNotEmpty()) { + suspiciousScopeHandler(LocalSearchScope(scopeElements)) + } + } + } + + private fun detectClassToSearch(): PsiClass? { + if (searchScope.restrictToKotlinSources().isEmpty()) return null // optimization + + val classDescriptor = typeToSearch.type.constructor.declarationDescriptor ?: return null + val classDeclaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, classDescriptor) + return when (classDeclaration) { + is PsiClass -> classDeclaration + is KtClassOrObject -> classDeclaration.toLightClass() + else -> null } } @@ -167,19 +179,19 @@ class ExpressionsOfTypeProcessor( data class ProcessClassUsagesTask(val classToSearch: PsiClass) : Task { override fun perform() { testLog?.add("Searched references to ${logPresentation(classToSearch)}") - ReferencesSearch.search(classToSearch).forEach(Processor processor@ { reference -> //TODO: see KT-13607 - if (processClassUsage(reference)) return@processor true + searchReferences(classToSearch, GlobalSearchScope.allScope(project)) { reference -> + if (processClassUsage(reference)) return@searchReferences true if (mode != Mode.ALWAYS_SMART) { downShiftToPlainSearch() - return@processor false + return@searchReferences false } val element = reference.element val document = PsiDocumentManager.getInstance(project).getDocument(element.containingFile) val lineAndCol = DiagnosticUtils.offsetToLineAndColumn(document, element.startOffset) error("Unsupported reference: '${element.text}' in ${element.containingFile.name} line ${lineAndCol.line} column ${lineAndCol.column}") - }) + } // we must use plain search inside our class (and inheritors) because implicit 'this' can happen anywhere (classToSearch as? KtLightClass)?.kotlinOrigin?.let { usePlainSearch(it) } @@ -211,7 +223,7 @@ class ExpressionsOfTypeProcessor( testLog?.add("Searched references to ${logPresentation(declaration)} in Kotlin files") val searchParameters = KotlinReferencesSearchParameters( declaration, scope, kotlinOptions = KotlinReferencesSearchOptions(searchNamedArguments = false)) - ReferencesSearch.search(searchParameters).forEach { reference -> + searchReferences(searchParameters) { reference -> when (kind) { CallableToProcessKind.HAS_OUR_CLASS_TYPE -> { if (reference is KtDestructuringDeclarationReference) { @@ -227,6 +239,7 @@ class ExpressionsOfTypeProcessor( (reference.element as? KtReferenceExpression)?.let { processLambdasForCallableReference(it) } } } + true } } } @@ -239,13 +252,14 @@ class ExpressionsOfTypeProcessor( //TODO: what about other JVM languages? val scope = GlobalSearchScope.getScopeRestrictedByFileTypes(GlobalSearchScope.projectScope(project), JavaFileType.INSTANCE) testLog?.add("Searched references to ${logPresentation(psiClass)} in java files") - ReferencesSearch.search(psiClass, scope).forEach { reference -> + searchReferences(psiClass, scope) { reference -> // check if the reference is method parameter type val parameter = ((reference as? PsiJavaCodeReferenceElement)?.parent as? PsiTypeElement)?.parent as? PsiParameter val method = parameter?.declarationScope as? PsiMethod if (method != null) { addCallableDeclarationToProcess(method, CallableToProcessKind.PROCESS_LAMBDAS) } + true } } } @@ -559,24 +573,30 @@ class ExpressionsOfTypeProcessor( } private fun usePlainSearch(scope: KtElement) { - val file = scope.getContainingKtFile() - val restricted = LocalSearchScope(scope).intersectWith(searchScope) - if (restricted is LocalSearchScope) { - ScopeLoop@ - for (element in restricted.scope) { - val prevElements = scopesToUsePlainSearch.getOrPut(file) { ArrayList() } - for ((index, prevElement) in prevElements.withIndex()) { - if (prevElement.isAncestor(element, strict = false)) continue@ScopeLoop - if (element.isAncestor(prevElement)) { - prevElements[index] = element - continue@ScopeLoop + runReadAction { + if (!scope.isValid) return@runReadAction + + val file = scope.getContainingKtFile() + val restricted = LocalSearchScope(scope).intersectWith(searchScope) + if (restricted is LocalSearchScope) { + ScopeLoop@ + for (element in restricted.scope) { + val prevElements = scopesToUsePlainSearch.getOrPut(file) { ArrayList() } + for ((index, prevElement) in prevElements.withIndex()) { + if (!prevElement.isValid) continue@ScopeLoop + if (prevElement.isAncestor(element, strict = false)) continue@ScopeLoop + if (element.isAncestor(prevElement)) { + prevElements[index] = element + continue@ScopeLoop + } } + prevElements.add(element) } - prevElements.add(element) } - } - else { - assert(restricted == GlobalSearchScope.EMPTY_SCOPE) + else { + assert(restricted == GlobalSearchScope.EMPTY_SCOPE) + } + } } @@ -642,4 +662,22 @@ class ExpressionsOfTypeProcessor( else -> false } } + + private fun searchReferences(element: PsiElement,scope: SearchScope, processor: (PsiReference) -> Boolean) { + val parameters = ReferencesSearch.SearchParameters(element, scope, false) + searchReferences(parameters, processor) + } + + private fun searchReferences(parameters: ReferencesSearch.SearchParameters, processor: (PsiReference) -> Boolean) { + ReferencesSearch.search(parameters).forEach(Processor { ref -> + runReadAction { + if (ref.element.isValid) { + processor(ref) + } + else { + true + } + } + }) + } } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/OperatorReferenceSearch.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/OperatorReferenceSearch.kt index d0946434c28..8b37b000a21 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/OperatorReferenceSearch.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/OperatorReferenceSearch.kt @@ -79,6 +79,21 @@ abstract class OperatorReferenceSearcher( consumer: Processor, optimizer: SearchRequestCollector, options: KotlinReferencesSearchOptions + ): OperatorReferenceSearcher<*>? { + return runReadAction { + if (declaration.isValid) + _create(declaration, searchScope, consumer, optimizer, options) + else + null + } + } + + private fun _create( + declaration: PsiElement, + searchScope: SearchScope, + consumer: Processor, + optimizer: SearchRequestCollector, + options: KotlinReferencesSearchOptions ): OperatorReferenceSearcher<*>? { val functionName = when (declaration) { is KtNamedFunction -> declaration.name @@ -212,16 +227,20 @@ abstract class OperatorReferenceSearcher( if (scope is LocalSearchScope) { for (element in scope.scope) { - element.accept(object : PsiRecursiveElementWalkingVisitor() { - override fun visitElement(element: PsiElement) { - val reference = extractReference(element) - if (reference != null && reference.isReferenceTo(targetDeclaration)) { - consumer.process(reference) - } + runReadAction { + if (element.isValid) { + element.accept(object : PsiRecursiveElementWalkingVisitor() { + override fun visitElement(element: PsiElement) { + val reference = extractReference(element) + if (reference != null && reference.isReferenceTo(targetDeclaration)) { + consumer.process(reference) + } - super.visitElement(element) + super.visitElement(element) + } + }) } - }) + } } } else { @@ -238,7 +257,7 @@ abstract class OperatorReferenceSearcher( val psiManager = PsiManager.getInstance(project) ProjectRootManager.getInstance(project).fileIndex.iterateContent { file -> if (file in scope) { - val ktFile = psiManager.findFile(file) as? KtFile + val ktFile = runReadAction { psiManager.findFile(file) as? KtFile } if (ktFile != null) { doPlainSearch(LocalSearchScope(ktFile)) } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/utils.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/utils.kt index 62eeaaf128e..4a6d0de56f6 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/utils.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/utils.kt @@ -32,6 +32,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.getJavaMethodDescriptor import org.jetbrains.kotlin.idea.references.unwrappedTargets import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest import org.jetbrains.kotlin.idea.search.declarationsSearch.searchInheritors +import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.contains import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType @@ -39,6 +40,7 @@ import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.OverridingUtil +import org.jetbrains.kotlin.utils.addToStdlib.check val KtDeclaration.descriptor: DeclarationDescriptor? get() = this.analyze().get(BindingContext.DECLARATION_TO_DESCRIPTOR, this) @@ -109,36 +111,40 @@ private fun KtElement.getConstructorCallDescriptor(): DeclarationDescriptor? { return null } -fun PsiElement.processDelegationCallConstructorUsages(scope: SearchScope, process: (KtCallElement) -> Boolean): Boolean { - if (!processDelegationCallKotlinConstructorUsages(scope, process)) return false - return processDelegationCallJavaConstructorUsages(scope, process) +// should be executed under read-action, returns long-running part to be executed outside read-action +fun PsiElement.buildProcessDelegationCallConstructorUsagesTask(scope: SearchScope, process: (KtCallElement) -> Boolean): () -> Boolean { + val task1 = buildProcessDelegationCallKotlinConstructorUsagesTask(scope, process) + val task2 = buildProcessDelegationCallJavaConstructorUsagesTask(scope, process) + return { task1() && task2() } } -private fun PsiElement.processDelegationCallKotlinConstructorUsages(scope: SearchScope, process: (KtCallElement) -> Boolean): Boolean { +private fun PsiElement.buildProcessDelegationCallKotlinConstructorUsagesTask(scope: SearchScope, process: (KtCallElement) -> Boolean): () -> Boolean { val element = unwrapped - if (element != null && element !in scope) return true + if (element != null && element !in scope) return { true } val klass = when (element) { is KtConstructor<*> -> element.getContainingClassOrObject() is KtClass -> element - else -> return true + else -> return { true } } - if (klass !is KtClass || element !is KtDeclaration) return true - val descriptor = element.constructor ?: return true + if (klass !is KtClass || element !is KtDeclaration) return { true } + val descriptor = element.constructor ?: return { true } - if (!processClassDelegationCallsToSpecifiedConstructor(klass, descriptor, process)) return false - return processInheritorsDelegatingCallToSpecifiedConstructor(klass, scope, descriptor, process) + if (!processClassDelegationCallsToSpecifiedConstructor(klass, descriptor, process)) return { false } + + // long-running task, return it to execute outside read-action + return { processInheritorsDelegatingCallToSpecifiedConstructor(klass, scope, descriptor, process) } } -private fun PsiElement.processDelegationCallJavaConstructorUsages(scope: SearchScope, process: (KtCallElement) -> Boolean): Boolean { - if (this is KtLightElement<*, *>) return true +private fun PsiElement.buildProcessDelegationCallJavaConstructorUsagesTask(scope: SearchScope, process: (KtCallElement) -> Boolean): () -> Boolean { + if (this is KtLightElement<*, *>) return { true } // TODO: Temporary hack to avoid NPE while KotlinNoOriginLightMethod is around - if (this is KtLightMethod && this.kotlinOrigin == null) return true - if (!(this is PsiMethod && isConstructor)) return true - val klass = containingClass ?: return true - val descriptor = getJavaMethodDescriptor() as? ConstructorDescriptor ?: return true - return processInheritorsDelegatingCallToSpecifiedConstructor(klass, scope, descriptor, process) + if (this is KtLightMethod && this.kotlinOrigin == null) return { true } + if (!(this is PsiMethod && isConstructor)) return { true } + val klass = containingClass ?: return { true } + val descriptor = getJavaMethodDescriptor() as? ConstructorDescriptor ?: return { true } + return { processInheritorsDelegatingCallToSpecifiedConstructor(klass, scope, descriptor, process) } } @@ -149,11 +155,13 @@ private fun processInheritorsDelegatingCallToSpecifiedConstructor( process: (KtCallElement) -> Boolean ): Boolean { return HierarchySearchRequest(klass, scope, false).searchInheritors().all { - val unwrapped = it.unwrapped - if (unwrapped is KtClass) { - processClassDelegationCallsToSpecifiedConstructor(unwrapped, descriptor, process) - } else - true + runReadAction { + val unwrapped = it.check { it.isValid }?.unwrapped + if (unwrapped is KtClass) + processClassDelegationCallsToSpecifiedConstructor(unwrapped, descriptor, process) + else + true + } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/findUsages/handlers/KotlinFindClassUsagesHandler.kt b/idea/src/org/jetbrains/kotlin/idea/findUsages/handlers/KotlinFindClassUsagesHandler.kt index 775b9625eac..1054d41ae66 100644 --- a/idea/src/org/jetbrains/kotlin/idea/findUsages/handlers/KotlinFindClassUsagesHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/findUsages/handlers/KotlinFindClassUsagesHandler.kt @@ -22,8 +22,6 @@ import com.intellij.find.findUsages.JavaFindUsagesHelper import com.intellij.openapi.actionSystem.DataContext import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement -import com.intellij.psi.PsiMethod -import com.intellij.psi.PsiReference import com.intellij.psi.search.PsiElementProcessor import com.intellij.psi.search.PsiElementProcessorAdapter import com.intellij.psi.search.searches.ReferencesSearch @@ -44,9 +42,10 @@ import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchPar 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.search.usagesSearch.buildProcessDelegationCallConstructorUsagesTask import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType import org.jetbrains.kotlin.psi.psiUtil.contains import org.jetbrains.kotlin.psi.psiUtil.effectiveDeclarations import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType @@ -71,19 +70,60 @@ class KotlinFindClassUsagesHandler( this) } - override fun searchReferences(element: PsiElement, processor: Processor, options: FindUsagesOptions): Boolean { - val kotlinOptions = options as KotlinClassFindUsagesOptions + override fun createSearcher(element: PsiElement, processor: Processor, options: FindUsagesOptions): Searcher { + return MySearcher(element, processor, options) + } - fun processInheritors(): Boolean { - val request = HierarchySearchRequest(element, options.searchScope, options.isCheckDeepInheritance) - return runReadAction { + private class MySearcher( + element: PsiElement, processor: Processor, options: FindUsagesOptions + ) : Searcher(element, processor, options) { + + private val kotlinOptions = options as KotlinClassFindUsagesOptions + private val referenceProcessor = KotlinFindUsagesHandler.createReferenceProcessor(processor) + + override fun buildTaskList(): Boolean { + val classOrObject = element as KtClassOrObject + + if (kotlinOptions.isUsages || kotlinOptions.searchConstructorUsages) { + processClassReferencesLater(classOrObject) + } + + if (kotlinOptions.isFieldsUsages || kotlinOptions.isMethodsUsages) { + processMemberReferencesLater(classOrObject) + } + + if (kotlinOptions.isUsages && classOrObject is KtObjectDeclaration && classOrObject.isCompanion() && classOrObject in options.searchScope ) { + if (!processCompanionObjectInternalReferences(classOrObject)) return false + } + + if (kotlinOptions.searchConstructorUsages) { + classOrObject.toLightClass()?.constructors?.filterIsInstance()?.forEach { constructor -> + val scope = constructor.useScope.intersectWith(options.searchScope) + val task = constructor.buildProcessDelegationCallConstructorUsagesTask(scope) { + it.calleeExpression?.mainReference?.let { referenceProcessor.process(it) } ?: false + } + addTask(task) + } + } + + if (kotlinOptions.isDerivedClasses || kotlinOptions.isDerivedInterfaces) { + processInheritorsLater() + } + + return true + } + + private fun processInheritorsLater() { + val request = HierarchySearchRequest(element, options.searchScope, kotlinOptions.isCheckDeepInheritance) + addTask { request.searchInheritors().forEach( PsiElementProcessorAdapter( - object : PsiElementProcessor { - override fun execute(element: PsiClass): Boolean { + PsiElementProcessor { element -> + runReadAction { + if (!element.isValid) return@runReadAction false val isInterface = element.isInterface - return when { - isInterface && options.isDerivedInterfaces || !isInterface && options.isDerivedClasses -> + when { + isInterface && kotlinOptions.isDerivedInterfaces || !isInterface && kotlinOptions.isDerivedClasses -> KotlinFindUsagesHandler.processUsage(processor, element.navigationElement) else -> true } @@ -94,101 +134,49 @@ class KotlinFindClassUsagesHandler( } } - val classOrObject = element as KtClassOrObject - val referenceProcessor = KotlinFindUsagesHandler.createReferenceProcessor(processor) + private fun processClassReferencesLater(classOrObject: KtClassOrObject) { + val searchParameters = KotlinReferencesSearchParameters(classOrObject, + scope = options.searchScope, + kotlinOptions = KotlinReferencesSearchOptions(acceptCompanionObjectMembers = true)) + var usagesQuery = ReferencesSearch.search(searchParameters) - if (kotlinOptions.isUsages || kotlinOptions.searchConstructorUsages) { - if (!processClassReferences(classOrObject, kotlinOptions, referenceProcessor)) return false - } - - if (kotlinOptions.isFieldsUsages || kotlinOptions.isMethodsUsages) { - if (!processMemberReferences(classOrObject, kotlinOptions, referenceProcessor)) return false - } - - if (kotlinOptions.isUsages && classOrObject is KtObjectDeclaration && classOrObject.isCompanion() && classOrObject in options.searchScope) { - if (!processCompanionObjectInternalReferences(classOrObject, referenceProcessor)) { - return false + if (kotlinOptions.isSkipImportStatements) { + usagesQuery = FilteredQuery(usagesQuery) { !it.isImportUsage() } } - } - if (kotlinOptions.searchConstructorUsages) { - val result = runReadAction { - val constructors = classOrObject.toLightClass()?.constructors ?: PsiMethod.EMPTY_ARRAY - constructors.filterIsInstance().all { constructor -> - constructor.processDelegationCallConstructorUsages(constructor.useScope.intersectWith(options.searchScope)) { - it.calleeExpression?.mainReference?.let { referenceProcessor.process(it) } ?: false - } - } + if (!kotlinOptions.searchConstructorUsages) { + usagesQuery = FilteredQuery(usagesQuery) { !it.isConstructorUsage(classOrObject) } } - if (!result) return false + else if (!options.isUsages) { + usagesQuery = FilteredQuery(usagesQuery) { it.isConstructorUsage(classOrObject) } + } + addTask { usagesQuery.forEach(referenceProcessor) } } - if (options.isDerivedClasses || options.isDerivedInterfaces) { - if (!processInheritors()) return false - } - - return true - } - - private fun processClassReferences(classOrObject: KtClassOrObject, - options: KotlinClassFindUsagesOptions, - processor: Processor): Boolean { - val searchParameters = KotlinReferencesSearchParameters(classOrObject, - scope = options.searchScope, - kotlinOptions = KotlinReferencesSearchOptions(acceptCompanionObjectMembers = true)) - var usagesQuery = ReferencesSearch.search(searchParameters) - - if (options.isSkipImportStatements) { - usagesQuery = FilteredQuery(usagesQuery) { !it.isImportUsage() } - } - - if (!options.searchConstructorUsages) { - usagesQuery = FilteredQuery(usagesQuery) { !it.isConstructorUsage(classOrObject) } - } - else if (!options.isUsages) { - usagesQuery = FilteredQuery(usagesQuery) { it.isConstructorUsage(classOrObject) } - } - return usagesQuery.forEach(processor) - } - - private fun processCompanionObjectInternalReferences(companionObject: KtObjectDeclaration, - processor: Processor): Boolean { - var stop: Boolean = false - runReadAction { - val klass = companionObject.getStrictParentOfType() ?: return@runReadAction + private fun processCompanionObjectInternalReferences(companionObject: KtObjectDeclaration): Boolean { + val klass = companionObject.getStrictParentOfType() ?: return true val companionObjectDescriptor = companionObject.descriptor - klass.acceptChildren(object : KtVisitorVoid() { - override fun visitKtElement(element: KtElement) { - if (element == companionObject) return // skip companion object itself - if (stop) return - element.acceptChildren(this) + return !klass.anyDescendantOfType(fun (element: KtElement): Boolean { + if (element == companionObject) return false // skip companion object itself - val bindingContext = element.analyze() - val resolvedCall = bindingContext[BindingContext.CALL, element]?.getResolvedCall(bindingContext) ?: return - if ((resolvedCall.dispatchReceiver as? ImplicitClassReceiver)?.declarationDescriptor == companionObjectDescriptor - || (resolvedCall.extensionReceiver as? ImplicitClassReceiver)?.declarationDescriptor == companionObjectDescriptor) { - element.references.forEach { - if (!stop && !processor.process(it)) { - stop = true - } - } - } + val bindingContext = element.analyze() + val resolvedCall = bindingContext[BindingContext.CALL, element]?.getResolvedCall(bindingContext) ?: return false + if ((resolvedCall.dispatchReceiver as? ImplicitClassReceiver)?.declarationDescriptor == companionObjectDescriptor + || (resolvedCall.extensionReceiver as? ImplicitClassReceiver)?.declarationDescriptor == companionObjectDescriptor) { + return element.references.any { !referenceProcessor.process(it) } } + return false }) } - return !stop - } - private fun processMemberReferences(classOrObject: KtClassOrObject, - options: KotlinClassFindUsagesOptions, - processor: Processor): Boolean { - for (decl in classOrObject.effectiveDeclarations()) { - if ((decl is KtNamedFunction && options.isMethodsUsages) || - ((decl is KtProperty || decl is KtParameter) && options.isFieldsUsages)) { - if (!ReferencesSearch.search(decl, options.searchScope).forEach(processor)) return false + private fun processMemberReferencesLater(classOrObject: KtClassOrObject) { + for (declaration in classOrObject.effectiveDeclarations()) { + if ((declaration is KtNamedFunction && kotlinOptions.isMethodsUsages) || + ((declaration is KtProperty || declaration is KtParameter) && kotlinOptions.isFieldsUsages)) { + addTask { ReferencesSearch.search(declaration, options.searchScope).forEach(referenceProcessor) } + } } } - return true } override fun getStringsToSearch(element: PsiElement): Collection { diff --git a/idea/src/org/jetbrains/kotlin/idea/findUsages/handlers/KotlinFindMemberUsagesHandler.kt b/idea/src/org/jetbrains/kotlin/idea/findUsages/handlers/KotlinFindMemberUsagesHandler.kt index d649d00cb8f..ae6d44cbf93 100644 --- a/idea/src/org/jetbrains/kotlin/idea/findUsages/handlers/KotlinFindMemberUsagesHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/findUsages/handlers/KotlinFindMemberUsagesHandler.kt @@ -54,6 +54,7 @@ import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.findOriginalTopMostOverriddenDescriptors import org.jetbrains.kotlin.resolve.source.getPsi +import org.jetbrains.kotlin.utils.addToStdlib.check abstract class KotlinFindMemberUsagesHandler protected constructor(declaration: T, elementsToSearch: Collection, factory: KotlinFindUsagesHandlerFactory) @@ -98,7 +99,7 @@ abstract class KotlinFindMemberUsagesHandler } override fun applyQueryFilters(element: PsiElement, options: FindUsagesOptions, query: Query): Query { - var kotlinOptions = options as KotlinPropertyFindUsagesOptions + val kotlinOptions = options as KotlinPropertyFindUsagesOptions if (!kotlinOptions.isReadAccess && !kotlinOptions.isWriteAccess) { return EmptyQuery() @@ -127,53 +128,67 @@ abstract class KotlinFindMemberUsagesHandler } } - override fun searchReferences(element: PsiElement, processor: Processor, options: FindUsagesOptions): Boolean { - val kotlinOptions = options as KotlinCallableFindUsagesOptions - - val referenceProcessor = KotlinFindUsagesHandler.createReferenceProcessor(processor) - val uniqueProcessor = CommonProcessors.UniqueProcessor(processor) - - if (options.isUsages) { - val kotlinSearchOptions = createKotlinReferencesSearchOptions(options) - val searchParameters = KotlinReferencesSearchParameters(element, options.searchScope, kotlinOptions = kotlinSearchOptions) - - with(applyQueryFilters(element, options, ReferencesSearch.search(searchParameters))) { - if (!forEach(referenceProcessor)) return false - } - - for (psiMethod in runReadAction { element.toLightMethods() }) { - var searchScope = options.searchScope - // TODO: very bad code!! ReferencesSearch does not work correctly for constructors and annotation parameters - if (element is KtNamedFunction || (element is KtParameter && element.dataClassComponentFunction() != null)) { - searchScope = searchScope.excludeKotlinSources() - } - with(applyQueryFilters(element, options, MethodReferencesSearch.search(psiMethod, searchScope, true))) { - if (!forEach(referenceProcessor)) return false - } - } - } - - if (kotlinOptions.searchOverrides) { - for (method in HierarchySearchRequest(element, options.searchScope, true).searchOverriders()) { - if (!KotlinFindUsagesHandler.processUsage(uniqueProcessor, method.navigationElement)) break - } - } - - return true + override fun createSearcher(element: PsiElement, processor: Processor, options: FindUsagesOptions): Searcher { + return MySearcher(element, processor, options) } - private fun SearchScope.excludeKotlinSources(): SearchScope { - if (this is GlobalSearchScope) { - val fileTypes = FileTypeManager.getInstance().registeredFileTypes.filter { it != KotlinFileType.INSTANCE }.toTypedArray() - return GlobalSearchScope.getScopeRestrictedByFileTypes(this, *fileTypes) + private inner class MySearcher( + element: PsiElement, processor: Processor, options: FindUsagesOptions + ) : Searcher(element, processor, options) { + + private val kotlinOptions = options as KotlinCallableFindUsagesOptions + + override fun buildTaskList(): Boolean { + val referenceProcessor = KotlinFindUsagesHandler.createReferenceProcessor(processor) + val uniqueProcessor = CommonProcessors.UniqueProcessor(processor) + + if (options.isUsages) { + val kotlinSearchOptions = createKotlinReferencesSearchOptions(options) + val searchParameters = KotlinReferencesSearchParameters(element, options.searchScope, kotlinOptions = kotlinSearchOptions) + + applyQueryFilters(element, options, ReferencesSearch.search(searchParameters)).let { query -> + addTask { query.forEach(referenceProcessor) } + } + + + for (psiMethod in element.toLightMethods()) { + var searchScope = options.searchScope + // TODO: very bad code!! ReferencesSearch does not work correctly for constructors and annotation parameters + if (element is KtNamedFunction || (element is KtParameter && element.dataClassComponentFunction() != null)) { + searchScope = searchScope.excludeKotlinSources() + } + applyQueryFilters(element, options, MethodReferencesSearch.search(psiMethod, searchScope, true)).let { query -> + addTask { query.forEach(referenceProcessor) } + } + } + } + + if (kotlinOptions.searchOverrides) { + addTask { + val overriders = HierarchySearchRequest(element, options.searchScope, true).searchOverriders() + overriders.all { + val element = runReadAction { it.check { it.isValid }?.navigationElement } ?: return@all true + KotlinFindUsagesHandler.processUsage(uniqueProcessor, element) + } + } + } + + return true } - else { - this as LocalSearchScope - val filteredElements = scope.filter { it.containingFile !is KtFile } - return if (filteredElements.isNotEmpty()) - LocalSearchScope(filteredElements.toTypedArray()) - else - GlobalSearchScope.EMPTY_SCOPE + + private fun SearchScope.excludeKotlinSources(): SearchScope { + if (this is GlobalSearchScope) { + val fileTypes = FileTypeManager.getInstance().registeredFileTypes.filter { it != KotlinFileType.INSTANCE }.toTypedArray() + return GlobalSearchScope.getScopeRestrictedByFileTypes(this, *fileTypes) + } + else { + this as LocalSearchScope + val filteredElements = scope.filter { it.containingFile !is KtFile } + return if (filteredElements.isNotEmpty()) + LocalSearchScope(filteredElements.toTypedArray()) + else + GlobalSearchScope.EMPTY_SCOPE + } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/findUsages/handlers/KotlinFindUsagesHandler.kt b/idea/src/org/jetbrains/kotlin/idea/findUsages/handlers/KotlinFindUsagesHandler.kt index 848c9de0f64..487df9df143 100644 --- a/idea/src/org/jetbrains/kotlin/idea/findUsages/handlers/KotlinFindUsagesHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/findUsages/handlers/KotlinFindUsagesHandler.kt @@ -68,7 +68,13 @@ abstract class KotlinFindUsagesHandler(psiElement: T, return searchReferences(element, processor, options) && searchTextOccurrences(element, processor, options) } - protected abstract fun searchReferences(element: PsiElement, processor: Processor, options: FindUsagesOptions): Boolean + protected fun searchReferences(element: PsiElement, processor: Processor, options: FindUsagesOptions): Boolean { + val searcher = createSearcher(element, processor, options) + if (!runReadAction { searcher.buildTaskList() }) return false + return searcher.executeTasks() + } + + protected abstract fun createSearcher(element: PsiElement, processor: Processor, options: FindUsagesOptions): Searcher override fun findReferencesToHighlight(target: PsiElement, searchScope: SearchScope): Collection { val results = Collections.synchronizedList(arrayListOf()) @@ -86,6 +92,20 @@ abstract class KotlinFindUsagesHandler(psiElement: T, return results } + protected abstract class Searcher(val element: PsiElement, val processor: Processor, val options: FindUsagesOptions) { + private val tasks = ArrayList<() -> Boolean>() + + protected fun addTask(task: () -> Boolean) { + tasks.add(task) + } + + fun executeTasks(): Boolean { + return tasks.all { it() } + } + + abstract fun buildTaskList(): Boolean + } + companion object { val LOG = Logger.getInstance(KotlinFindUsagesHandler::class.java) diff --git a/idea/src/org/jetbrains/kotlin/idea/findUsages/handlers/KotlinTypeParameterFindUsagesHandler.kt b/idea/src/org/jetbrains/kotlin/idea/findUsages/handlers/KotlinTypeParameterFindUsagesHandler.kt index 21d8f773ec4..57bb677407f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/findUsages/handlers/KotlinTypeParameterFindUsagesHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/findUsages/handlers/KotlinTypeParameterFindUsagesHandler.kt @@ -39,8 +39,15 @@ class KotlinTypeParameterFindUsagesHandler( ) } - override fun searchReferences(element: PsiElement, processor: Processor, options: FindUsagesOptions): Boolean { - return ReferencesSearch.search(element, options.searchScope).all { KotlinFindUsagesHandler.processUsage(processor, it ) } + override fun createSearcher(element: PsiElement, processor: Processor, options: FindUsagesOptions): Searcher { + return object: Searcher(element, processor, options) { + override fun buildTaskList(): Boolean { + addTask { + ReferencesSearch.search(element, options.searchScope).all { KotlinFindUsagesHandler.processUsage(processor, it ) } + } + return true + } + } } override fun getFindUsagesOptions(dataContext: DataContext?): FindUsagesOptions { diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureUsageProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureUsageProcessor.kt index a85a9f3c050..5bf1708f167 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureUsageProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureUsageProcessor.kt @@ -52,7 +52,7 @@ import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchParameters import org.jetbrains.kotlin.idea.search.restrictToKotlinSources -import org.jetbrains.kotlin.idea.search.usagesSearch.processDelegationCallConstructorUsages +import org.jetbrains.kotlin.idea.search.usagesSearch.buildProcessDelegationCallConstructorUsagesTask import org.jetbrains.kotlin.idea.util.* import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.kdoc.psi.impl.KDocName @@ -311,13 +311,13 @@ class KotlinChangeSignatureUsageProcessor : ChangeSignatureUsageProcessor { } } - functionPsi.processDelegationCallConstructorUsages(functionPsi.useScope) { + functionPsi.buildProcessDelegationCallConstructorUsagesTask(functionPsi.useScope) { when (it) { is KtConstructorDelegationCall -> result.add(KotlinConstructorDelegationCallUsage(it, changeInfo)) is KtSuperTypeCallEntry -> result.add(KotlinFunctionCallUsage(it, functionUsageInfo)) } true - } + }.invoke() } private fun processInternalReferences(functionUsageInfo: KotlinCallableDefinitionUsage<*>, visitor: KtTreeVisitor) { diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinSafeDeleteProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinSafeDeleteProcessor.kt index 42d5eb56059..1cf05775003 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinSafeDeleteProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinSafeDeleteProcessor.kt @@ -43,7 +43,7 @@ import org.jetbrains.kotlin.idea.refactoring.checkSuperMethods import org.jetbrains.kotlin.idea.refactoring.formatClass import org.jetbrains.kotlin.idea.refactoring.formatFunction import org.jetbrains.kotlin.idea.references.KtReference -import org.jetbrains.kotlin.idea.search.usagesSearch.processDelegationCallConstructorUsages +import org.jetbrains.kotlin.idea.search.usagesSearch.buildProcessDelegationCallConstructorUsagesTask import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType @@ -179,12 +179,12 @@ class KotlinSafeDeleteProcessor : JavaSafeDeleteProcessor() { else -> return } for (constructor in constructors) { - constructor.processDelegationCallConstructorUsages(constructor.useScope) { + constructor.buildProcessDelegationCallConstructorUsagesTask(constructor.useScope) { if (!getIgnoranceCondition().value(it)) { usages.add(SafeDeleteReferenceSimpleDeleteUsageInfo(it, element, false)) } true - } + }.invoke() } } diff --git a/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinConstructorDelegationCallReferenceSearcher.kt b/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinConstructorDelegationCallReferenceSearcher.kt index fc17b9367a9..c5b16e5a498 100644 --- a/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinConstructorDelegationCallReferenceSearcher.kt +++ b/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinConstructorDelegationCallReferenceSearcher.kt @@ -20,15 +20,15 @@ import com.intellij.openapi.application.QueryExecutorBase import com.intellij.psi.PsiReference import com.intellij.psi.search.searches.MethodReferencesSearch.SearchParameters import com.intellij.util.Processor -import org.jetbrains.kotlin.idea.search.usagesSearch.processDelegationCallConstructorUsages +import org.jetbrains.kotlin.idea.search.usagesSearch.buildProcessDelegationCallConstructorUsagesTask class KotlinConstructorDelegationCallReferenceSearcher() : QueryExecutorBase(true) { override fun processQuery(queryParameters: SearchParameters, consumer: Processor) { val method = queryParameters.method if (!method.isConstructor) return - method.processDelegationCallConstructorUsages(method.useScope.intersectWith(queryParameters.effectiveSearchScope)) { + method.buildProcessDelegationCallConstructorUsagesTask(method.useScope.intersectWith(queryParameters.effectiveSearchScope)) { it.calleeExpression?.reference?.let { consumer.process(it) } ?: true - } + }.invoke() } } \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/findUsages/AbstractFindUsagesTest.kt b/idea/tests/org/jetbrains/kotlin/findUsages/AbstractFindUsagesTest.kt index eccfa8884ee..05ebc08773e 100644 --- a/idea/tests/org/jetbrains/kotlin/findUsages/AbstractFindUsagesTest.kt +++ b/idea/tests/org/jetbrains/kotlin/findUsages/AbstractFindUsagesTest.kt @@ -388,8 +388,6 @@ abstract class AbstractFindUsagesTest : KotlinLightCodeInsightFixtureTestCase() options: FindUsagesOptions?, highlightingMode: Boolean ): Collection { - @Suppress("NAME_SHADOWING") - var options = options val project = project val handler: FindUsagesHandler = (if (targetElement is PsiMember) { @@ -402,21 +400,26 @@ abstract class AbstractFindUsagesTest : KotlinLightCodeInsightFixtureTestCase() (FindManager.getInstance(project) as FindManagerImpl).findUsagesManager.getFindUsagesHandler(targetElement, false) }) ?: error("Cannot find handler for: $targetElement") - if (options == null) { - options = handler.getFindUsagesOptions(null) - } + @Suppress("NAME_SHADOWING") + val options = options ?: handler.getFindUsagesOptions(null) options.searchScope = GlobalSearchScope.allScope(project) val processor = CommonProcessors.CollectProcessor() for (psiElement in handler.primaryElements + handler.secondaryElements) { if (highlightingMode) { + //TODO: should findReferencesToHighlight work outside read-action or it makes no sense? for (reference in handler.findReferencesToHighlight(psiElement, options.searchScope)) { processor.process(UsageInfo(reference)) } } else { - handler.processElementUsages(psiElement, processor, options) + // run in another thread to test read-action assertions + val thread = Thread { + handler.processElementUsages(psiElement, processor, options) + } + thread.start() + thread.join() } }