From 44d3b8fb1a3d62f0ecdc4fa601646562bed57f8e Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Thu, 15 Jun 2017 16:38:21 +0300 Subject: [PATCH] Ignore sub-queries for other operators with the same receiver (KT-18566) ExpressionsOfTypeProcessor searches for all occurence of expression with given type. It start from usages of the class, searches for sub-classes, declarations that return those classes, usages of these declarations, and so on. During this search, find usages for all operators that return the subject type is executed as sub-queries. Full search for such queries can't give addition types. And it also shouldn't give additional scopes for search, because same scopes should be located by operands. In other words, if sub-query can spot the scope of usage starting from the same type, the original query should also process same scope. #KT-18566 Fixed --- .../ExpressionsOfTypeProcessor.kt | 28 +++------ .../operators/OperatorReferenceSearcher.kt | 56 ++++++++++++----- .../java/findJavaMethodUsages/UnaryNot.log | 1 + .../components/recursiveDataClass1.log | 1 + .../components/recursiveDataClass2.log | 1 + .../findUsages/kotlin/conventions/inc.log | 1 + .../findUsages/kotlin/conventions/plus.log | 22 +------ .../kotlin/conventions/severalOperators.0.kt | 39 ++++++++++++ .../kotlin/conventions/severalOperators.log | 61 +++++++++++++++++++ .../conventions/severalOperators.results.txt | 5 ++ .../kotlin/conventions/unaryMinus.log | 1 + .../findUsages/FindUsagesTestGenerated.java | 6 ++ 12 files changed, 168 insertions(+), 54 deletions(-) create mode 100644 idea/testData/findUsages/kotlin/conventions/severalOperators.0.kt create mode 100644 idea/testData/findUsages/kotlin/conventions/severalOperators.log create mode 100644 idea/testData/findUsages/kotlin/conventions/severalOperators.results.txt 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 1686ca25b44..22bed4c566a 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 @@ -43,7 +43,6 @@ import org.jetbrains.kotlin.idea.KotlinLanguage import org.jetbrains.kotlin.idea.caches.resolve.analyze 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.excludeFileTypes @@ -67,6 +66,7 @@ import java.util.* class ExpressionsOfTypeProcessor( private val typeToSearch: FuzzyType, + private val classToSearch: PsiClass?, private val searchScope: SearchScope, private val project: Project, private val possibleMatchHandler: (KtExpression) -> Unit, @@ -120,7 +120,7 @@ class ExpressionsOfTypeProcessor( } private val tasks = ArrayDeque() - private val taskSet = HashSet() + private val taskSet = HashSet() private val scopesToUsePlainSearch = LinkedHashMap>() @@ -130,7 +130,7 @@ class ExpressionsOfTypeProcessor( 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) { + if (usePlainSearch || classToSearch == null) { possibleMatchesInScopeHandler(searchScope) return } @@ -138,15 +138,13 @@ class ExpressionsOfTypeProcessor( // optimization if (runReadAction { searchScope is GlobalSearchScope && !FileTypeIndex.containsFileOfType(KotlinFileType.INSTANCE, searchScope) }) return - val psiClass = runReadAction { detectClassToSearch() } - // for class from library always use plain search because we cannot search usages in compiled code (we could though) - if (psiClass == null || !runReadAction { psiClass.isValid && ProjectRootsUtil.isInProjectSource(psiClass) }) { + if (classToSearch == null || !runReadAction { classToSearch.isValid && ProjectRootsUtil.isInProjectSource(classToSearch) }) { possibleMatchesInScopeHandler(searchScope) return } - addClassToProcess(psiClass) + addClassToProcess(classToSearch) processTasks() @@ -161,16 +159,6 @@ class ExpressionsOfTypeProcessor( } } - private fun detectClassToSearch(): PsiClass? { - 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 - } - } - private fun addTask(task: Task) { if (taskSet.add(task)) { tasks.push(task) @@ -208,7 +196,6 @@ class ExpressionsOfTypeProcessor( return true } - private fun addNonKotlinClassToProcess(classToSearch: PsiClass) { if (!checkPsiClass(classToSearch)) { return @@ -398,7 +385,10 @@ class ExpressionsOfTypeProcessor( } @Suppress("NAME_SHADOWING") - data class ProcessCallableUsagesTask(val declaration: PsiElement, val processor: ReferenceProcessor, val scope: SearchScope) : Task { + data class ProcessCallableUsagesTask( + val declaration: PsiElement, + val processor: ReferenceProcessor, + val scope: SearchScope) : Task { override fun perform() { if (scope is LocalSearchScope) { testLog { "Searched imported static member $declaration in ${scope.scope.toList()}" } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/operators/OperatorReferenceSearcher.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/operators/OperatorReferenceSearcher.kt index 7f7b5245813..ffd32d00e89 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/operators/OperatorReferenceSearcher.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/operators/OperatorReferenceSearcher.kt @@ -23,12 +23,14 @@ import com.intellij.psi.search.* import com.intellij.util.Processor import org.jetbrains.kotlin.asJava.elements.KtLightMethod import org.jetbrains.kotlin.asJava.namedUnwrappedElement +import org.jetbrains.kotlin.asJava.toLightClass import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.caches.resolve.getJavaOrKotlinMemberDescriptor import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny +import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinRequestResultProcessor import org.jetbrains.kotlin.idea.search.restrictToKotlinSources @@ -192,7 +194,6 @@ abstract class OperatorReferenceSearcher( } - //TODO: check no light elements here private object SearchesInProgress : ThreadLocal>() { override fun initialValue() = HashSet() } @@ -207,14 +208,27 @@ abstract class OperatorReferenceSearcher( } fun run() { + val receiverType = runReadAction { extractReceiverType() } ?: return + val psiClass = runReadAction { receiverType.toPsiClass() } + val inProgress = SearchesInProgress.get() - if (!inProgress.add(targetDeclaration)) return //TODO: it's not quite correct + if (psiClass != null) { + if (!inProgress.add(psiClass)) { + testLog { "ExpressionOfTypeProcessor is already started for ${runReadAction { psiClass.qualifiedName }}. Exit for operator ${logPresentation(targetDeclaration)}." } + return + } + } + else { + if (!inProgress.add(targetDeclaration)) { + testLog { "ExpressionOfTypeProcessor is already started for operator ${logPresentation(targetDeclaration)}. Exit." } + return //TODO: it's not quite correct + } + } try { - val receiverType = runReadAction { extractReceiverType() } ?: return - ExpressionsOfTypeProcessor( receiverType, + psiClass, searchScope, project, possibleMatchHandler = { expression -> processPossibleReceiverExpression(expression) }, @@ -222,7 +236,17 @@ abstract class OperatorReferenceSearcher( ).run() } finally { - inProgress.remove(targetDeclaration) + inProgress.remove(if (psiClass != null) psiClass else targetDeclaration) + } + } + + private fun FuzzyType.toPsiClass(): PsiClass? { + val classDescriptor = type.constructor.declarationDescriptor ?: return null + val classDeclaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, classDescriptor) + return when (classDeclaration) { + is PsiClass -> classDeclaration + is KtClassOrObject -> classDeclaration.toLightClass() + else -> null } } @@ -312,16 +336,18 @@ abstract class OperatorReferenceSearcher( 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 } + "\"" + " " + runReadAction { + 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() } - is KtNamedDeclaration -> element.node.elementType.toString() + ":" + element.name - else -> element.toString() } } .toList() @@ -333,4 +359,4 @@ abstract class OperatorReferenceSearcher( } } -} +} \ No newline at end of file diff --git a/idea/testData/findUsages/java/findJavaMethodUsages/UnaryNot.log b/idea/testData/findUsages/java/findJavaMethodUsages/UnaryNot.log index a39cf588e5f..9037f2284ce 100644 --- a/idea/testData/findUsages/java/findJavaMethodUsages/UnaryNot.log +++ b/idea/testData/findUsages/java/findJavaMethodUsages/UnaryNot.log @@ -1,5 +1,6 @@ Checked type of u1 Checked type of u2 +ExpressionOfTypeProcessor is already started for UnaryNot. Exit for operator UnaryNot.not(). Resolved !u1 Searched references to UnaryNot Searched references to UnaryNot.not() in non-Java files diff --git a/idea/testData/findUsages/kotlin/conventions/components/recursiveDataClass1.log b/idea/testData/findUsages/kotlin/conventions/components/recursiveDataClass1.log index 0d219885289..3aaa9783f9d 100644 --- a/idea/testData/findUsages/kotlin/conventions/components/recursiveDataClass1.log +++ b/idea/testData/findUsages/kotlin/conventions/components/recursiveDataClass1.log @@ -4,6 +4,7 @@ Checked type of a2 Checked type of n1 Checked type of n2 Checked type of n2 +ExpressionOfTypeProcessor is already started for A. Exit for operator parameter a of A(val a: A?, val n: Int). Resolved (a1, n1) Resolved (a2, n2) Resolved (a2, n2) diff --git a/idea/testData/findUsages/kotlin/conventions/components/recursiveDataClass2.log b/idea/testData/findUsages/kotlin/conventions/components/recursiveDataClass2.log index 2c2f3dc1544..429d44f00ea 100644 --- a/idea/testData/findUsages/kotlin/conventions/components/recursiveDataClass2.log +++ b/idea/testData/findUsages/kotlin/conventions/components/recursiveDataClass2.log @@ -2,6 +2,7 @@ Checked type of a1 Checked type of b Checked type of n Checked type of s +ExpressionOfTypeProcessor is already started for A. Exit for operator parameter b of A(val b: B, val n: Int). Resolved (a1, s) Resolved (b, n) Searched references to A diff --git a/idea/testData/findUsages/kotlin/conventions/inc.log b/idea/testData/findUsages/kotlin/conventions/inc.log index 2001b877ab9..c82a23de27d 100644 --- a/idea/testData/findUsages/kotlin/conventions/inc.log +++ b/idea/testData/findUsages/kotlin/conventions/inc.log @@ -1,4 +1,5 @@ Checked type of a +ExpressionOfTypeProcessor is already started for A. Exit for operator A.inc(). Resolved ++a Resolved a++ Searched references to A diff --git a/idea/testData/findUsages/kotlin/conventions/plus.log b/idea/testData/findUsages/kotlin/conventions/plus.log index 18a42bf45c5..39889f1322a 100644 --- a/idea/testData/findUsages/kotlin/conventions/plus.log +++ b/idea/testData/findUsages/kotlin/conventions/plus.log @@ -1,39 +1,21 @@ Checked type of a -Checked type of a -Checked type of a1 Checked type of a1 Checked type of a2 -Checked type of a2 -Resolved A(0) + A(1) +ExpressionOfTypeProcessor is already started for A. Exit for operator A.plus(a: A). +ExpressionOfTypeProcessor is already started for A. Exit for operator A.plus(m: Int). Resolved A(0) + A(1) Resolved A(0) + A(1) + 2 Resolved A(0) + A(1) + 2 -Resolved A(0) + A(1) + 2 -Resolved A(0) + A(1) + 2 -Resolved A(0) + A(1) + 2 -Resolved a += 1 Resolved a += 1 Resolved a += A(1) -Resolved a += A(1) -Resolved a1 + 1 Resolved a1 + 1 Searched references to A -Searched references to A -Searched references to A.plus(a: A) in non-Java files Searched references to A.plus(a: A) in non-Java files Searched references to A.plus(m: Int) in non-Java files -Searched references to A.plus(m: Int) in non-Java files -Searched references to a in non-Java files Searched references to a in non-Java files Searched references to a1 in non-Java files -Searched references to a1 in non-Java files -Searched references to a2 in non-Java files Searched references to a2 in non-Java files Searched references to parameter a of A.plus(a: A) in non-Java files -Searched references to parameter a of A.plus(a: A) in non-Java files Searched references to parameter array of test(array: Array) in non-Java files -Searched references to parameter array of test(array: Array) in non-Java files -Used plain search of A.plus(a: A) in LocalSearchScope: - CLASS:A Used plain search of A.plus(m: Int) in LocalSearchScope: CLASS:A \ No newline at end of file diff --git a/idea/testData/findUsages/kotlin/conventions/severalOperators.0.kt b/idea/testData/findUsages/kotlin/conventions/severalOperators.0.kt new file mode 100644 index 00000000000..dcc0fce158c --- /dev/null +++ b/idea/testData/findUsages/kotlin/conventions/severalOperators.0.kt @@ -0,0 +1,39 @@ +// PSI_ELEMENT: org.jetbrains.kotlin.psi.KtNamedFunction +// OPTIONS: usages + +open class Diction { + operator fun minus(other: Diction): Diction { return Diction() } + operator fun plus(other: Diction): Diction { return Diction() } +} + +operator fun Diction.times(other: Diction) = Diction() + +class A +operator fun A.div(other: A) = Diction() + +fun indirectDiction() = A() / A() +fun indirectPlusDiction() = indirectDiction() + indirectDiction() + +val t = { d: Diction -> d + d } +val tt = { t(indirectDiction()) } + +fun test1(d1: Diction, d2: Diction) { + val a = d1 + d2 + val b = d1 - d2 + val c = d1 * d2 + val d = b - c +} + +fun test2() { + val dInT2 = indirectDiction() + dInT2 - dInT2 +} + +fun test3() { + val dInT3 = indirectPlusDiction() + dInT3 - dInT3 +} + +fun test4() { + tt() - tt() +} \ No newline at end of file diff --git a/idea/testData/findUsages/kotlin/conventions/severalOperators.log b/idea/testData/findUsages/kotlin/conventions/severalOperators.log new file mode 100644 index 00000000000..d306798b8aa --- /dev/null +++ b/idea/testData/findUsages/kotlin/conventions/severalOperators.log @@ -0,0 +1,61 @@ +Checked type of a +Checked type of a +Checked type of b +Checked type of b +Checked type of c +Checked type of c +Checked type of d +Checked type of d +Checked type of dInT2 +Checked type of dInT3 +Checked type of div(other: A) +Checked type of indirectDiction() +Checked type of indirectDiction() +Checked type of indirectDiction() +Checked type of indirectPlusDiction() +Checked type of indirectPlusDiction() +Checked type of t +Checked type of t +Checked type of times(other: Diction) +Checked type of tt +Checked type of tt +ExpressionOfTypeProcessor is already started for Diction. Exit for operator Diction.minus(other: Diction). +ExpressionOfTypeProcessor is already started for Diction. Exit for operator Diction.plus(other: Diction). +ExpressionOfTypeProcessor is already started for Diction. Exit for operator times(other: Diction). +Resolved A() / A() +Resolved b - c +Resolved d1 - d2 +Resolved dInT2 - dInT2 +Resolved dInT3 - dInT3 +Resolved tt() - tt() +Searched references to A +Searched references to Diction +Searched references to Diction.minus(other: Diction) in non-Java files +Searched references to Diction.plus(other: Diction) in non-Java files +Searched references to a in non-Java files +Searched references to b in non-Java files +Searched references to c in non-Java files +Searched references to d in non-Java files +Searched references to dInT2 in non-Java files +Searched references to dInT3 in non-Java files +Searched references to div(other: A) in non-Java files +Searched references to indirectDiction() in non-Java files +Searched references to indirectPlusDiction() in non-Java files +Searched references to parameter d of d: Diction in non-Java files +Searched references to parameter d1 of test1(d1: Diction, d2: Diction) in non-Java files +Searched references to parameter d2 of test1(d1: Diction, d2: Diction) in non-Java files +Searched references to parameter other of Diction.minus(other: Diction) in non-Java files +Searched references to parameter other of Diction.plus(other: Diction) in non-Java files +Searched references to parameter other of div(other: A) in non-Java files +Searched references to parameter other of times(other: Diction) in non-Java files +Searched references to t in non-Java files +Searched references to times(other: Diction) in non-Java files +Searched references to tt in non-Java files +Used plain search of Diction.minus(other: Diction) in LocalSearchScope: + CLASS:Diction + FUN:times + { d: Diction -> d + d } + { t(indirectDiction()) } +Used plain search of div(other: A) in LocalSearchScope: + CLASS:A + FUN:div \ No newline at end of file diff --git a/idea/testData/findUsages/kotlin/conventions/severalOperators.results.txt b/idea/testData/findUsages/kotlin/conventions/severalOperators.results.txt new file mode 100644 index 00000000000..f748738c39b --- /dev/null +++ b/idea/testData/findUsages/kotlin/conventions/severalOperators.results.txt @@ -0,0 +1,5 @@ +Function call 22 val b = d1 - d2 +Function call 24 val d = b - c +Function call 29 dInT2 - dInT2 +Function call 34 dInT3 - dInT3 +Function call 38 tt() - tt() \ No newline at end of file diff --git a/idea/testData/findUsages/kotlin/conventions/unaryMinus.log b/idea/testData/findUsages/kotlin/conventions/unaryMinus.log index 578898efb74..3a3ed2b06e1 100644 --- a/idea/testData/findUsages/kotlin/conventions/unaryMinus.log +++ b/idea/testData/findUsages/kotlin/conventions/unaryMinus.log @@ -1,3 +1,4 @@ +ExpressionOfTypeProcessor is already started for A. Exit for operator A.unaryMinus(). Resolved -A(1) Searched references to A Searched references to A.unaryMinus() in non-Java files diff --git a/idea/tests/org/jetbrains/kotlin/findUsages/FindUsagesTestGenerated.java b/idea/tests/org/jetbrains/kotlin/findUsages/FindUsagesTestGenerated.java index d8cf05f6594..cf32e472dab 100644 --- a/idea/tests/org/jetbrains/kotlin/findUsages/FindUsagesTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/findUsages/FindUsagesTestGenerated.java @@ -169,6 +169,12 @@ public class FindUsagesTestGenerated extends AbstractFindUsagesTest { doTest(fileName); } + @TestMetadata("severalOperators.0.kt") + public void testSeveralOperators() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/conventions/severalOperators.0.kt"); + doTest(fileName); + } + @TestMetadata("unaryMinus.0.kt") public void testUnaryMinus() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/conventions/unaryMinus.0.kt");