Initial implementation of faster component function usage search

This commit is contained in:
Valentin Kipyatkov
2016-08-23 22:52:23 +03:00
parent 77bb9b8092
commit 1674beb64b
32 changed files with 609 additions and 121 deletions
@@ -16,10 +16,9 @@
package org.jetbrains.kotlin.psi;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.Nullable;
public interface KtWithExpressionInitializer extends PsiElement {
public interface KtWithExpressionInitializer extends KtDeclaration {
@Nullable
KtExpression getInitializer();
@@ -20,8 +20,11 @@ import org.jetbrains.kotlin.name.Name
private val COMPONENT_FUNCTION_NAME_PREFIX = "component"
fun isComponentLike(name: Name): Boolean {
if (!name.asString().startsWith(COMPONENT_FUNCTION_NAME_PREFIX)) return false
fun isComponentLike(name: Name): Boolean
= isComponentLike(name.asString())
fun isComponentLike(name: String): Boolean {
if (!name.startsWith(COMPONENT_FUNCTION_NAME_PREFIX)) return false
try {
getComponentIndex(name)
@@ -33,8 +36,8 @@ fun isComponentLike(name: Name): Boolean {
return true
}
fun getComponentIndex(componentName: Name): Int =
componentName.asString().substring(COMPONENT_FUNCTION_NAME_PREFIX.length).toInt()
fun getComponentIndex(componentName: String): Int
= componentName.substring(COMPONENT_FUNCTION_NAME_PREFIX.length).toInt()
fun createComponentName(index: Int): Name =
Name.identifier(COMPONENT_FUNCTION_NAME_PREFIX + index)
fun createComponentName(index: Int): Name
= Name.identifier(COMPONENT_FUNCTION_NAME_PREFIX + index)
@@ -38,6 +38,7 @@ 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.stubindex.KotlinSourceFilterScope
@@ -45,6 +46,7 @@ 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
data class KotlinReferencesSearchOptions(val acceptCallableOverrides: Boolean = false,
val acceptOverloads: Boolean = false,
@@ -96,8 +98,8 @@ class KotlinReferencesSearcher : QueryExecutorBase<PsiReference, ReferencesSearc
val resultProcessor = KotlinRequestResultProcessor(unwrappedElement, filter = refFilter, options = kotlinOptions)
val name = runReadAction { unwrappedElement.name }
if (kotlinOptions.anyEnabled()) {
val name = runReadAction { unwrappedElement.name }
if (name != null) {
queryParameters.optimizer.searchWord(name, effectiveSearchScope, UsageSearchContext.IN_CODE, true, unwrappedElement,
resultProcessor)
@@ -114,7 +116,32 @@ class KotlinReferencesSearcher : QueryExecutorBase<PsiReference, ReferencesSearc
}
if (!(unwrappedElement is KtElement && isOnlyKotlinSearch(effectiveSearchScope))) {
searchLightElements(queryParameters, element)
searchLightElements(queryParameters, element, consumer)
}
if (kotlinOptions.searchForComponentConventions) {
when (element) {
is KtFunction -> {
if (name != null && isComponentLike(name)) {
findDestructuringDeclarationUsages(element, effectiveSearchScope, consumer, queryParameters.optimizer)
}
}
is KtParameter -> {
val componentFunctionDescriptor = element.dataClassComponentFunction()
if (componentFunctionDescriptor != null) {
val containingClass = element.getStrictParentOfType<KtClassOrObject>()?.toLightClass()
searchDataClassComponentUsages(queryParameters, containingClass, componentFunctionDescriptor, consumer)
}
}
is KtLightParameter -> {
val componentFunctionDescriptor = element.kotlinOrigin?.dataClassComponentFunction()
if (componentFunctionDescriptor != null) {
searchDataClassComponentUsages(queryParameters, element.method.containingClass, componentFunctionDescriptor, consumer)
}
}
}
}
}
@@ -209,16 +236,19 @@ class KotlinReferencesSearcher : QueryExecutorBase<PsiReference, ReferencesSearc
private fun searchDataClassComponentUsages(queryParameters: ReferencesSearch.SearchParameters,
containingClass: PsiClass?,
componentFunctionDescriptor: FunctionDescriptor) {
componentFunctionDescriptor: FunctionDescriptor,
consumer: Processor<PsiReference>
) {
val componentFunction = containingClass?.methods?.find {
it.name == componentFunctionDescriptor.name.asString() && it.parameterList.parametersCount == 0
}
if (componentFunction != null) {
searchNamedElement(queryParameters, componentFunction)
findDestructuringDeclarationUsages(componentFunction, queryParameters.effectiveSearchScope, consumer, queryParameters.optimizer)
}
}
private fun searchLightElements(queryParameters: ReferencesSearch.SearchParameters, element: PsiElement) {
private fun searchLightElements(queryParameters: ReferencesSearch.SearchParameters, element: PsiElement, consumer: Processor<PsiReference>) {
when (element) {
is KtClassOrObject -> processKtClassOrObject(element, queryParameters)
is KtNamedFunction, is KtSecondaryConstructor -> {
@@ -242,14 +272,6 @@ class KotlinReferencesSearcher : QueryExecutorBase<PsiReference, ReferencesSearc
is KtParameter -> {
searchPropertyMethods(queryParameters, element)
runReadAction {
val componentFunctionDescriptor = element.dataClassComponentFunction()
if (componentFunctionDescriptor != null) {
val containingClass = element.getStrictParentOfType<KtClassOrObject>()?.toLightClass()
searchDataClassComponentUsages(queryParameters, containingClass, componentFunctionDescriptor)
}
}
}
is KtLightMethod -> {
@@ -269,12 +291,6 @@ class KotlinReferencesSearcher : QueryExecutorBase<PsiReference, ReferencesSearc
is KtLightParameter -> {
val origin = element.kotlinOrigin ?: return
runReadAction {
val componentFunctionDescriptor = origin.dataClassComponentFunction()
if (componentFunctionDescriptor != null) {
searchDataClassComponentUsages(queryParameters, element.method.containingClass, componentFunctionDescriptor)
}
}
searchPropertyMethods(queryParameters, origin)
}
}
@@ -17,12 +17,14 @@
package org.jetbrains.kotlin.idea.search.usagesSearch
import com.google.common.collect.ImmutableSet
import org.jetbrains.kotlin.idea.references.*
import org.jetbrains.kotlin.idea.references.KtArrayAccessReference
import org.jetbrains.kotlin.idea.references.KtForLoopInReference
import org.jetbrains.kotlin.idea.references.KtPropertyDelegationMethodsReference
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.lexer.KtToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.DelegatedPropertyResolver
import org.jetbrains.kotlin.resolve.dataClassUtils.isComponentLike
import org.jetbrains.kotlin.types.expressions.OperatorConventions.*
import org.jetbrains.kotlin.util.OperatorNameConventions
@@ -57,8 +59,6 @@ fun Name.getOperationSymbolsToSearch(): Pair<Set<KtToken>, Class<*>?> {
DelegatedPropertyResolver.PROPERTY_DELEGATED_FUNCTION_NAME -> return setOf(KtTokens.BY_KEYWORD) to KtPropertyDelegationMethodsReference::class.java
}
if (isComponentLike(this)) return setOf(KtTokens.LPAR) to KtDestructuringDeclarationReference::class.java
val unaryOp = UNARY_OPERATION_NAMES_WITH_DEPRECATED_INVERTED[this]
if (unaryOp != null) return setOf(unaryOp) to KtSimpleNameReference::class.java
@@ -0,0 +1,332 @@
/*
* 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.lang.java.JavaLanguage
import com.intellij.psi.*
import com.intellij.psi.search.*
import com.intellij.psi.search.searches.ClassInheritorsSearch
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.util.Processor
import org.jetbrains.kotlin.KtNodeTypes
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.resolveToDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.references.KtDestructuringDeclarationReference
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinRequestResultProcessor
import org.jetbrains.kotlin.idea.search.restrictToKotlinSources
import org.jetbrains.kotlin.idea.util.FuzzyType
import org.jetbrains.kotlin.idea.util.fuzzyExtensionReceiverType
import org.jetbrains.kotlin.idea.util.toFuzzyType
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
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.*
//TODO: compiled code
//TODO: check if it's too expensive
fun findDestructuringDeclarationUsages(
componentFunction: PsiMethod,
scope: SearchScope,
consumer: Processor<PsiReference>,
optimizer: SearchRequestCollector
) {
if (componentFunction !is KtLightMethod) return //TODO
val ktDeclarationTarget = componentFunction.kotlinOrigin as? KtDeclaration ?: return //TODO?
findDestructuringDeclarationUsages(ktDeclarationTarget, scope, consumer, optimizer)
}
fun findDestructuringDeclarationUsages(
ktDeclaration: KtDeclaration,
scope: SearchScope,
consumer: Processor<PsiReference>,
optimizer: SearchRequestCollector
) {
// for local scope it's faster to use plain search
if (scope is LocalSearchScope) {
val unwrappedElement = ktDeclaration.namedUnwrappedElement ?: return
val resultProcessor = KotlinRequestResultProcessor(unwrappedElement,
filter = { ref -> ref is KtDestructuringDeclarationReference })
optimizer.searchWord("(", scope.restrictToKotlinSources(), UsageSearchContext.IN_CODE, true, unwrappedElement, resultProcessor)
return
}
val descriptor = ktDeclaration.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)
}
Processor(dataType, ktDeclaration, scope, consumer).run()
}
private class Processor(
private val dataType: FuzzyType,
private val target: KtDeclaration,
private val searchScope: SearchScope,
private val consumer: Processor<PsiReference>
) {
private val project = target.project
private val declarationsToSearch = ArrayList<PsiElement>()
private val declarationsToSearchSet = HashSet<PsiElement>()
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
}
val parameters = ClassInheritorsSearch.SearchParameters(psiClass, GlobalSearchScope.allScope(project), true, true, false)
val classesToSearch = listOf(psiClass) + ClassInheritorsSearch.search(parameters).findAll()
for (classToSearch in classesToSearch) {
ReferencesSearch.search(classToSearch).forEach(Processor { reference ->
if (!processDataClassUsage(reference)) {
//TODO
val element = reference.element
val document = PsiDocumentManager.getInstance(project).getDocument(element.containingFile)
val lineAndCol = DiagnosticUtils.offsetToLineAndColumn(document, element.startOffset)
TODO("Unsupported reference: '${element.text}' in ${element.containingFile.name} line ${lineAndCol.line} column ${lineAndCol.column}")
}
else {
true
}
})
}
// we use index instead of iterator because elements are added during iteration
var index = 0
while (index < declarationsToSearch.size) {
val declaration = declarationsToSearch[index++]
processDeclarationOfTypeWithDataClass(declaration)
}
}
//TODO: check if it's operator (too expensive)
private fun addDeclarationToProcess(declaration: PsiElement) {
if (declarationsToSearchSet.add(declaration)) {
declarationsToSearch.add(declaration)
}
}
/**
* 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)
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
//TODO: doc-comments
when (element) {
is KtReferenceExpression -> {
val parent = element.parent
when (parent) {
is KtUserType -> {
val typeRef = parent.parents.lastOrNull { it is KtTypeReference }
val typeRefParent = typeRef?.parent
when (typeRefParent) {
is KtCallableDeclaration -> {
when (typeRef) {
typeRefParent.typeReference -> {
addDeclarationToProcess(typeRefParent)
return true
}
typeRefParent.receiverTypeReference -> {
//TODO: search usages inside extensions and member functions of our class & derived
return true
}
}
}
is KtTypeProjection -> {
val callExpression = (typeRefParent.parent as? KtTypeArgumentList)?.parent as? KtCallExpression
if (callExpression != null) {
processSuspiciousExpression(callExpression)
return true
}
}
is KtConstructorCalleeExpression -> {
if (typeRefParent.parent is KtSuperTypeCallEntry) {
// usage in super type list - just ignore, inheritors are processed above
return true
}
}
}
}
is KtCallExpression -> {
if (element == parent.calleeExpression) {
processSuspiciousExpression(parent)
return true
}
}
}
if (element.getStrictParentOfType<KtImportDirective>() != null) return true // ignore usage in import
}
}
return false // unsupported type of reference
}
private fun processJavaDataClassUsage(element: PsiElement): Boolean {
if (element !is PsiJavaCodeReferenceElement) return true // meaningless reference from Java
ParentsLoop@
for (parent in element.parents) {
when (parent) {
is PsiCodeBlock,
is PsiExpression,
is PsiImportStatement,
is PsiParameter ->
break@ParentsLoop // ignore local usages, usages in imports and in parameter types
is PsiMethod, is PsiField -> {
if (!(parent as PsiModifierListOwner).isPrivateOrLocal()) {
addDeclarationToProcess(parent)
}
break@ParentsLoop
}
}
}
return true
}
/**
* Process declaration whose type is our data class (or data class used anywhere inside that type)
*/
private fun processDeclarationOfTypeWithDataClass(declaration: PsiElement) {
// 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 declarationUsageScope = GlobalSearchScope.projectScope(declaration.project).restrictToKotlinSources()
ReferencesSearch.search(declaration, declarationUsageScope).forEach { reference ->
(reference.element as? KtReferenceExpression)?.let { processSuspiciousExpression(it) }
}
}
/**
* Process expression which may have type of our data class (or data class used anywhere inside that type)
*/
private fun processSuspiciousExpression(expression: KtExpression) {
ParentsLoop@
for (container in expression.parentsWithSelf) {
if (container !is KtExpression) continue
val parent = container.parent
when (parent) {
is KtDestructuringDeclaration -> {
processSuspiciousDeclaration(parent)
break@ParentsLoop
}
is KtWithExpressionInitializer -> {
if (container != parent.initializer) continue@ParentsLoop
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)
}
}
}
//TODO: entry of data type
}
}
}
/**
* 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)) {
val declarationReference = declaration.references.firstIsInstance<KtDestructuringDeclarationReference>()
if (declarationReference.isReferenceTo(target)) {
consumer.process(declarationReference)
}
}
}
else {
if (!isImplicitlyTyped(declaration)) return
val descriptor = declaration.resolveToDescriptorIfAny() as? CallableDescriptor ?: return
val type = descriptor.returnType
if (type != null && type.containsTypeOrDerivedInside(dataType)) {
addDeclarationToProcess(declaration)
}
}
}
private fun PsiModifierListOwner.isPrivateOrLocal(): Boolean {
return hasModifierProperty(PsiModifier.PRIVATE) || parents.any { it is PsiCodeBlock }
}
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
}
}
}
@@ -22,10 +22,8 @@ import org.jetbrains.kotlin.asJava.LightClassUtil.PropertyAccessorsPsiMethods
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.references.KtDestructuringDeclarationReference
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions
import org.jetbrains.kotlin.lexer.KtSingleValueToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
@@ -69,14 +67,7 @@ fun PsiNamedElement.getSpecialNamesToSearch(options: KotlinReferencesSearchOptio
val name = name
return when {
name == null || !Name.isValidIdentifier(name) -> Collections.emptyList<String>() to null
this is KtParameter -> {
if (!options.searchForComponentConventions) return Collections.emptyList<String>() to null
val componentFunctionName = this.dataClassComponentFunction()?.name
if (componentFunctionName == null) return Collections.emptyList<String>() to null
return listOf(componentFunctionName.asString(), KtTokens.LPAR.value) to KtDestructuringDeclarationReference::class.java
}
else -> {
val operationSymbolsToSearch = Name.identifier(name).getOperationSymbolsToSearch()
operationSymbolsToSearch.first.map { (it as KtSingleValueToken).value } to operationSymbolsToSearch.second
@@ -46,6 +46,7 @@ import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.asJava.toLightMethods
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.annotations.Annotated
import org.jetbrains.kotlin.idea.caches.resolve.analyze
@@ -158,10 +159,10 @@ class UnusedSymbolInspection : AbstractKotlinInspection() {
if (declaration.hasModifier(KtTokens.OVERRIDE_KEYWORD)) return
if (declaration is KtProperty && declaration.isLocal) return
if (declaration is KtParameter && (declaration.getParent()?.parent !is KtPrimaryConstructor || !declaration.hasValOrVar())) return
if (declaration is KtNamedFunction && isConventionalName(declaration)) return
// More expensive, resolve-based checks
declaration.resolveToDescriptorIfAny() ?: return
val descriptor = declaration.resolveToDescriptorIfAny() ?: return
if (descriptor is FunctionDescriptor && descriptor.isOperator) return
if (isEntryPoint(declaration)) return
if (declaration is KtProperty && declaration.isSerializationImplicitlyUsedField()) return
if (declaration is KtNamedFunction && declaration.isSerializationImplicitlyUsedMethod()) return
@@ -205,11 +206,6 @@ class UnusedSymbolInspection : AbstractKotlinInspection() {
return hasTextUsages
}
private fun isConventionalName(namedDeclaration: KtNamedDeclaration): Boolean {
val name = namedDeclaration.nameAsName
return name!!.getOperationSymbolsToSearch().first.isNotEmpty() || name == OperatorNameConventions.INVOKE
}
private fun hasNonTrivialUsages(declaration: KtNamedDeclaration): Boolean {
val psiSearchHelper = PsiSearchHelper.SERVICE.getInstance(declaration.project)
@@ -199,7 +199,7 @@ class ChangeFunctionReturnTypeFix(element: KtFunction, type: KotlinType) : Kotli
companion object {
fun getDestructuringDeclarationEntryThatTypeMismatchComponentFunction(diagnostic: Diagnostic): KtDestructuringDeclarationEntry {
val componentName = COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH.cast(diagnostic).a
val componentIndex = getComponentIndex(componentName)
val componentIndex = getComponentIndex(componentName.asString())
val multiDeclaration = QuickFixUtil.getParentElementOfType(diagnostic, KtDestructuringDeclaration::class.java) ?: error("COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH reported on expression that is not within any multi declaration")
return multiDeclaration.entries[componentIndex - 1]
}
@@ -22,8 +22,8 @@ import org.jetbrains.kotlin.idea.core.quickfix.QuickFixUtil
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.CallableInfo
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.FunctionInfo
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.TypeInfo
import org.jetbrains.kotlin.psi.KtForExpression
import org.jetbrains.kotlin.psi.KtDestructuringDeclaration
import org.jetbrains.kotlin.psi.KtForExpression
import org.jetbrains.kotlin.resolve.dataClassUtils.getComponentIndex
import org.jetbrains.kotlin.resolve.dataClassUtils.isComponentLike
import org.jetbrains.kotlin.types.Variance
@@ -40,7 +40,7 @@ object CreateComponentFunctionActionFactory : CreateCallableMemberFromUsageFacto
val name = diagnosticWithParameters.a
if (!isComponentLike(name)) return null
val componentNumber = getComponentIndex(name) - 1
val componentNumber = getComponentIndex(name.asString()) - 1
val ownerType = element.initializer?.let { TypeInfo(it, Variance.IN_VARIANCE) }
?: TypeInfo(diagnosticWithParameters.b, Variance.IN_VARIANCE)
@@ -22,26 +22,35 @@ import com.intellij.psi.search.UsageSearchContext
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.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
class KotlinConventionMethodReferencesSearcher() : QueryExecutorBase<PsiReference, MethodReferencesSearch.SearchParameters>(true) {
override fun processQuery(queryParameters: MethodReferencesSearch.SearchParameters, consumer: Processor<PsiReference>) {
val method = queryParameters.method
val name = runReadAction { method.name }
if (!Name.isValidIdentifier(name)) return
val operationSymbolsToSearch = Name.identifier(name).getOperationSymbolsToSearch()
val wordsToSearch = operationSymbolsToSearch.first.map { (it as KtSingleValueToken).value }
if (wordsToSearch.isEmpty()) return
val resultProcessor = KotlinRequestResultProcessor(method,
filter = { ref -> ref.javaClass == operationSymbolsToSearch.second })
val identifier = Name.identifier(name)
wordsToSearch.forEach { word ->
queryParameters.optimizer.searchWord(word, queryParameters.effectiveSearchScope.restrictToKotlinSources(),
UsageSearchContext.IN_CODE, true, method,
resultProcessor)
if (isComponentLike(identifier)) {
findDestructuringDeclarationUsages(method, queryParameters.effectiveSearchScope, consumer, queryParameters.optimizer)
}
else {
val operationSymbolsToSearch = identifier.getOperationSymbolsToSearch()
val wordsToSearch = operationSymbolsToSearch.first.map { (it as KtSingleValueToken).value }
if (wordsToSearch.isEmpty()) return
val resultProcessor = KotlinRequestResultProcessor(method,
filter = { ref -> ref.javaClass == operationSymbolsToSearch.second })
wordsToSearch.forEach { word ->
queryParameters.optimizer.searchWord(word, queryParameters.effectiveSearchScope.restrictToKotlinSources(),
UsageSearchContext.IN_CODE, true, method,
resultProcessor)
}
}
}
}
@@ -1,4 +0,0 @@
fun test() {
for ((x, y, z) in arrayOf<A>()) {
}
}
@@ -1,4 +0,0 @@
[componentFunctions.0.kt] Function call 9 a.component1()
[componentFunctions.0.kt] Value read 10 val (x, y, z) = a
[componentFunctions.0.kt] Value read 8 a.n
[componentFunctions.1.kt] Value read 2 for ((x, y, z) in arrayOf<A>()) {
@@ -0,0 +1,13 @@
// PSI_ELEMENT: org.jetbrains.kotlin.psi.KtFunction
// OPTIONS: usages
class X<T>
operator fun <T> X<T>.<caret>component1(): Int = 0
operator fun <T> X<T>.component2(): Int = 0
fun f() = X<String>()
fun test() {
val (x, y) = f()
}
@@ -0,0 +1 @@
Value read 12 val (x, y) = f()
@@ -0,0 +1,18 @@
// PSI_ELEMENT: org.jetbrains.kotlin.psi.KtFunction
// OPTIONS: usages
class X<T>
operator fun X<Int>.<caret>component1(): Int = 0
operator fun X<Int>.component2(): Int = 0
operator fun X<String>.component1(): Int = 0
operator fun X<String>.component2(): Int = 0
fun f() = X<Int>()
fun g() = X<String>()
fun test() {
val (x1, y1) = f()
val (x2, y2) = g()
}
@@ -0,0 +1 @@
Value read 16 val (x1, y1) = f()
@@ -0,0 +1,11 @@
import java.util.List;
import pack.A;
class JavaClass {
public List<A> getA() {
A a = new A(1, "", "");
return Collections.singletonList(a);
}
public void takeA(A a){}
}
@@ -1,11 +1,5 @@
// PSI_ELEMENT: org.jetbrains.kotlin.psi.KtParameter
// OPTIONS: usages
package pack
data class A(val <caret>n: Int, val s: String, val o: Any)
fun test() {
val a = A(1, "2", Any())
a.n
a.component1()
val (x, y, z) = a
}
@@ -0,0 +1,10 @@
import pack.A
fun test() {
for ((x, y, z) in arrayOf<A>()) {
}
for (a in listOf<A>()) {
val (x, y) = a
}
}
@@ -0,0 +1,22 @@
import pack.A
fun test(javaClass: JavaClass) {
val a = A(1, "2", Any())
a.n
a.component1()
val (x, y, z) = a
val (x1, y1, z1) = f()
val (x2, y2, z2) = g()
val (x3, y3, z3) = h()
val (x4, y4, z4) = listOfA()[0]
val (x5, y5, z5) = javaClass.getA()[0]
}
fun f(): A = TODO()
fun g() = A()
fun h() = g()
fun listOfA() = listOf<A>(A(1, "", ""))
@@ -0,0 +1,10 @@
[dataClass.1.kt] Value read 4 for ((x, y, z) in arrayOf<A>()) {
[dataClass.1.kt] Value read 8 val (x, y) = a
[dataClass.2.kt] Function call 6 a.component1()
[dataClass.2.kt] Value read 10 val (x2, y2, z2) = g()
[dataClass.2.kt] Value read 11 val (x3, y3, z3) = h()
[dataClass.2.kt] Value read 13 val (x4, y4, z4) = listOfA()[0]
[dataClass.2.kt] Value read 15 val (x5, y5, z5) = javaClass.getA()[0]
[dataClass.2.kt] Value read 5 a.n
[dataClass.2.kt] Value read 8 val (x, y, z) = a
[dataClass.2.kt] Value read 9 val (x1, y1, z1) = f()
@@ -0,0 +1,14 @@
// PSI_ELEMENT: org.jetbrains.kotlin.psi.KtFunction
// OPTIONS: usages
class X
operator fun X.component1(): Int = 0
operator fun X.<caret>component2(): Int = 1
fun f() = X()
fun test() {
val (x, y) = f()
}
@@ -0,0 +1 @@
Value read 13 val (x, y) = f()
@@ -0,0 +1,20 @@
// PSI_ELEMENT: org.jetbrains.kotlin.psi.KtFunction
// OPTIONS: usages
open class X {
operator fun <caret>component1(): Int = 0
operator fun component2(): Int = 1
}
open class Y : X()
open class Z : Y()
fun f() = X()
fun g() = Y()
fun h() = Z()
fun test() {
val (x, y) = f()
val (x1, y1) = g()
val (x2, y2) = h()
}
@@ -0,0 +1,3 @@
Value read 17 val (x, y) = f()
Value read 18 val (x1, y1) = g()
Value read 19 val (x2, y2) = h()
@@ -1,34 +0,0 @@
// all these are unused, but have conventional names, so they won't be marked as unused
fun inc() {
}
fun dec() {
}
fun component1() {
}
fun component100() {
}
fun equals() {
}
fun invoke() {
}
fun iterator() {
}
fun compareTo() {
}
fun get() {
}
fun set() {
}
fun plusAssign() {
}
@@ -0,0 +1,9 @@
// all these are unused but are operators
operator fun String.inc() = ""
operator fun String.dec() = ""
operator fun String.component1() = 0
operator fun String.component100() = 0
+10
View File
@@ -0,0 +1,10 @@
class A
operator fun A.<caret>component1(): Int = 0
operator fun A.component2(): Int = 1
fun test() {
val a = A()
a.component1()
val (x, y) = a
}
@@ -84,18 +84,6 @@ public class FindUsagesTestGenerated extends AbstractFindUsagesTest {
doTest(fileName);
}
@TestMetadata("componentFunctions.0.kt")
public void testComponentFunctions() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/conventions/componentFunctions.0.kt");
doTest(fileName);
}
@TestMetadata("componentFunctionsByRef.0.kt")
public void testComponentFunctionsByRef() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/conventions/componentFunctionsByRef.0.kt");
doTest(fileName);
}
@TestMetadata("contains.0.kt")
public void testContains() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/conventions/contains.0.kt");
@@ -173,6 +161,51 @@ public class FindUsagesTestGenerated extends AbstractFindUsagesTest {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/conventions/unaryMinus.0.kt");
doTest(fileName);
}
@TestMetadata("idea/testData/findUsages/kotlin/conventions/components")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Components extends AbstractFindUsagesTest {
public void testAllFilesPresentInComponents() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/findUsages/kotlin/conventions/components"), Pattern.compile("^(.+)\\.0\\.kt$"), true);
}
@TestMetadata("componentFunForGenericType1.0.kt")
public void testComponentFunForGenericType1() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/conventions/components/componentFunForGenericType1.0.kt");
doTest(fileName);
}
@TestMetadata("componentFunForGenericType2.0.kt")
public void testComponentFunForGenericType2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/conventions/components/componentFunForGenericType2.0.kt");
doTest(fileName);
}
@TestMetadata("dataClass.0.kt")
public void testDataClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/conventions/components/dataClass.0.kt");
doTest(fileName);
}
@TestMetadata("dataClassComponentByRef.0.kt")
public void testDataClassComponentByRef() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/conventions/components/dataClassComponentByRef.0.kt");
doTest(fileName);
}
@TestMetadata("extensionComponentFun.0.kt")
public void testExtensionComponentFun() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/conventions/components/extensionComponentFun.0.kt");
doTest(fileName);
}
@TestMetadata("memberComponentFun.0.kt")
public void testMemberComponentFun() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/conventions/components/memberComponentFun.0.kt");
doTest(fileName);
}
}
}
@TestMetadata("idea/testData/findUsages/kotlin/findClassUsages")
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.search
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
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
@@ -49,12 +50,25 @@ class KotlinReferencesSearchTest(): AbstractSearcherTest() {
Assert.assertTrue(refs[2] is KtDestructuringDeclarationReference)
}
fun testComponentFun() {
val refs = doTest<KtFunction>()
Assert.assertEquals(2, refs.size)
Assert.assertEquals("component1", refs[0].canonicalText)
Assert.assertTrue(refs[1] is KtDestructuringDeclarationReference)
}
// workaround for KT-9788 AssertionError from backand when we read field from inline function
private val myFixtureProxy: JavaCodeInsightTestFixture get() = myFixture
private inline fun <reified T: PsiElement> doTest(): List<PsiReference> {
myFixtureProxy.configureByFile(fileName)
val psiFile = myFixtureProxy.configureByFile(fileName)
val func = myFixtureProxy.elementAtCaret.getParentOfType<T>(false)!!
return ReferencesSearch.search(func).findAll().sortedBy { it.element.textRange.startOffset }
val refs = ReferencesSearch.search(func).findAll().sortedBy { it.element.textRange.startOffset }
// check that local references search gives the same result
val localRefs = ReferencesSearch.search(func, LocalSearchScope(psiFile)).findAll()
Assert.assertEquals(refs.size, localRefs.size)
return refs
}
}