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");