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 new file mode 100644 index 00000000000..8f4930e6e65 --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/ExpressionsOfTypeProcessor.kt @@ -0,0 +1,651 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.search.usagesSearch + +import com.intellij.ide.highlighter.JavaFileType +import com.intellij.lang.java.JavaLanguage +import com.intellij.lang.xml.XMLLanguage +import com.intellij.openapi.application.ApplicationManager +import com.intellij.psi.* +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.search.LocalSearchScope +import com.intellij.psi.search.SearchScope +import com.intellij.psi.search.searches.ReferencesSearch +import com.intellij.util.Processor +import org.jetbrains.kotlin.KtNodeTypes +import org.jetbrains.kotlin.asJava.classes.KtLightClass +import org.jetbrains.kotlin.asJava.elements.KtLightMethod +import org.jetbrains.kotlin.asJava.toLightClass +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.diagnostics.DiagnosticUtils +import org.jetbrains.kotlin.idea.KotlinLanguage +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny +import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde +import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName +import org.jetbrains.kotlin.idea.references.KtDestructuringDeclarationReference +import org.jetbrains.kotlin.idea.resolve.ResolutionFacade +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.util.FuzzyType +import org.jetbrains.kotlin.idea.util.ProjectRootsUtil +import org.jetbrains.kotlin.kdoc.psi.impl.KDocName +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.load.java.sam.SingleAbstractMethodUtils +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.* +import org.jetbrains.kotlin.types.KotlinType +import java.util.* + +//TODO: check if smart search is too expensive + +class ExpressionsOfTypeProcessor( + private val typeToSearch: FuzzyType, + private val searchScope: SearchScope, + private val suspiciousExpressionHandler: (KtExpression) -> Unit, + suspiciousScopeHandler: (SearchScope) -> Unit, + private val resolutionFacade: ResolutionFacade +) { + enum class Mode { + ALWAYS_SMART, + ALWAYS_PLAIN, + PLAIN_WHEN_NEEDED // use plain search for LocalSearchScope and when unknown type of reference encountered + } + + companion object { + // for tests + var mode = if (ApplicationManager.getApplication().isUnitTestMode) Mode.ALWAYS_SMART else Mode.PLAIN_WHEN_NEEDED + var testLog: MutableList? = null + + fun logPresentation(declaration: PsiElement): String? { + val fqName = declaration.getKotlinFqName()?.asString() + ?: (declaration as? KtNamedDeclaration)?.name + return when (declaration) { + is PsiMethod, is KtFunction -> fqName + "()" + is KtParameter -> { + val owner = declaration.ownerFunction?.let { logPresentation(it) } ?: declaration.parent.toString() + "parameter ${declaration.name} in $owner" + } + is KtDestructuringDeclaration -> declaration.entries.joinToString(", ", prefix = "(", postfix = ")") { it.text } + else -> fqName + } + } + } + + private val project = resolutionFacade.project + + private val plainSearchHandler: (SearchScope) -> Unit = { scope -> + testLog?.add("Used plain search in ${scope.logPresentation()}") + suspiciousScopeHandler(scope) + } + + // note: a Task must define equals & hashCode! + private interface Task { + fun perform() + } + + private val tasks = ArrayDeque() + private val taskSet = HashSet() + + private val scopesToUsePlainSearch = LinkedHashMap>() + + fun run() { + 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 + } + + // for class from library always use plain search because we cannot search usages in compiled code (we could though) + if (!ProjectRootsUtil.isInProjectSource(psiClass)) { + plainSearchHandler(searchScope) + return + } + + addClassToProcess(psiClass) + + processTasks() + + val scopeElements = scopesToUsePlainSearch.values.flatMap { it }.toTypedArray() + plainSearchHandler(LocalSearchScope(scopeElements)) + } + + private fun addTask(task: Task) { + if (taskSet.add(task)) { + tasks.push(task) + } + + } + + private fun processTasks() { + while (tasks.isNotEmpty()) { + tasks.pop().perform() + } + } + + private fun downShiftToPlainSearch() { + tasks.clear() + scopesToUsePlainSearch.clear() + plainSearchHandler(searchScope) + } + + private fun addClassToProcess(classToSearch: PsiClass) { + 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 + + if (mode != Mode.ALWAYS_SMART) { + downShiftToPlainSearch() + return@processor 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) } + } + } + addTask(ProcessClassUsagesTask(classToSearch)) + } + + private enum class CallableToProcessKind { + HAS_OUR_CLASS_TYPE, + PROCESS_LAMBDAS + } + + /** + * Adds declaration whose type is our class (or our class used anywhere inside that type) + * or which has parameter of functional type with our class used inside + */ + private fun addCallableDeclarationToProcess(declaration: PsiElement, kind: CallableToProcessKind) { + if (declaration.isOperatorExpensiveToSearch()) { // cancel all tasks and use plain search + downShiftToPlainSearch() + return + } + + data class ProcessCallableUsagesTask(val declaration: PsiElement, val kind: CallableToProcessKind) : Task { + override fun perform() { + // we don't need to search usages of declarations in Java because Java doesn't have implicitly typed declarations so such usages cannot affect Kotlin code + //TODO: what about Scala and other JVM-languages? + val scope = GlobalSearchScope.projectScope(project).restrictToKotlinSources() + testLog?.add("Searched references to ${logPresentation(declaration)} in Kotlin files") + val searchParameters = KotlinReferencesSearchParameters( + declaration, scope, kotlinOptions = KotlinReferencesSearchOptions(searchNamedArguments = false)) + ReferencesSearch.search(searchParameters).forEach { reference -> + when (kind) { + CallableToProcessKind.HAS_OUR_CLASS_TYPE -> { + if (reference is KtDestructuringDeclarationReference) { + // declaration usage in form of destructuring declaration entry + addCallableDeclarationToProcess(reference.element, CallableToProcessKind.HAS_OUR_CLASS_TYPE) + } + else { + (reference.element as? KtReferenceExpression)?.let { processSuspiciousExpression(it) } + } + } + + CallableToProcessKind.PROCESS_LAMBDAS -> { + (reference.element as? KtReferenceExpression)?.let { processLambdasForCallableReference(it) } + } + } + } + } + } + addTask(ProcessCallableUsagesTask(declaration, kind)) + } + + private fun addSamInterfaceToProcess(psiClass: PsiClass) { + data class ProcessSamInterfaceTask(val psiClass: PsiClass) : Task { + override fun perform() { + //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 -> + // 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) + } + } + } + } + addTask(ProcessSamInterfaceTask(psiClass)) + } + + /** + * Process usage of our class or one of its inheritors + */ + private fun processClassUsage(reference: PsiReference): Boolean { + val element = reference.element + return when (element.language) { + KotlinLanguage.INSTANCE -> processClassUsageInKotlin(element) + + JavaLanguage.INSTANCE -> processClassUsageInJava(element) + + XMLLanguage.INSTANCE -> true // ignore usages in XML - they don't affect us + + else -> false // we don't know anything about usages in other languages - so we downgrade to slow algorithm in this case + } + } + + private fun processClassUsageInKotlin(element: PsiElement): Boolean { + //TODO: type aliases + + when (element) { + is KtReferenceExpression -> { + val parent = element.parent + when (parent) { + is KtUserType -> { + return processClassUsageInUserType(parent) + } + + is KtCallExpression -> { + if (element == parent.calleeExpression) { + processSuspiciousExpression(parent) + return true + } + } + + is KtContainerNode -> { + if (parent.node.elementType == KtNodeTypes.LABEL_QUALIFIER) { + return true // this@ClassName - it will be handled anyway because members and extensions are processed with plain search + } + } + + is KtQualifiedExpression -> { + if (element == parent.receiverExpression) { + return true // companion object member or static member access - ignore it + } + } + + is KtCallableReferenceExpression -> { + when (element) { + parent.receiverExpression -> { // usage in receiver of callable reference (before "::") - ignore it + return true + } + + parent.callableReference -> { // usage after "::" in callable reference - should be reference to constructor of our class + processSuspiciousExpression(element) + return true + } + } + } + + is KtClassLiteralExpression -> { + if (element == parent.receiverExpression) { // ClassName::class + processSuspiciousExpression(element) + return true + } + } + } + + if (element.getStrictParentOfType() != null) return true // ignore usage in import + } + + is KDocName -> return true // ignore usage in doc-comment + } + + return false // unsupported type of reference + } + + private fun processClassUsageInUserType(userType: KtUserType): Boolean { + val typeRef = userType.parents.lastOrNull { it is KtTypeReference } + val typeRefParent = typeRef?.parent + when (typeRefParent) { + is KtCallableDeclaration -> { + when (typeRef) { + typeRefParent.typeReference -> { + addCallableDeclarationToProcess(typeRefParent, CallableToProcessKind.HAS_OUR_CLASS_TYPE) + + if (typeRefParent is KtParameter) { //TODO: what if functional type is declared with "FunctionN<...>"? + val usedInsideFunctionalType = userType.parents.takeWhile { it != typeRef }.any { it is KtFunctionType } + if (usedInsideFunctionalType) { + val function = (typeRefParent.parent as? KtParameterList)?.parent as? KtFunction + if (function != null) { + addCallableDeclarationToProcess(function, CallableToProcessKind.PROCESS_LAMBDAS) + } + } + } + + return true + } + + typeRefParent.receiverTypeReference -> { + // we must use plain search inside extensions because implicit 'this' can happen anywhere + usePlainSearch(typeRefParent) + return true + } + } + } + + is KtTypeProjection -> { + val callExpression = (typeRefParent.parent as? KtTypeArgumentList)?.parent as? KtCallExpression + if (callExpression != null) { + processSuspiciousExpression(callExpression) + return true + } + } + + is KtConstructorCalleeExpression -> { + val parent = typeRefParent.parent + if (parent is KtSuperTypeCallEntry) { + val classOrObject = (parent.parent as KtSuperTypeList).parent as KtClassOrObject + val psiClass = classOrObject.toLightClass() + psiClass?.let { addClassToProcess(it) } + return true + } + } + + is KtSuperTypeListEntry -> { + if (typeRef == typeRefParent.typeReference) { + val classOrObject = (typeRefParent.parent as KtSuperTypeList).parent as KtClassOrObject + val psiClass = classOrObject.toLightClass() + psiClass?.let { addClassToProcess(it) } + return true + } + } + + is KtIsExpression -> { + val scopeOfPossibleSmartCast = typeRefParent.getParentOfType(true) + scopeOfPossibleSmartCast?.let { usePlainSearch(it) } + return true + } + + is KtWhenConditionIsPattern -> { + val whenEntry = typeRefParent.parent as KtWhenEntry + if (typeRefParent.isNegated) { + val whenExpression = whenEntry.parent as KtWhenExpression + val entriesAfter = whenExpression.entries.dropWhile { it != whenEntry }.drop(1) + entriesAfter.forEach { usePlainSearch(it) } + } + else { + usePlainSearch(whenEntry) + } + return true + } + + is KtBinaryExpressionWithTypeRHS -> { + processSuspiciousExpression(typeRefParent) + return true + } + } + + return false // unsupported case + } + + private fun processClassUsageInJava(element: PsiElement): Boolean { + if (element !is PsiJavaCodeReferenceElement) return true // meaningless reference from Java + + var prev = element + ParentsLoop@ + for (parent in element.parents) { + when (parent) { + is PsiCodeBlock, + is PsiExpression -> + break@ParentsLoop // ignore local usages + + is PsiMethod -> { + if (prev == parent.returnTypeElement && !parent.isPrivateOrLocal()) { + addCallableDeclarationToProcess(parent, CallableToProcessKind.HAS_OUR_CLASS_TYPE) + } + break@ParentsLoop + } + + is PsiField -> { + if (prev == parent.typeElement && !parent.isPrivateOrLocal()) { + addCallableDeclarationToProcess(parent, CallableToProcessKind.HAS_OUR_CLASS_TYPE) + } + break@ParentsLoop + } + + is PsiReferenceList -> { + if (parent.role == PsiReferenceList.Role.EXTENDS_LIST || parent.role == PsiReferenceList.Role.IMPLEMENTS_LIST) { + val psiClass = parent.parent as PsiClass + if (!psiClass.isPrivateOrLocal()) { + addClassToProcess(psiClass) + } + } + break@ParentsLoop + } + + //TODO: if Java parameter has Kotlin functional type then we should process method usages + is PsiParameter -> { + if (prev == parent.typeElement) { + val method = parent.declarationScope as? PsiMethod + if (method != null && method.hasModifierProperty(PsiModifier.ABSTRACT)) { + val psiClass = method.containingClass + if (psiClass != null) { + testLog?.add("Resolved java class to descriptor: ${psiClass.qualifiedName}") + + val classDescriptor = psiClass.resolveToDescriptor(resolutionFacade) + if (classDescriptor != null && SingleAbstractMethodUtils.getSingleAbstractMethodOrNull(classDescriptor) != null) { + addSamInterfaceToProcess(psiClass) + } + } + } + } + break@ParentsLoop + } + } + + prev = parent + } + + return true + } + + /** + * Process expression which may have type of our class (or our class used anywhere inside that type) + */ + private fun processSuspiciousExpression(expression: KtExpression) { + var inScope = expression in searchScope + var affectedScope: PsiElement = expression + ParentsLoop@ + for (element in expression.parentsWithSelf) { + affectedScope = element + if (element !is KtExpression) continue + + if (searchScope is LocalSearchScope) { // optimization to not check every expression + inScope = inScope && element in searchScope + } + if (inScope) { + suspiciousExpressionHandler(element) + } + + val parent = element.parent + when (parent) { + is KtDestructuringDeclaration -> { + processSuspiciousDeclaration(parent) + break@ParentsLoop + } + + is KtWithExpressionInitializer -> { + if (element == parent.initializer) { + processSuspiciousDeclaration(parent) + } + break@ParentsLoop + } + + is KtContainerNode -> { + if (parent.node.elementType == KtNodeTypes.LOOP_RANGE) { + val forExpression = parent.parent as KtForExpression + (forExpression.destructuringParameter ?: forExpression.loopParameter as KtDeclaration?)?.let { + processSuspiciousDeclaration(it) + } + break@ParentsLoop + } + } + } + + if (!element.mayTypeAffectAncestors()) break + } + + // use plain search in all lambdas and anonymous functions inside because they parameters or receiver can be implicitly typed with our class + usePlainSearchInLambdas(affectedScope) + } + + private fun processLambdasForCallableReference(expression: KtReferenceExpression) { + //TODO: receiver? + usePlainSearchInLambdas(expression.parent) + } + + /** + * Process declaration which may have implicit type of our class (or our class used anywhere inside that type) + */ + private fun processSuspiciousDeclaration(declaration: KtDeclaration) { + if (declaration is KtDestructuringDeclaration) { + //TODO: process entries! + } + else { + if (!isImplicitlyTyped(declaration)) return + + testLog?.add("Checked type of ${logPresentation(declaration)}") + + val descriptor = declaration.resolveToDescriptorIfAny() as? CallableDescriptor ?: return + val type = descriptor.returnType + if (type != null && type.containsTypeOrDerivedInside(typeToSearch)) { + addCallableDeclarationToProcess(declaration, CallableToProcessKind.HAS_OUR_CLASS_TYPE) + } + } + } + + private fun usePlainSearchInLambdas(scope: PsiElement) { + scope.forEachDescendantOfType { + if (it.nameIdentifier == null) { + usePlainSearch(it) + } + } + } + + private fun usePlainSearch(scope: KtElement) { + val file = scope.getContainingKtFile() + val restricted = LocalSearchScope(scope).intersectWith(searchScope) as 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 + } + } + prevElements.add(element) + } + } + + //TODO: code is quite similar to PartialBodyResolveFilter.isValueNeeded + private fun KtExpression.mayTypeAffectAncestors(): Boolean { + val parent = this.parent + when (parent) { + is KtBlockExpression -> { + return this == parent.statements.last() && parent.mayTypeAffectAncestors() + } + + is KtDeclarationWithBody -> { + if (this == parent.bodyExpression) { + return !parent.hasBlockBody() && !parent.hasDeclaredReturnType() + } + } + + is KtContainerNode -> { + val grandParent = parent.parent + return when (parent.node.elementType) { + KtNodeTypes.CONDITION, KtNodeTypes.BODY -> false + KtNodeTypes.THEN, KtNodeTypes.ELSE -> (grandParent as KtExpression).mayTypeAffectAncestors() + KtNodeTypes.LOOP_RANGE, KtNodeTypes.INDICES -> true + else -> true // something else unknown + } + } + } + return true // we don't know + } + + private fun PsiModifierListOwner.isPrivateOrLocal(): Boolean { + return hasModifierProperty(PsiModifier.PRIVATE) || parents.any { it is PsiCodeBlock } + } + + private fun PsiElement.isOperatorExpensiveToSearch(): Boolean { + when (this) { + is KtFunction -> { + if (name?.startsWith("component") == true) return false // component functions are not so expensive to search + return hasModifier(KtTokens.OPERATOR_KEYWORD) + || hasModifier(KtTokens.OVERRIDE_KEYWORD) && (resolveToDescriptorIfAny() as? FunctionDescriptor)?.isOperator == true + } + + is KtLightMethod -> { + return kotlinOrigin?.isOperatorExpensiveToSearch() == true + } + + else -> { + return false + } + } + } + + private fun KotlinType.containsTypeOrDerivedInside(type: FuzzyType): Boolean { + return type.checkIsSuperTypeOf(this) != null || arguments.any { it.type.containsTypeOrDerivedInside(type) } + } + + private fun isImplicitlyTyped(declaration: KtDeclaration): Boolean { + return when (declaration) { + is KtFunction -> !declaration.hasDeclaredReturnType() + is KtVariableDeclaration -> declaration.typeReference == null + is KtParameter -> declaration.typeReference == null + else -> false + } + } + + private fun SearchScope.logPresentation(): String { + return when (this) { + searchScope -> "whole search scope" + + is LocalSearchScope -> { + scope + .map { element -> + " " + when (element) { + is KtFunctionLiteral -> element.text + is KtWhenEntry -> { + if (element.isElse) + "KtWhenEntry \"else\"" + else + "KtWhenEntry \"" + element.conditions.joinToString(", ") { it.text } + "\"" + } + is KtNamedDeclaration -> element.node.elementType.toString() + ":" + element.name + else -> element.toString() + } + } + .toList() + .sorted() + .joinToString("\n", "LocalSearchScope:\n") + } + + else -> this.displayName + } + + } +} diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/destructuringDeclarationUsages.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/destructuringDeclarationUsages.kt index d7888118cb6..cce1170db90 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/destructuringDeclarationUsages.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/destructuringDeclarationUsages.kt @@ -16,67 +16,39 @@ package org.jetbrains.kotlin.idea.search.usagesSearch -import com.intellij.ide.highlighter.JavaFileType -import com.intellij.lang.java.JavaLanguage -import com.intellij.lang.xml.XMLLanguage -import com.intellij.openapi.application.ApplicationManager -import com.intellij.psi.* -import com.intellij.psi.search.* -import com.intellij.psi.search.searches.ReferencesSearch +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiReference +import com.intellij.psi.search.LocalSearchScope +import com.intellij.psi.search.SearchRequestCollector +import com.intellij.psi.search.SearchScope +import com.intellij.psi.search.UsageSearchContext import com.intellij.util.Processor import org.jetbrains.kotlin.KtNodeTypes -import org.jetbrains.kotlin.asJava.classes.KtLightClass import org.jetbrains.kotlin.asJava.elements.KtLightMethod import org.jetbrains.kotlin.asJava.namedUnwrappedElement -import org.jetbrains.kotlin.asJava.toLightClass import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor -import org.jetbrains.kotlin.diagnostics.DiagnosticUtils -import org.jetbrains.kotlin.idea.KotlinLanguage import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor -import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny -import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde -import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName import org.jetbrains.kotlin.idea.references.KtDestructuringDeclarationReference -import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions -import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchParameters import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinRequestResultProcessor import org.jetbrains.kotlin.idea.search.restrictToKotlinSources -import org.jetbrains.kotlin.idea.search.usagesSearch.DestructuringDeclarationUsageSearch.* -import org.jetbrains.kotlin.idea.util.FuzzyType -import org.jetbrains.kotlin.idea.util.ProjectRootsUtil +import org.jetbrains.kotlin.idea.search.usagesSearch.ExpressionsOfTypeProcessor.Companion.logPresentation +import org.jetbrains.kotlin.idea.search.usagesSearch.ExpressionsOfTypeProcessor.Companion.testLog import org.jetbrains.kotlin.idea.util.fuzzyExtensionReceiverType import org.jetbrains.kotlin.idea.util.toFuzzyType -import org.jetbrains.kotlin.kdoc.psi.impl.KDocName -import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.load.java.sam.SingleAbstractMethodUtils import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.resolve.dataClassUtils.getComponentIndex import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension -import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.util.isValidOperator import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance import java.util.* -enum class DestructuringDeclarationUsageSearch { - ALWAYS_SMART, - ALWAYS_PLAIN, - PLAIN_WHEN_NEEDED // use plain search for LocalSearchScope and when unknown type of reference encountered -} - -// for tests -var destructuringDeclarationUsageSearchMode = if (ApplicationManager.getApplication().isUnitTestMode) ALWAYS_SMART else PLAIN_WHEN_NEEDED -var destructuringDeclarationUsageSearchLog: MutableList? = null - private object DestructuringDeclarationSearchesInProgress : ThreadLocal>() { override fun initialValue() = HashSet() } -//TODO: check if it's too expensive - fun findDestructuringDeclarationUsages( componentFunction: PsiMethod, scope: SearchScope, @@ -89,26 +61,26 @@ fun findDestructuringDeclarationUsages( } fun findDestructuringDeclarationUsages( - ktDeclaration: KtDeclaration, - scope: SearchScope, + targetDeclaration: KtDeclaration, + searchScope: SearchScope, consumer: Processor, optimizer: SearchRequestCollector ) { val inProgress = DestructuringDeclarationSearchesInProgress.get() try { - if (!inProgress.add(ktDeclaration)) return + if (!inProgress.add(targetDeclaration)) return - val usePlainSearch = when (destructuringDeclarationUsageSearchMode) { - ALWAYS_SMART -> false - ALWAYS_PLAIN -> true - PLAIN_WHEN_NEEDED -> scope is LocalSearchScope // for local scope it's faster to use plain search + val usePlainSearch = when (ExpressionsOfTypeProcessor.mode) { + ExpressionsOfTypeProcessor.Mode.ALWAYS_SMART -> false + ExpressionsOfTypeProcessor.Mode.ALWAYS_PLAIN -> true + ExpressionsOfTypeProcessor.Mode.PLAIN_WHEN_NEEDED -> searchScope is LocalSearchScope // for local scope it's faster to use plain search } if (usePlainSearch) { - doPlainSearch(ktDeclaration, scope, optimizer) + doPlainSearch(targetDeclaration, searchScope, optimizer) return } - val descriptor = ktDeclaration.resolveToDescriptor() as? CallableDescriptor ?: return + val descriptor = targetDeclaration.resolveToDescriptor() as? CallableDescriptor ?: return if (descriptor is FunctionDescriptor && !descriptor.isValidOperator()) return @@ -120,24 +92,50 @@ fun findDestructuringDeclarationUsages( classDescriptor.defaultType.toFuzzyType(classDescriptor.typeConstructor.parameters) } - val componentIndex = when (ktDeclaration) { - is KtParameter -> ktDeclaration.dataClassComponentFunction()?.name?.asString()?.let { getComponentIndex(it) } - is KtFunction -> ktDeclaration.name?.let { getComponentIndex(it) } + val componentIndex = when (targetDeclaration) { + is KtParameter -> targetDeclaration.dataClassComponentFunction()?.name?.asString()?.let { getComponentIndex(it) } + is KtFunction -> targetDeclaration.name?.let { getComponentIndex(it) } //TODO: java component functions (see KT-13605) else -> null } ?: return - Processor(dataType, - ktDeclaration, - componentIndex, - scope, - consumer, - plainSearchHandler = { searchScope -> doPlainSearch(ktDeclaration, searchScope, optimizer) }, - testLog = destructuringDeclarationUsageSearchLog + ExpressionsOfTypeProcessor( + dataType, + searchScope, + suspiciousExpressionHandler = { expression -> processSuspiciousExpression(expression, targetDeclaration, componentIndex, consumer) }, + suspiciousScopeHandler = { searchScope -> doPlainSearch(targetDeclaration, searchScope, optimizer) }, + resolutionFacade = targetDeclaration.getResolutionFacade() ).run() } finally { - inProgress.remove(ktDeclaration) + inProgress.remove(targetDeclaration) + } +} + +private fun processSuspiciousExpression(expression: KtExpression, targetDeclaration: KtDeclaration, componentIndex: Int, consumer: Processor) { + val parent = expression.parent + val destructuringDeclaration = when (parent) { + is KtDestructuringDeclaration -> parent + + is KtContainerNode -> { + if (parent.node.elementType == KtNodeTypes.LOOP_RANGE) { + (parent.parent as KtForExpression).destructuringParameter + } + else { + null + } + } + + else -> null + } + + if (destructuringDeclaration != null && componentIndex <= destructuringDeclaration.entries.size) { + testLog?.add("Checked type of ${logPresentation(destructuringDeclaration)}") + + val declarationReference = destructuringDeclaration.entries[componentIndex - 1].references.firstIsInstance() + if (declarationReference.isReferenceTo(targetDeclaration)) { + consumer.process(declarationReference) + } } } @@ -147,587 +145,3 @@ private fun doPlainSearch(ktDeclaration: KtDeclaration, scope: SearchScope, opti filter = { ref -> ref is KtDestructuringDeclarationReference }) optimizer.searchWord("(", scope.restrictToKotlinSources(), UsageSearchContext.IN_CODE, true, unwrappedElement, resultProcessor) } - -private class Processor( - private val dataType: FuzzyType, - private val target: KtDeclaration, - private val componentIndex: Int, - private val searchScope: SearchScope, - private val consumer: Processor, - plainSearchHandler: (SearchScope) -> Unit, - private val testLog: MutableList? -) { - private val plainSearchHandler: (SearchScope) -> Unit = { scope -> - testLog?.add("Used plain search in ${scope.logPresentation()}") - plainSearchHandler(scope) - } - - private val project = target.project - - // note: a Task must define equals & hashCode! - private interface Task { - fun perform() - } - - private val tasks = ArrayDeque() - private val taskSet = HashSet() - - private val scopesToUsePlainSearch = LinkedHashMap>() - - fun run() { - val dataClassDescriptor = dataType.type.constructor.declarationDescriptor ?: return - val dataClassDeclaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, dataClassDescriptor) - val psiClass = when (dataClassDeclaration) { - is PsiClass -> dataClassDeclaration - is KtClassOrObject -> dataClassDeclaration.toLightClass() ?: return - else -> return - } - - // for data class from library always use plain search because we cannot search usages in compiled code (we could though) - if (!ProjectRootsUtil.isInProjectSource(psiClass)) { - plainSearchHandler(searchScope) - return - } - - addClassToProcess(psiClass) - - processTasks() - - val scopeElements = scopesToUsePlainSearch.values.flatMap { it }.toTypedArray() - plainSearchHandler(LocalSearchScope(scopeElements)) - } - - private fun addTask(task: Task) { - if (taskSet.add(task)) { - tasks.push(task) - } - - } - - private fun processTasks() { - while (tasks.isNotEmpty()) { - tasks.pop().perform() - } - } - - private fun downShiftToPlainSearch() { - tasks.clear() - scopesToUsePlainSearch.clear() - plainSearchHandler(searchScope) - } - - private fun addClassToProcess(classToSearch: PsiClass) { - data class ProcessClassUsagesTask(val classToSearch: PsiClass) : Task { - override fun perform() { - testLog?.add("Searched references to ${classToSearch.logPresentation()}") - ReferencesSearch.search(classToSearch).forEach(Processor processor@ { reference -> //TODO: see KT-13607 - if (processDataClassUsage(reference)) return@processor true - - if (destructuringDeclarationUsageSearchMode != ALWAYS_SMART) { - downShiftToPlainSearch() - return@processor 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 data class (and inheritors) because implicit 'this' can happen anywhere - (classToSearch as? KtLightClass)?.kotlinOrigin?.let { usePlainSearch(it) } - } - } - addTask(ProcessClassUsagesTask(classToSearch)) - } - - private enum class CallableToProcessKind { - HAS_DATA_CLASS_TYPE, - PROCESS_LAMBDAS - } - - /** - * Adds declaration whose type is our data class (or data class used anywhere inside that type) - * or which has parameter of functional type with our data class used inside - */ - private fun addCallableDeclarationToProcess(declaration: PsiElement, kind: CallableToProcessKind) { - if (declaration.isOperatorExpensiveToSearch()) { // cancel all tasks and use plain search - downShiftToPlainSearch() - return - } - - data class ProcessCallableUsagesTask(val declaration: PsiElement, val kind: CallableToProcessKind) : Task { - override fun perform() { - // we don't need to search usages of declarations in Java because Java doesn't have implicitly typed declarations so such usages cannot affect Kotlin code - //TODO: what about Scala and other JVM-languages? - val scope = GlobalSearchScope.projectScope(project).restrictToKotlinSources() - testLog?.add("Searched references to ${declaration.logPresentation()} in Kotlin files") - val searchParameters = KotlinReferencesSearchParameters( - declaration, scope, kotlinOptions = KotlinReferencesSearchOptions(searchNamedArguments = false)) - ReferencesSearch.search(searchParameters).forEach { reference -> - when (kind) { - CallableToProcessKind.HAS_DATA_CLASS_TYPE -> { - if (reference is KtDestructuringDeclarationReference) { - // declaration usage in form of destructuring declaration entry - addCallableDeclarationToProcess(reference.element, CallableToProcessKind.HAS_DATA_CLASS_TYPE) - } - else { - (reference.element as? KtReferenceExpression)?.let { processSuspiciousExpression(it) } - } - } - - CallableToProcessKind.PROCESS_LAMBDAS -> { - (reference.element as? KtReferenceExpression)?.let { processLambdasForCallableReference(it) } - } - } - } - } - } - addTask(ProcessCallableUsagesTask(declaration, kind)) - } - - private fun addSamInterfaceToProcess(psiClass: PsiClass) { - data class ProcessSamInterfaceTask(val psiClass: PsiClass) : Task { - override fun perform() { - //TODO: what about other JVM languages? - val scope = GlobalSearchScope.getScopeRestrictedByFileTypes(GlobalSearchScope.projectScope(project), JavaFileType.INSTANCE) - testLog?.add("Searched references to ${psiClass.logPresentation()} in java files") - ReferencesSearch.search(psiClass, scope).forEach { 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) - } - } - } - } - addTask(ProcessSamInterfaceTask(psiClass)) - } - - /** - * Process usage of our data class or one of its inheritors - */ - private fun processDataClassUsage(reference: PsiReference): Boolean { - val element = reference.element - return when (element.language) { - KotlinLanguage.INSTANCE -> processKotlinDataClassUsage(element) - - JavaLanguage.INSTANCE -> processJavaDataClassUsage(element) - - XMLLanguage.INSTANCE -> true // ignore usages in XML - they don't affect us - - else -> false // we don't know anything about usages in other languages - so we downgrade to slow algorithm in this case - } - } - - private fun processKotlinDataClassUsage(element: PsiElement): Boolean { - //TODO: type aliases - - when (element) { - is KtReferenceExpression -> { - val parent = element.parent - when (parent) { - is KtUserType -> { - return processDataClassUsageInUserType(parent) - } - - is KtCallExpression -> { - if (element == parent.calleeExpression) { - processSuspiciousExpression(parent) - return true - } - } - - is KtContainerNode -> { - if (parent.node.elementType == KtNodeTypes.LABEL_QUALIFIER) { - return true // this@ClassName - it will be handled anyway because members and extensions are processed with plain search - } - } - - is KtQualifiedExpression -> { - if (element == parent.receiverExpression) { - return true // companion object member or static member access - ignore it - } - } - - is KtCallableReferenceExpression -> { - when (element) { - parent.receiverExpression -> { // usage in receiver of callable reference (before "::") - ignore it - return true - } - - parent.callableReference -> { // usage after "::" in callable reference - should be reference to constructor of our data class - processSuspiciousExpression(element) - return true - } - } - } - - is KtClassLiteralExpression -> { - if (element == parent.receiverExpression) { // ClassName::class - processSuspiciousExpression(element) - return true - } - } - } - - if (element.getStrictParentOfType() != null) return true // ignore usage in import - } - - is KDocName -> return true // ignore usage in doc-comment - } - - return false // unsupported type of reference - } - - private fun processDataClassUsageInUserType(userType: KtUserType): Boolean { - val typeRef = userType.parents.lastOrNull { it is KtTypeReference } - val typeRefParent = typeRef?.parent - when (typeRefParent) { - is KtCallableDeclaration -> { - when (typeRef) { - typeRefParent.typeReference -> { - addCallableDeclarationToProcess(typeRefParent, CallableToProcessKind.HAS_DATA_CLASS_TYPE) - - if (typeRefParent is KtParameter) { //TODO: what if functional type is declared with "FunctionN<...>"? - val usedInsideFunctionalType = userType.parents.takeWhile { it != typeRef }.any { it is KtFunctionType } - if (usedInsideFunctionalType) { - val function = (typeRefParent.parent as? KtParameterList)?.parent as? KtFunction - if (function != null) { - addCallableDeclarationToProcess(function, CallableToProcessKind.PROCESS_LAMBDAS) - } - } - } - - return true - } - - typeRefParent.receiverTypeReference -> { - // we must use plain search inside extensions because implicit 'this' can happen anywhere - usePlainSearch(typeRefParent) - return true - } - } - } - - is KtTypeProjection -> { - val callExpression = (typeRefParent.parent as? KtTypeArgumentList)?.parent as? KtCallExpression - if (callExpression != null) { - processSuspiciousExpression(callExpression) - return true - } - } - - is KtConstructorCalleeExpression -> { - val parent = typeRefParent.parent - if (parent is KtSuperTypeCallEntry) { - val classOrObject = (parent.parent as KtSuperTypeList).parent as KtClassOrObject - val psiClass = classOrObject.toLightClass() - psiClass?.let { addClassToProcess(it) } - return true - } - } - - is KtSuperTypeListEntry -> { - if (typeRef == typeRefParent.typeReference) { - val classOrObject = (typeRefParent.parent as KtSuperTypeList).parent as KtClassOrObject - val psiClass = classOrObject.toLightClass() - psiClass?.let { addClassToProcess(it) } - return true - } - } - - is KtIsExpression -> { - val scopeOfPossibleSmartCast = typeRefParent.getParentOfType(true) - scopeOfPossibleSmartCast?.let { usePlainSearch(it) } - return true - } - - is KtWhenConditionIsPattern -> { - val whenEntry = typeRefParent.parent as KtWhenEntry - if (typeRefParent.isNegated) { - val whenExpression = whenEntry.parent as KtWhenExpression - val entriesAfter = whenExpression.entries.dropWhile { it != whenEntry }.drop(1) - entriesAfter.forEach { usePlainSearch(it) } - } - else { - usePlainSearch(whenEntry) - } - return true - } - - is KtBinaryExpressionWithTypeRHS -> { - processSuspiciousExpression(typeRefParent) - return true - } - } - - return false // unsupported case - } - - private fun processJavaDataClassUsage(element: PsiElement): Boolean { - if (element !is PsiJavaCodeReferenceElement) return true // meaningless reference from Java - - var prev = element - ParentsLoop@ - for (parent in element.parents) { - when (parent) { - is PsiCodeBlock, - is PsiExpression -> - break@ParentsLoop // ignore local usages - - is PsiMethod -> { - if (prev == parent.returnTypeElement && !parent.isPrivateOrLocal()) { - addCallableDeclarationToProcess(parent, CallableToProcessKind.HAS_DATA_CLASS_TYPE) - } - break@ParentsLoop - } - - is PsiField -> { - if (prev == parent.typeElement && !parent.isPrivateOrLocal()) { - addCallableDeclarationToProcess(parent, CallableToProcessKind.HAS_DATA_CLASS_TYPE) - } - break@ParentsLoop - } - - is PsiReferenceList -> { - if (parent.role == PsiReferenceList.Role.EXTENDS_LIST || parent.role == PsiReferenceList.Role.IMPLEMENTS_LIST) { - val psiClass = parent.parent as PsiClass - if (!psiClass.isPrivateOrLocal()) { - addClassToProcess(psiClass) - } - } - break@ParentsLoop - } - - //TODO: if Java parameter has Kotlin functional type then we should process method usages - is PsiParameter -> { - if (prev == parent.typeElement) { - val method = parent.declarationScope as? PsiMethod - if (method != null && method.hasModifierProperty(PsiModifier.ABSTRACT)) { - val psiClass = method.containingClass - if (psiClass != null) { - testLog?.add("Resolved java class to descriptor: ${psiClass.qualifiedName}") - - val classDescriptor = psiClass.resolveToDescriptor(target.getResolutionFacade()) - if (classDescriptor != null && SingleAbstractMethodUtils.getSingleAbstractMethodOrNull(classDescriptor) != null) { - addSamInterfaceToProcess(psiClass) - } - } - } - } - break@ParentsLoop - } - } - - prev = parent - } - - return true - } - - /** - * Process expression which may have type of our data class (or data class used anywhere inside that type) - */ - private fun processSuspiciousExpression(expression: KtExpression) { - var affectedScope: PsiElement = expression - ParentsLoop@ - for (element in expression.parentsWithSelf) { - affectedScope = element - if (element !is KtExpression) continue - - val parent = element.parent - when (parent) { - is KtDestructuringDeclaration -> { - processSuspiciousDeclaration(parent) - break@ParentsLoop - } - - is KtWithExpressionInitializer -> { - if (element == parent.initializer) { - processSuspiciousDeclaration(parent) - } - break@ParentsLoop - } - - is KtContainerNode -> { - if (parent.node.elementType == KtNodeTypes.LOOP_RANGE) { - val forExpression = parent.parent as KtForExpression - (forExpression.destructuringParameter ?: forExpression.loopParameter as KtDeclaration?)?.let { - processSuspiciousDeclaration(it) - } - break@ParentsLoop - } - } - } - - if (!element.mayTypeAffectAncestors()) break - } - - // use plain search in all lambdas and anonymous functions inside because they parameters or receiver can be implicitly typed with our data class - usePlainSearchInLambdas(affectedScope) - } - - private fun processLambdasForCallableReference(expression: KtReferenceExpression) { - //TODO: receiver? - usePlainSearchInLambdas(expression.parent) - } - - /** - * Process declaration which may have implicit type of our data class (or data class used anywhere inside that type) - */ - private fun processSuspiciousDeclaration(declaration: KtDeclaration) { - if (declaration is KtDestructuringDeclaration) { - if (searchScope.contains(declaration) && componentIndex <= declaration.entries.size) { - testLog?.add("Checked type of ${declaration.logPresentation()}") - - val declarationReference = declaration.entries[componentIndex - 1].references.firstIsInstance() - if (declarationReference.isReferenceTo(target)) { - consumer.process(declarationReference) - } - } - } - else { - if (!isImplicitlyTyped(declaration)) return - - testLog?.add("Checked type of ${declaration.logPresentation()}") - - val descriptor = declaration.resolveToDescriptorIfAny() as? CallableDescriptor ?: return - val type = descriptor.returnType - if (type != null && type.containsTypeOrDerivedInside(dataType)) { - addCallableDeclarationToProcess(declaration, CallableToProcessKind.HAS_DATA_CLASS_TYPE) - } - } - } - - private fun usePlainSearchInLambdas(scope: PsiElement) { - scope.forEachDescendantOfType { - if (it.nameIdentifier == null) { - usePlainSearch(it) - } - } - } - - private fun usePlainSearch(scope: KtElement) { - val file = scope.getContainingKtFile() - val restricted = LocalSearchScope(scope).intersectWith(searchScope) as 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 - } - } - prevElements.add(element) - } - } - - //TODO: code is quite similar to PartialBodyResolveFilter.isValueNeeded - private fun KtExpression.mayTypeAffectAncestors(): Boolean { - val parent = this.parent - when (parent) { - is KtBlockExpression -> { - return this == parent.statements.last() && parent.mayTypeAffectAncestors() - } - - is KtDeclarationWithBody -> { - if (this == parent.bodyExpression) { - return !parent.hasBlockBody() && !parent.hasDeclaredReturnType() - } - } - - is KtContainerNode -> { - val grandParent = parent.parent - return when (parent.node.elementType) { - KtNodeTypes.CONDITION, KtNodeTypes.BODY -> false - KtNodeTypes.THEN, KtNodeTypes.ELSE -> (grandParent as KtExpression).mayTypeAffectAncestors() - KtNodeTypes.LOOP_RANGE, KtNodeTypes.INDICES -> true - else -> true // something else unknown - } - } - } - return true // we don't know - } - - private fun PsiModifierListOwner.isPrivateOrLocal(): Boolean { - return hasModifierProperty(PsiModifier.PRIVATE) || parents.any { it is PsiCodeBlock } - } - - private fun PsiElement.isOperatorExpensiveToSearch(): Boolean { - when (this) { - is KtFunction -> { - if (name?.startsWith("component") == true) return false // component functions are not so expensive to search - return hasModifier(KtTokens.OPERATOR_KEYWORD) - || hasModifier(KtTokens.OVERRIDE_KEYWORD) && (resolveToDescriptorIfAny() as? FunctionDescriptor)?.isOperator == true - } - - is KtLightMethod -> { - return kotlinOrigin?.isOperatorExpensiveToSearch() == true - } - - else -> { - return false - } - } - } - - private fun KotlinType.containsTypeOrDerivedInside(type: FuzzyType): Boolean { - return type.checkIsSuperTypeOf(this) != null || arguments.any { it.type.containsTypeOrDerivedInside(type) } - } - - private fun isImplicitlyTyped(declaration: KtDeclaration): Boolean { - return when (declaration) { - is KtFunction -> !declaration.hasDeclaredReturnType() - is KtVariableDeclaration -> declaration.typeReference == null - is KtParameter -> declaration.typeReference == null - else -> false - } - } - - private fun PsiElement.logPresentation(): String? { - val fqName = getKotlinFqName()?.asString() - ?: (this as? KtNamedDeclaration)?.name - return when (this) { - is PsiMethod, is KtFunction -> fqName + "()" - is KtParameter -> { - val owner = this.ownerFunction?.logPresentation() ?: this.parent.toString() - "parameter ${this.name} in $owner" - } - is KtDestructuringDeclaration -> entries.joinToString(", ", prefix = "(", postfix = ")") { it.text } - else -> fqName - } - } - - private fun SearchScope.logPresentation(): String { - return when (this) { - searchScope -> "whole search scope" - - is LocalSearchScope -> { - scope - .map { element -> - " " + when (element) { - is KtFunctionLiteral -> element.text - is KtWhenEntry -> { - if (element.isElse) - "KtWhenEntry \"else\"" - else - "KtWhenEntry \"" + element.conditions.joinToString(", ") { it.text } + "\"" - } - is KtNamedDeclaration -> element.node.elementType.toString() + ":" + element.name - else -> element.toString() - } - } - .toList() - .sorted() - .joinToString("\n", "LocalSearchScope:\n") - } - - else -> this.displayName - } - - } -} diff --git a/idea/src/org/jetbrains/kotlin/idea/actions/internal/CheckComponentsUsageSearchAction.kt b/idea/src/org/jetbrains/kotlin/idea/actions/internal/CheckComponentsUsageSearchAction.kt index e8fc815fafd..b1f27036a42 100644 --- a/idea/src/org/jetbrains/kotlin/idea/actions/internal/CheckComponentsUsageSearchAction.kt +++ b/idea/src/org/jetbrains/kotlin/idea/actions/internal/CheckComponentsUsageSearchAction.kt @@ -28,8 +28,7 @@ import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFileVisitor import com.intellij.psi.PsiManager import com.intellij.psi.search.searches.ReferencesSearch -import org.jetbrains.kotlin.idea.search.usagesSearch.DestructuringDeclarationUsageSearch -import org.jetbrains.kotlin.idea.search.usagesSearch.destructuringDeclarationUsageSearchMode +import org.jetbrains.kotlin.idea.search.usagesSearch.ExpressionsOfTypeProcessor import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtFile @@ -67,12 +66,12 @@ class CheckComponentsUsageSearchAction : AnAction() { try { var smartRefsCount = 0 var goldRefsCount = 0 - ProgressManager.getInstance().runProcess(Runnable { - destructuringDeclarationUsageSearchMode = DestructuringDeclarationUsageSearch.ALWAYS_SMART + ProgressManager.getInstance().runProcess({ + ExpressionsOfTypeProcessor.mode = ExpressionsOfTypeProcessor.Mode.ALWAYS_SMART smartRefsCount = ReferencesSearch.search(parameter).findAll().size - destructuringDeclarationUsageSearchMode = DestructuringDeclarationUsageSearch.ALWAYS_PLAIN + ExpressionsOfTypeProcessor.mode = ExpressionsOfTypeProcessor.Mode.ALWAYS_PLAIN goldRefsCount = ReferencesSearch.search(parameter).findAll().size }, EmptyProgressIndicator()) @@ -85,7 +84,7 @@ class CheckComponentsUsageSearchAction : AnAction() { } } finally { - destructuringDeclarationUsageSearchMode = DestructuringDeclarationUsageSearch.PLAIN_WHEN_NEEDED + ExpressionsOfTypeProcessor.mode = ExpressionsOfTypeProcessor.Mode.PLAIN_WHEN_NEEDED } } diff --git a/idea/tests/org/jetbrains/kotlin/findUsages/AbstractFindUsagesTest.java b/idea/tests/org/jetbrains/kotlin/findUsages/AbstractFindUsagesTest.java index b80cfe4071c..cb4293899d9 100644 --- a/idea/tests/org/jetbrains/kotlin/findUsages/AbstractFindUsagesTest.java +++ b/idea/tests/org/jetbrains/kotlin/findUsages/AbstractFindUsagesTest.java @@ -58,7 +58,7 @@ import org.jetbrains.kotlin.idea.findUsages.KotlinClassFindUsagesOptions; import org.jetbrains.kotlin.idea.findUsages.KotlinFindUsagesHandlerFactory; import org.jetbrains.kotlin.idea.findUsages.KotlinFunctionFindUsagesOptions; import org.jetbrains.kotlin.idea.findUsages.KotlinPropertyFindUsagesOptions; -import org.jetbrains.kotlin.idea.search.usagesSearch.DestructuringDeclarationUsagesKt; +import org.jetbrains.kotlin.idea.search.usagesSearch.ExpressionsOfTypeProcessor; import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase; import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor; import org.jetbrains.kotlin.idea.test.PluginTestCaseBase; @@ -70,7 +70,10 @@ import org.jetbrains.kotlin.test.KotlinTestUtils; import java.io.File; import java.io.FilenameFilter; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; public abstract class AbstractFindUsagesTest extends KotlinLightCodeInsightFixtureTestCase { @@ -401,14 +404,14 @@ public abstract class AbstractFindUsagesTest extends KotlinLightCodeInsightFixtu Collection usageInfos; String log = null; try { - DestructuringDeclarationUsagesKt.setDestructuringDeclarationUsageSearchLog(new ArrayList()); + ExpressionsOfTypeProcessor.Companion.setTestLog(new ArrayList()); usageInfos = findUsages(caretElement, options, highlightingMode); } finally { - List logList = DestructuringDeclarationUsagesKt.getDestructuringDeclarationUsageSearchLog(); + List logList = ExpressionsOfTypeProcessor.Companion.getTestLog(); assert logList != null; - DestructuringDeclarationUsagesKt.setDestructuringDeclarationUsageSearchLog(null); + ExpressionsOfTypeProcessor.Companion.setTestLog(null); if (logList.size() > 0) { Collections.sort(logList); log = StringUtil.join(logList, "\n"); diff --git a/idea/tests/org/jetbrains/kotlin/search/KotlinReferencesSearchTest.kt b/idea/tests/org/jetbrains/kotlin/search/KotlinReferencesSearchTest.kt index 8251c4f3dfe..cbace223064 100644 --- a/idea/tests/org/jetbrains/kotlin/search/KotlinReferencesSearchTest.kt +++ b/idea/tests/org/jetbrains/kotlin/search/KotlinReferencesSearchTest.kt @@ -22,8 +22,7 @@ import com.intellij.psi.search.LocalSearchScope import com.intellij.psi.search.searches.ReferencesSearch import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture import org.jetbrains.kotlin.idea.references.KtDestructuringDeclarationReference -import org.jetbrains.kotlin.idea.search.usagesSearch.DestructuringDeclarationUsageSearch -import org.jetbrains.kotlin.idea.search.usagesSearch.destructuringDeclarationUsageSearchMode +import org.jetbrains.kotlin.idea.search.usagesSearch.ExpressionsOfTypeProcessor import org.jetbrains.kotlin.idea.test.PluginTestCaseBase import org.jetbrains.kotlin.psi.KtFunction import org.jetbrains.kotlin.psi.KtParameter @@ -69,12 +68,12 @@ class KotlinReferencesSearchTest(): AbstractSearcherTest() { // check that local reference search gives the same result try { - destructuringDeclarationUsageSearchMode = DestructuringDeclarationUsageSearch.PLAIN_WHEN_NEEDED + ExpressionsOfTypeProcessor.mode = ExpressionsOfTypeProcessor.Mode.PLAIN_WHEN_NEEDED val localRefs = ReferencesSearch.search(func, LocalSearchScope(psiFile)).findAll() Assert.assertEquals(refs.size, localRefs.size) } finally { - destructuringDeclarationUsageSearchMode = DestructuringDeclarationUsageSearch.ALWAYS_SMART + ExpressionsOfTypeProcessor.mode = ExpressionsOfTypeProcessor.Mode.ALWAYS_SMART } return refs