Refactored reference searches to take read-actions when necessary

This commit is contained in:
Valentin Kipyatkov
2016-09-04 14:17:43 +03:00
parent 637da2e693
commit 4e0c3fcee7
13 changed files with 509 additions and 420 deletions
@@ -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<PsiClass> {
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,
@@ -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<PsiReference, ReferencesSearch.SearchParameters>() {
override fun processQuery(queryParameters: ReferencesSearch.SearchParameters, consumer: Processor<PsiReference>) {
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<PsiReference>) {
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<KtClassOrObject>()?.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<KtClassOrObject>()?.toLightClass()
searchDataClassComponentUsages(containingClass, componentFunctionDescriptor, kotlinOptions)
}
}
val kotlinReferencesSearchOptions = (queryParameters as? KotlinReferencesSearchParameters)?.kotlinOptions
if (kotlinReferencesSearchOptions?.acceptCompanionObjectMembers == true) {
runReadAction {
val originClass = element.getStrictParentOfType<KtClass>()
val originLightClass = originClass?.toLightClass()
if (originLightClass != null) {
val lightDeclarations: List<KtLightElement<*, *>?> =
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<PsiMethod> {
val originObject = declaration.parents
.dropWhile { it is KtClassBody }
.firstOrNull() as? KtObjectDeclaration ?: return emptyList()
if (originObject.isCompanion()) {
val originClass = originObject.getStrictParentOfType<KtClass>()
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<PsiReference>,
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<PsiReference>) {
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<KtProperty>()
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<KtClass>()?.toLightClass()
if (originLightClass != null) {
val lightDeclarations: List<KtLightElement<*, *>?> =
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<PsiMethod> {
val originObject = declaration.parents
.dropWhile { it is KtClassBody }
.firstOrNull() as? KtObjectDeclaration ?: return emptyList()
if (!originObject.isCompanion()) return emptyList()
val originClass = originObject.getStrictParentOfType<KtClass>()
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
}
}
}
@@ -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<String>? = 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<KtFile, ArrayList<PsiElement>>()
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
}
}
})
}
}
@@ -79,6 +79,21 @@ abstract class OperatorReferenceSearcher<TReferenceElement : KtElement>(
consumer: Processor<PsiReference>,
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<PsiReference>,
optimizer: SearchRequestCollector,
options: KotlinReferencesSearchOptions
): OperatorReferenceSearcher<*>? {
val functionName = when (declaration) {
is KtNamedFunction -> declaration.name
@@ -212,16 +227,20 @@ abstract class OperatorReferenceSearcher<TReferenceElement : KtElement>(
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<TReferenceElement : KtElement>(
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))
}
@@ -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
}
}
}