From e3b37f921947f475b2c18e0d9c267309453bb10b Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Wed, 31 Aug 2016 22:41:47 +0300 Subject: [PATCH] Implemented fast search for invoke operator calls #KT-13643 Fixed --- .../KotlinReferencesSearcher.kt | 12 +- .../ExpressionsOfTypeProcessor.kt | 3 +- ... => findDestructuringDeclarationUsages.kt} | 0 .../usagesSearch/findInvokeOperatorUsages.kt | 136 ++++++++++++++++++ .../handlers/KotlinFindMemberUsagesHandler.kt | 4 +- ...otlinConventionMethodReferencesSearcher.kt | 5 + .../findUsages/kotlin/conventions/invoke.0.kt | 8 +- .../findUsages/kotlin/conventions/invoke.log | 8 ++ .../kotlin/conventions/invoke.results.txt | 3 +- .../rename/renameInvoke/after/invoke.kt | 2 +- .../search/references/testInvokeFun.kt | 10 ++ .../findUsages/AbstractFindUsagesTest.java | 10 ++ .../search/KotlinReferencesSearchTest.kt | 8 ++ 13 files changed, 197 insertions(+), 12 deletions(-) rename idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/{destructuringDeclarationUsages.kt => findDestructuringDeclarationUsages.kt} (100%) create mode 100644 idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/findInvokeOperatorUsages.kt create mode 100644 idea/testData/findUsages/kotlin/conventions/invoke.log create mode 100644 idea/testData/search/references/testInvokeFun.kt diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinReferencesSearcher.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinReferencesSearcher.kt index 0725ce85ee1..e1aa29b7dda 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinReferencesSearcher.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinReferencesSearcher.kt @@ -37,22 +37,21 @@ import org.jetbrains.kotlin.idea.search.KOTLIN_NAMED_ARGUMENT_SEARCH_CONTEXT import org.jetbrains.kotlin.idea.search.allScope import org.jetbrains.kotlin.idea.search.effectiveSearchScope import org.jetbrains.kotlin.idea.search.unionSafe -import org.jetbrains.kotlin.idea.search.usagesSearch.dataClassComponentFunction -import org.jetbrains.kotlin.idea.search.usagesSearch.findDestructuringDeclarationUsages -import org.jetbrains.kotlin.idea.search.usagesSearch.getClassNameForCompanionObject -import org.jetbrains.kotlin.idea.search.usagesSearch.getSpecialNamesToSearch +import org.jetbrains.kotlin.idea.search.usagesSearch.* import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.psi.psiUtil.parents import org.jetbrains.kotlin.resolve.dataClassUtils.isComponentLike +import org.jetbrains.kotlin.util.OperatorNameConventions data class KotlinReferencesSearchOptions(val acceptCallableOverrides: Boolean = false, val acceptOverloads: Boolean = false, val acceptExtensionsOfDeclarationClass: Boolean = false, val acceptCompanionObjectMembers: Boolean = false, val searchForComponentConventions: Boolean = true, + val searchInvokeOperator: Boolean = true, val searchNamedArguments: Boolean = true) { fun anyEnabled(): Boolean = acceptCallableOverrides || acceptOverloads || acceptExtensionsOfDeclarationClass @@ -144,6 +143,11 @@ class KotlinReferencesSearcher : QueryExecutorBase { - if (name?.startsWith("component") == true) return false // component functions are not so expensive to search + if (name?.startsWith("component") == true || name == OperatorNameConventions.INVOKE.asString()) return false return hasModifier(KtTokens.OPERATOR_KEYWORD) || hasModifier(KtTokens.OVERRIDE_KEYWORD) && (resolveToDescriptorIfAny() as? FunctionDescriptor)?.isOperator == true } 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/findDestructuringDeclarationUsages.kt similarity index 100% rename from idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/destructuringDeclarationUsages.kt rename to idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/findDestructuringDeclarationUsages.kt diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/findInvokeOperatorUsages.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/findInvokeOperatorUsages.kt new file mode 100644 index 00000000000..870ad829fd8 --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/findInvokeOperatorUsages.kt @@ -0,0 +1,136 @@ +/* + * 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.openapi.roots.ProjectRootManager +import com.intellij.psi.PsiManager +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiReference +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.search.LocalSearchScope +import com.intellij.psi.search.SearchScope +import com.intellij.util.Processor +import org.jetbrains.kotlin.asJava.elements.KtLightMethod +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor +import org.jetbrains.kotlin.idea.references.KtInvokeFunctionReference +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.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType +import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension +import org.jetbrains.kotlin.util.isValidOperator +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance + +//TODO: problem with infinite recursion not solved + +//TODO: code duplication + +fun findInvokeOperatorUsages( + invokeFunction: PsiMethod, + scope: SearchScope, + consumer: Processor +) { + if (invokeFunction !is KtLightMethod) return //TODO + val ktDeclarationTarget = invokeFunction.kotlinOrigin as? KtDeclaration ?: return //TODO? + findInvokeOperatorUsages(ktDeclarationTarget, scope, consumer) +} + +fun findInvokeOperatorUsages( + targetDeclaration: KtDeclaration, + searchScope: SearchScope, + consumer: Processor +) { + 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(targetDeclaration, searchScope, consumer) + return + } + + val descriptor = targetDeclaration.resolveToDescriptor() as? CallableDescriptor ?: return + + if (descriptor is FunctionDescriptor && !descriptor.isValidOperator()) return + + val dataType = if (descriptor.isExtension) { + descriptor.fuzzyExtensionReceiverType()!! + } + else { + val classDescriptor = descriptor.containingDeclaration as? ClassDescriptor ?: return + classDescriptor.defaultType.toFuzzyType(classDescriptor.typeConstructor.parameters) + } + + ExpressionsOfTypeProcessor( + dataType, + searchScope, + suspiciousExpressionHandler = { expression -> processSuspiciousExpression(expression, targetDeclaration, consumer) }, + suspiciousScopeHandler = { searchScope -> doPlainSearch(targetDeclaration, searchScope, consumer) }, + resolutionFacade = targetDeclaration.getResolutionFacade() + ).run() +} + +private fun processSuspiciousExpression(expression: KtExpression, targetDeclaration: KtDeclaration, consumer: Processor) { + val callExpression = expression.parent as? KtCallExpression ?: return + testLog?.add("Resolving call ${callExpression.text}") + processCallExpression(callExpression, targetDeclaration, consumer) +} + +private fun doPlainSearch(ktDeclaration: KtDeclaration, scope: SearchScope, consumer: Processor) { + if (scope is LocalSearchScope) { + for (element in scope.scope) { + val stop = element.anyDescendantOfType { !processCallExpression(it, ktDeclaration, consumer) } + if (stop) break + } + } + else { + scope as GlobalSearchScope + val project = ktDeclaration.project + val psiManager = PsiManager.getInstance(project) + ProjectRootManager.getInstance(project).fileIndex.iterateContent { file -> + if (file in scope) { + val ktFile = psiManager.findFile(file) as? KtFile + if (ktFile != null) { + val stop = ktFile.anyDescendantOfType { + !processCallExpression(it, ktDeclaration, consumer) + } + if (stop) return@iterateContent false + } + } + true + } + } +} + +private fun processCallExpression(callExpression: KtCallExpression, targetDeclaration: KtDeclaration, consumer: Processor): Boolean { + val reference = callExpression.references.firstIsInstance() + if (reference.isReferenceTo(targetDeclaration)) { + return consumer.process(reference) + } + else { + return true + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/findUsages/handlers/KotlinFindMemberUsagesHandler.kt b/idea/src/org/jetbrains/kotlin/idea/findUsages/handlers/KotlinFindMemberUsagesHandler.kt index 1076a671074..163c7e9d3f3 100644 --- a/idea/src/org/jetbrains/kotlin/idea/findUsages/handlers/KotlinFindMemberUsagesHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/findUsages/handlers/KotlinFindMemberUsagesHandler.kt @@ -131,9 +131,9 @@ abstract class KotlinFindMemberUsagesHandler val uniqueProcessor = CommonProcessors.UniqueProcessor(processor) if (options.isUsages) { - // we disable searchForComponentConventions for ReferencesSearch because they will be searched by MethodReferencesSearch + // we disable searchForComponentConventions and searchInvokeOperator for ReferencesSearch because they will be searched by MethodReferencesSearch val kotlinSearchOptions = createKotlinReferencesSearchOptions(options) - .copy(searchForComponentConventions = false) + .copy(searchForComponentConventions = false, searchInvokeOperator = false) val searchParameters = KotlinReferencesSearchParameters(element, options.searchScope, kotlinOptions = kotlinSearchOptions) with(applyQueryFilters(element, options, ReferencesSearch.search(searchParameters))) { diff --git a/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinConventionMethodReferencesSearcher.kt b/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinConventionMethodReferencesSearcher.kt index 8600bdf960d..61fd25b0a94 100644 --- a/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinConventionMethodReferencesSearcher.kt +++ b/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinConventionMethodReferencesSearcher.kt @@ -23,11 +23,13 @@ import com.intellij.psi.search.searches.MethodReferencesSearch import com.intellij.util.Processor import org.jetbrains.kotlin.idea.search.restrictToKotlinSources import org.jetbrains.kotlin.idea.search.usagesSearch.findDestructuringDeclarationUsages +import org.jetbrains.kotlin.idea.search.usagesSearch.findInvokeOperatorUsages import org.jetbrains.kotlin.idea.search.usagesSearch.getOperationSymbolsToSearch import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.lexer.KtSingleValueToken import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.dataClassUtils.isComponentLike +import org.jetbrains.kotlin.util.OperatorNameConventions class KotlinConventionMethodReferencesSearcher() : QueryExecutorBase(true) { override fun processQuery(queryParameters: MethodReferencesSearch.SearchParameters, consumer: Processor) { @@ -39,6 +41,9 @@ class KotlinConventionMethodReferencesSearcher() : QueryExecutorBaseinvoke(i: Int): B = B(i) + operator fun invoke(i: Int){} } +fun f() = B(1) + fun test() { - B(1).invoke(2) - B(1)(2) + f(1).invoke(2) + f(2)(2) } diff --git a/idea/testData/findUsages/kotlin/conventions/invoke.log b/idea/testData/findUsages/kotlin/conventions/invoke.log new file mode 100644 index 00000000000..71833a1b3ae --- /dev/null +++ b/idea/testData/findUsages/kotlin/conventions/invoke.log @@ -0,0 +1,8 @@ +Checked type of f() +Resolving call f(1) +Resolving call f(2) +Resolving call f(2)(2) +Searched references to B +Searched references to f() in Kotlin files +Used plain search in LocalSearchScope: + CLASS:B \ No newline at end of file diff --git a/idea/testData/findUsages/kotlin/conventions/invoke.results.txt b/idea/testData/findUsages/kotlin/conventions/invoke.results.txt index cae68d38fe6..aa797ea0af4 100644 --- a/idea/testData/findUsages/kotlin/conventions/invoke.results.txt +++ b/idea/testData/findUsages/kotlin/conventions/invoke.results.txt @@ -1 +1,2 @@ -Function call 9 B(1).invoke(2) +Function call 11 f(1).invoke(2) +Implicit 'invoke' 12 f(2)(2) \ No newline at end of file diff --git a/idea/testData/refactoring/rename/renameInvoke/after/invoke.kt b/idea/testData/refactoring/rename/renameInvoke/after/invoke.kt index fb506903b87..d620b43aa28 100644 --- a/idea/testData/refactoring/rename/renameInvoke/after/invoke.kt +++ b/idea/testData/refactoring/rename/renameInvoke/after/invoke.kt @@ -4,5 +4,5 @@ class A(val n: Int) { fun test() { A(1).foo(2) - A(1)(2) + A(1).foo(2) } \ No newline at end of file diff --git a/idea/testData/search/references/testInvokeFun.kt b/idea/testData/search/references/testInvokeFun.kt new file mode 100644 index 00000000000..693a372e273 --- /dev/null +++ b/idea/testData/search/references/testInvokeFun.kt @@ -0,0 +1,10 @@ +class B(val n: Int) { + operator fun invoke(i: Int){} +} + +fun f() = B(1) + +fun test() { + f(1).invoke(2) + f(2)(2) +} diff --git a/idea/tests/org/jetbrains/kotlin/findUsages/AbstractFindUsagesTest.java b/idea/tests/org/jetbrains/kotlin/findUsages/AbstractFindUsagesTest.java index cb4293899d9..d3a20d9df38 100644 --- a/idea/tests/org/jetbrains/kotlin/findUsages/AbstractFindUsagesTest.java +++ b/idea/tests/org/jetbrains/kotlin/findUsages/AbstractFindUsagesTest.java @@ -478,6 +478,16 @@ public abstract class AbstractFindUsagesTest extends KotlinLightCodeInsightFixtu if (log != null) { KotlinTestUtils.assertEqualsToFile(new File(rootPath, prefix + "log"), log); + + // if log is empty then compare results with plain search + try { + ExpressionsOfTypeProcessor.Companion.setMode(ExpressionsOfTypeProcessor.Mode.ALWAYS_PLAIN); + + findUsagesAndCheckResults(mainFileText, prefix, rootPath, caretElement, options); + } + finally { + ExpressionsOfTypeProcessor.Companion.setMode(ExpressionsOfTypeProcessor.Mode.ALWAYS_SMART); + } } } diff --git a/idea/tests/org/jetbrains/kotlin/search/KotlinReferencesSearchTest.kt b/idea/tests/org/jetbrains/kotlin/search/KotlinReferencesSearchTest.kt index cbace223064..9a5ad92ceb7 100644 --- a/idea/tests/org/jetbrains/kotlin/search/KotlinReferencesSearchTest.kt +++ b/idea/tests/org/jetbrains/kotlin/search/KotlinReferencesSearchTest.kt @@ -22,6 +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.references.KtInvokeFunctionReference import org.jetbrains.kotlin.idea.search.usagesSearch.ExpressionsOfTypeProcessor import org.jetbrains.kotlin.idea.test.PluginTestCaseBase import org.jetbrains.kotlin.psi.KtFunction @@ -58,6 +59,13 @@ class KotlinReferencesSearchTest(): AbstractSearcherTest() { Assert.assertTrue(refs[1] is KtDestructuringDeclarationReference) } + fun testInvokeFun() { + val refs = doTest() + Assert.assertEquals(2, refs.size) + Assert.assertEquals("invoke", refs[0].canonicalText) + Assert.assertTrue(refs[1] is KtInvokeFunctionReference) + } + // workaround for KT-9788 AssertionError from backand when we read field from inline function private val myFixtureProxy: JavaCodeInsightTestFixture get() = myFixture