Implemented fast search for invoke operator calls

#KT-13643 Fixed
This commit is contained in:
Valentin Kipyatkov
2016-08-31 22:41:47 +03:00
parent e1c7d07189
commit e3b37f9219
13 changed files with 197 additions and 12 deletions
@@ -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<PsiReference, ReferencesSearc
}
}
}
//TODO: Java invoke's
if (kotlinOptions.searchInvokeOperator && element is KtFunction && name == OperatorNameConventions.INVOKE.asString()) {
findInvokeOperatorUsages(element, effectiveSearchScope, consumer)
}
}
private fun searchNamedArguments(parameter: KtParameter, queryParameters: ReferencesSearch.SearchParameters) {
@@ -51,6 +51,7 @@ 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 org.jetbrains.kotlin.util.OperatorNameConventions
import java.util.*
//TODO: check if smart search is too expensive
@@ -592,7 +593,7 @@ class ExpressionsOfTypeProcessor(
private fun PsiElement.isOperatorExpensiveToSearch(): Boolean {
when (this) {
is KtFunction -> {
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
}
@@ -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<PsiReference>
) {
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<PsiReference>
) {
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<PsiReference>) {
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<PsiReference>) {
if (scope is LocalSearchScope) {
for (element in scope.scope) {
val stop = element.anyDescendantOfType<KtCallExpression> { !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<KtCallExpression> {
!processCallExpression(it, ktDeclaration, consumer)
}
if (stop) return@iterateContent false
}
}
true
}
}
}
private fun processCallExpression(callExpression: KtCallExpression, targetDeclaration: KtDeclaration, consumer: Processor<PsiReference>): Boolean {
val reference = callExpression.references.firstIsInstance<KtInvokeFunctionReference>()
if (reference.isReferenceTo(targetDeclaration)) {
return consumer.process(reference)
}
else {
return true
}
}