diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtWithExpressionInitializer.java b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtWithExpressionInitializer.java index 24f6c66e10c..007dcc34d37 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtWithExpressionInitializer.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtWithExpressionInitializer.java @@ -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(); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DataClassUtils.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DataClassUtils.kt index f39efaf79a4..479702577de 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DataClassUtils.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DataClassUtils.kt @@ -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) 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 697b5f3387a..c3c418c4f7a 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 @@ -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 { + if (name != null && isComponentLike(name)) { + findDestructuringDeclarationUsages(element, effectiveSearchScope, consumer, queryParameters.optimizer) + } + } + + is KtParameter -> { + val componentFunctionDescriptor = element.dataClassComponentFunction() + if (componentFunctionDescriptor != null) { + val containingClass = element.getStrictParentOfType()?.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 + ) { 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) { when (element) { is KtClassOrObject -> processKtClassOrObject(element, queryParameters) is KtNamedFunction, is KtSecondaryConstructor -> { @@ -242,14 +272,6 @@ class KotlinReferencesSearcher : QueryExecutorBase { searchPropertyMethods(queryParameters, element) - runReadAction { - - val componentFunctionDescriptor = element.dataClassComponentFunction() - if (componentFunctionDescriptor != null) { - val containingClass = element.getStrictParentOfType()?.toLightClass() - searchDataClassComponentUsages(queryParameters, containingClass, componentFunctionDescriptor) - } - } } is KtLightMethod -> { @@ -269,12 +291,6 @@ class KotlinReferencesSearcher : QueryExecutorBase { val origin = element.kotlinOrigin ?: return - runReadAction { - val componentFunctionDescriptor = origin.dataClassComponentFunction() - if (componentFunctionDescriptor != null) { - searchDataClassComponentUsages(queryParameters, element.method.containingClass, componentFunctionDescriptor) - } - } searchPropertyMethods(queryParameters, origin) } } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/conventions.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/conventions.kt index 87eee3c31a4..cc2d48d9708 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/conventions.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/conventions.kt @@ -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, 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 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/destructuringDeclarationUsages.kt new file mode 100644 index 00000000000..9421de94a79 --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/destructuringDeclarationUsages.kt @@ -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, + 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, + 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 +) { + private val project = target.project + private val declarationsToSearch = ArrayList() + private val declarationsToSearchSet = HashSet() + + 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() != 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() + 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 + } + } +} diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/searchHelpers.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/searchHelpers.kt index 505593ced70..d95c0c00f84 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/searchHelpers.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/searchHelpers.kt @@ -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() to null - this is KtParameter -> { - if (!options.searchForComponentConventions) return Collections.emptyList() to null - val componentFunctionName = this.dataClassComponentFunction()?.name - if (componentFunctionName == null) return Collections.emptyList() 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 diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt index a7fdb839926..bf6b03aa39c 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt @@ -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) diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeFunctionReturnTypeFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeFunctionReturnTypeFix.kt index f94bed187be..dd2d8f081eb 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeFunctionReturnTypeFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeFunctionReturnTypeFix.kt @@ -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] } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateComponentFunctionActionFactory.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateComponentFunctionActionFactory.kt index f05763fb689..6212dff0927 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateComponentFunctionActionFactory.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateComponentFunctionActionFactory.kt @@ -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) 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 927574c8cb4..8600bdf960d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinConventionMethodReferencesSearcher.kt +++ b/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinConventionMethodReferencesSearcher.kt @@ -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(true) { override fun processQuery(queryParameters: MethodReferencesSearch.SearchParameters, consumer: Processor) { 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) + } } } } diff --git a/idea/testData/findUsages/kotlin/conventions/componentFunctions.1.kt b/idea/testData/findUsages/kotlin/conventions/componentFunctions.1.kt deleted file mode 100644 index 97515185643..00000000000 --- a/idea/testData/findUsages/kotlin/conventions/componentFunctions.1.kt +++ /dev/null @@ -1,4 +0,0 @@ -fun test() { - for ((x, y, z) in arrayOf()) { - } -} diff --git a/idea/testData/findUsages/kotlin/conventions/componentFunctions.results.txt b/idea/testData/findUsages/kotlin/conventions/componentFunctions.results.txt deleted file mode 100644 index 166e20e2b55..00000000000 --- a/idea/testData/findUsages/kotlin/conventions/componentFunctions.results.txt +++ /dev/null @@ -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()) { diff --git a/idea/testData/findUsages/kotlin/conventions/components/componentFunForGenericType1.0.kt b/idea/testData/findUsages/kotlin/conventions/components/componentFunForGenericType1.0.kt new file mode 100644 index 00000000000..a33252e4926 --- /dev/null +++ b/idea/testData/findUsages/kotlin/conventions/components/componentFunForGenericType1.0.kt @@ -0,0 +1,13 @@ +// PSI_ELEMENT: org.jetbrains.kotlin.psi.KtFunction +// OPTIONS: usages + +class X + +operator fun X.component1(): Int = 0 +operator fun X.component2(): Int = 0 + +fun f() = X() + +fun test() { + val (x, y) = f() +} diff --git a/idea/testData/findUsages/kotlin/conventions/components/componentFunForGenericType1.results.txt b/idea/testData/findUsages/kotlin/conventions/components/componentFunForGenericType1.results.txt new file mode 100644 index 00000000000..94e74e9df21 --- /dev/null +++ b/idea/testData/findUsages/kotlin/conventions/components/componentFunForGenericType1.results.txt @@ -0,0 +1 @@ +Value read 12 val (x, y) = f() diff --git a/idea/testData/findUsages/kotlin/conventions/components/componentFunForGenericType2.0.kt b/idea/testData/findUsages/kotlin/conventions/components/componentFunForGenericType2.0.kt new file mode 100644 index 00000000000..0ae923bf43c --- /dev/null +++ b/idea/testData/findUsages/kotlin/conventions/components/componentFunForGenericType2.0.kt @@ -0,0 +1,18 @@ +// PSI_ELEMENT: org.jetbrains.kotlin.psi.KtFunction +// OPTIONS: usages + +class X + +operator fun X.component1(): Int = 0 +operator fun X.component2(): Int = 0 + +operator fun X.component1(): Int = 0 +operator fun X.component2(): Int = 0 + +fun f() = X() +fun g() = X() + +fun test() { + val (x1, y1) = f() + val (x2, y2) = g() +} diff --git a/idea/testData/findUsages/kotlin/conventions/components/componentFunForGenericType2.results.txt b/idea/testData/findUsages/kotlin/conventions/components/componentFunForGenericType2.results.txt new file mode 100644 index 00000000000..9302456b5c6 --- /dev/null +++ b/idea/testData/findUsages/kotlin/conventions/components/componentFunForGenericType2.results.txt @@ -0,0 +1 @@ +Value read 16 val (x1, y1) = f() diff --git a/idea/testData/findUsages/kotlin/conventions/components/dataClass.0.java b/idea/testData/findUsages/kotlin/conventions/components/dataClass.0.java new file mode 100644 index 00000000000..e103eb2d200 --- /dev/null +++ b/idea/testData/findUsages/kotlin/conventions/components/dataClass.0.java @@ -0,0 +1,11 @@ +import java.util.List; +import pack.A; + +class JavaClass { + public List getA() { + A a = new A(1, "", ""); + return Collections.singletonList(a); + } + + public void takeA(A a){} +} \ No newline at end of file diff --git a/idea/testData/findUsages/kotlin/conventions/componentFunctions.0.kt b/idea/testData/findUsages/kotlin/conventions/components/dataClass.0.kt similarity index 58% rename from idea/testData/findUsages/kotlin/conventions/componentFunctions.0.kt rename to idea/testData/findUsages/kotlin/conventions/components/dataClass.0.kt index 094beb83f7e..e50e162c8a7 100644 --- a/idea/testData/findUsages/kotlin/conventions/componentFunctions.0.kt +++ b/idea/testData/findUsages/kotlin/conventions/components/dataClass.0.kt @@ -1,11 +1,5 @@ // PSI_ELEMENT: org.jetbrains.kotlin.psi.KtParameter // OPTIONS: usages +package pack data class A(val 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 -} diff --git a/idea/testData/findUsages/kotlin/conventions/components/dataClass.1.kt b/idea/testData/findUsages/kotlin/conventions/components/dataClass.1.kt new file mode 100644 index 00000000000..a0ae1f8e531 --- /dev/null +++ b/idea/testData/findUsages/kotlin/conventions/components/dataClass.1.kt @@ -0,0 +1,10 @@ +import pack.A + +fun test() { + for ((x, y, z) in arrayOf()) { + } + + for (a in listOf()) { + val (x, y) = a + } +} diff --git a/idea/testData/findUsages/kotlin/conventions/components/dataClass.2.kt b/idea/testData/findUsages/kotlin/conventions/components/dataClass.2.kt new file mode 100644 index 00000000000..dbea985d1b9 --- /dev/null +++ b/idea/testData/findUsages/kotlin/conventions/components/dataClass.2.kt @@ -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(1, "", "")) diff --git a/idea/testData/findUsages/kotlin/conventions/components/dataClass.results.txt b/idea/testData/findUsages/kotlin/conventions/components/dataClass.results.txt new file mode 100644 index 00000000000..5c9d69add8c --- /dev/null +++ b/idea/testData/findUsages/kotlin/conventions/components/dataClass.results.txt @@ -0,0 +1,10 @@ +[dataClass.1.kt] Value read 4 for ((x, y, z) in arrayOf()) { +[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() \ No newline at end of file diff --git a/idea/testData/findUsages/kotlin/conventions/componentFunctionsByRef.0.kt b/idea/testData/findUsages/kotlin/conventions/components/dataClassComponentByRef.0.kt similarity index 100% rename from idea/testData/findUsages/kotlin/conventions/componentFunctionsByRef.0.kt rename to idea/testData/findUsages/kotlin/conventions/components/dataClassComponentByRef.0.kt diff --git a/idea/testData/findUsages/kotlin/conventions/componentFunctionsByRef.results.txt b/idea/testData/findUsages/kotlin/conventions/components/dataClassComponentByRef.results.txt similarity index 100% rename from idea/testData/findUsages/kotlin/conventions/componentFunctionsByRef.results.txt rename to idea/testData/findUsages/kotlin/conventions/components/dataClassComponentByRef.results.txt diff --git a/idea/testData/findUsages/kotlin/conventions/components/extensionComponentFun.0.kt b/idea/testData/findUsages/kotlin/conventions/components/extensionComponentFun.0.kt new file mode 100644 index 00000000000..e63dc6c4a1e --- /dev/null +++ b/idea/testData/findUsages/kotlin/conventions/components/extensionComponentFun.0.kt @@ -0,0 +1,14 @@ +// PSI_ELEMENT: org.jetbrains.kotlin.psi.KtFunction +// OPTIONS: usages + +class X + +operator fun X.component1(): Int = 0 +operator fun X.component2(): Int = 1 + + +fun f() = X() + +fun test() { + val (x, y) = f() +} diff --git a/idea/testData/findUsages/kotlin/conventions/components/extensionComponentFun.results.txt b/idea/testData/findUsages/kotlin/conventions/components/extensionComponentFun.results.txt new file mode 100644 index 00000000000..82090aac119 --- /dev/null +++ b/idea/testData/findUsages/kotlin/conventions/components/extensionComponentFun.results.txt @@ -0,0 +1 @@ +Value read 13 val (x, y) = f() diff --git a/idea/testData/findUsages/kotlin/conventions/components/memberComponentFun.0.kt b/idea/testData/findUsages/kotlin/conventions/components/memberComponentFun.0.kt new file mode 100644 index 00000000000..fdc48840e02 --- /dev/null +++ b/idea/testData/findUsages/kotlin/conventions/components/memberComponentFun.0.kt @@ -0,0 +1,20 @@ +// PSI_ELEMENT: org.jetbrains.kotlin.psi.KtFunction +// OPTIONS: usages + +open class X { + operator fun 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() +} diff --git a/idea/testData/findUsages/kotlin/conventions/components/memberComponentFun.results.txt b/idea/testData/findUsages/kotlin/conventions/components/memberComponentFun.results.txt new file mode 100644 index 00000000000..f1e88ad8305 --- /dev/null +++ b/idea/testData/findUsages/kotlin/conventions/components/memberComponentFun.results.txt @@ -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() \ No newline at end of file diff --git a/idea/testData/inspections/unusedSymbol/function/conventionalNames.kt b/idea/testData/inspections/unusedSymbol/function/conventionalNames.kt deleted file mode 100644 index 9bfdc2f2126..00000000000 --- a/idea/testData/inspections/unusedSymbol/function/conventionalNames.kt +++ /dev/null @@ -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() { -} \ No newline at end of file diff --git a/idea/testData/inspections/unusedSymbol/function/operators.kt b/idea/testData/inspections/unusedSymbol/function/operators.kt new file mode 100644 index 00000000000..d443a39b850 --- /dev/null +++ b/idea/testData/inspections/unusedSymbol/function/operators.kt @@ -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 diff --git a/idea/testData/search/references/testComponentFun.kt b/idea/testData/search/references/testComponentFun.kt new file mode 100644 index 00000000000..b38c9df36ef --- /dev/null +++ b/idea/testData/search/references/testComponentFun.kt @@ -0,0 +1,10 @@ +class A + +operator fun A.component1(): Int = 0 +operator fun A.component2(): Int = 1 + +fun test() { + val a = A() + a.component1() + val (x, y) = a +} diff --git a/idea/tests/org/jetbrains/kotlin/findUsages/FindUsagesTestGenerated.java b/idea/tests/org/jetbrains/kotlin/findUsages/FindUsagesTestGenerated.java index ad59e5f3e84..ba8f4528e59 100644 --- a/idea/tests/org/jetbrains/kotlin/findUsages/FindUsagesTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/findUsages/FindUsagesTestGenerated.java @@ -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") diff --git a/idea/tests/org/jetbrains/kotlin/search/KotlinReferencesSearchTest.kt b/idea/tests/org/jetbrains/kotlin/search/KotlinReferencesSearchTest.kt index 267b26bdc94..9cffed97647 100644 --- a/idea/tests/org/jetbrains/kotlin/search/KotlinReferencesSearchTest.kt +++ b/idea/tests/org/jetbrains/kotlin/search/KotlinReferencesSearchTest.kt @@ -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() + 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 doTest(): List { - myFixtureProxy.configureByFile(fileName) + val psiFile = myFixtureProxy.configureByFile(fileName) val func = myFixtureProxy.elementAtCaret.getParentOfType(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 } }