From 5b3102058c0f56e45f1b7fb60cad9f2634520326 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Fri, 26 Aug 2016 18:56:32 +0300 Subject: [PATCH] Handling of 2 more class of cases: 1. Function has parameter of functional type with our data class inside 2. Value of data class type used in a call with lambda --- .../destructuringDeclarationUsages.kt | 154 ++++++++++++------ .../conventions/components/dataClass.4.kt | 31 ++++ .../conventions/components/dataClass.5.kt | 11 ++ .../components/dataClass.results.txt | 9 +- 4 files changed, 158 insertions(+), 47 deletions(-) create mode 100644 idea/testData/findUsages/kotlin/conventions/components/dataClass.4.kt create mode 100644 idea/testData/findUsages/kotlin/conventions/components/dataClass.5.kt 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 index 11fbb19ee26..7b7c008762a 100644 --- 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 @@ -39,7 +39,6 @@ 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.search.unionSafe import org.jetbrains.kotlin.idea.search.usagesSearch.DestructuringDeclarationUsageSearch.* import org.jetbrains.kotlin.idea.util.FuzzyType import org.jetbrains.kotlin.idea.util.fuzzyExtensionReceiverType @@ -125,9 +124,14 @@ private class Processor( private val plainSearchHandler: (SearchScope) -> Unit ) { private val project = target.project - private val declarationsToSearch = ArrayList() - private val declarationsToSearchSet = HashSet() - private var scopeToUsePlainSearch: SearchScope = LocalSearchScope.EMPTY + + // 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? + private val declarationUsageScope = GlobalSearchScope.projectScope(project).restrictToKotlinSources() //TODO: search in libraries? + + private val declarationsToProcess = ArrayDeque() + private val declarationsToProcessSet = HashSet() + private val scopesToUsePlainSearch = LinkedHashMap>() fun run() { val dataClassDescriptor = dataType.type.constructor.declarationDescriptor ?: return @@ -160,20 +164,20 @@ private class Processor( (classToSearch as? KtLightClass)?.kotlinOrigin?.let { usePlainSearch(it) } } - // 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) - } + processDeclarations() - plainSearchHandler(scopeToUsePlainSearch) + val scopeElements = scopesToUsePlainSearch.values.flatMap { it }.toTypedArray() + plainSearchHandler(LocalSearchScope(scopeElements)) } //TODO: check if it's operator (too expensive) - private fun addDeclarationToProcess(declaration: PsiElement) { - if (declarationsToSearchSet.add(declaration)) { - declarationsToSearch.add(declaration) + /** + * Adds declaration whose type is our data class (or data class used anywhere inside that type) + * or which has parameter of functional type with our data class used inside + */ + private fun addCallableDeclarationToProcess(declaration: PsiElement) { + if (declarationsToProcessSet.add(declaration)) { + declarationsToProcess.push(declaration) } } @@ -205,7 +209,18 @@ private class Processor( is KtCallableDeclaration -> { when (typeRef) { typeRefParent.typeReference -> { - addDeclarationToProcess(typeRefParent) + addCallableDeclarationToProcess(typeRefParent) + + if (typeRefParent is KtParameter) { + val usedInsideFunctionalType = parent.parents.takeWhile { it != typeRef }.any { it is KtFunctionType } + if (usedInsideFunctionalType) { + val function = (typeRefParent.parent as? KtParameterList)?.parent as? KtFunction + if (function != null) { + addCallableDeclarationToProcess(function) + } + } + } + return true } @@ -288,7 +303,7 @@ private class Processor( is PsiMethod, is PsiField -> { if (!(parent as PsiModifierListOwner).isPrivateOrLocal()) { - addDeclarationToProcess(parent) + addCallableDeclarationToProcess(parent) } break@ParentsLoop } @@ -298,29 +313,27 @@ private class Processor( 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 -> - if (reference is KtDestructuringDeclarationReference) { // declaration usage in form of destructuring declaration - val entries = reference.element.entries - val componentIndex = when (declaration) { - is KtParameter -> declaration.dataClassComponentFunction()?.name?.asString()?.let { getComponentIndex(it) } - is KtFunction -> declaration.name?.let { getComponentIndex(it) } + private fun processDeclarations() { + while (declarationsToProcess.isNotEmpty()) { + val declaration = declarationsToProcess.pop() + + ReferencesSearch.search(declaration, declarationUsageScope).forEach { reference -> + if (reference is KtDestructuringDeclarationReference) { // declaration usage in form of destructuring declaration + val entries = reference.element.entries + val componentIndex = when (declaration) { + is KtParameter -> declaration.dataClassComponentFunction()?.name?.asString()?.let { getComponentIndex(it) } + is KtFunction -> declaration.name?.let { getComponentIndex(it) } //TODO: java component functions (see KT-13605) - else -> null + else -> null + } + if (componentIndex != null && componentIndex <= entries.size) { + addCallableDeclarationToProcess(entries[componentIndex - 1]) + } } - if (componentIndex != null && componentIndex <= entries.size) { - processDeclarationOfTypeWithDataClass(entries[componentIndex - 1]) + else { + (reference.element as? KtReferenceExpression)?.let { processSuspiciousExpression(it) } } } - else { - (reference.element as? KtReferenceExpression)?.let { processSuspiciousExpression(it) } - } } } @@ -328,11 +341,13 @@ private class Processor( * Process expression which may have type of our data class (or data class used anywhere inside that type) */ private fun processSuspiciousExpression(expression: KtExpression) { + var affectedScope: PsiElement = expression ParentsLoop@ - for (container in expression.parentsWithSelf) { - if (container !is KtExpression) continue - val parent = container.parent + for (element in expression.parentsWithSelf) { + affectedScope = element + if (element !is KtExpression) continue + val parent = element.parent when (parent) { is KtDestructuringDeclaration -> { processSuspiciousDeclaration(parent) @@ -340,10 +355,9 @@ private class Processor( } is KtWithExpressionInitializer -> { - if (container != parent.initializer) continue@ParentsLoop - - processSuspiciousDeclaration(parent) - + if (element == parent.initializer) { + processSuspiciousDeclaration(parent) + } break@ParentsLoop } @@ -353,9 +367,19 @@ private class Processor( (forExpression.destructuringParameter ?: forExpression.loopParameter as KtDeclaration?)?.let { processSuspiciousDeclaration(it) } + break@ParentsLoop } } } + + if (!element.mayTypeAffectAncestors()) break + } + + // use plain search in all lambdas and anonymous functions inside because they parameters or receiver can be implicitly typed with our data class + affectedScope.forEachDescendantOfType { + if (it.nameIdentifier == null) { + usePlainSearch(it) + } } } @@ -377,15 +401,53 @@ private class Processor( val descriptor = declaration.resolveToDescriptorIfAny() as? CallableDescriptor ?: return val type = descriptor.returnType if (type != null && type.containsTypeOrDerivedInside(dataType)) { - addDeclarationToProcess(declaration) + addCallableDeclarationToProcess(declaration) } } } private fun usePlainSearch(scopeElement: KtElement) { - scopeToUsePlainSearch = LocalSearchScope(scopeElement) - .intersectWith(searchScope) - .unionSafe(scopeToUsePlainSearch) + val file = scopeElement.getContainingKtFile() + val restricted = LocalSearchScope(scopeElement).intersectWith(searchScope) as LocalSearchScope + ScopeLoop@ + for (element in restricted.scope) { + val prevElements = scopesToUsePlainSearch.getOrPut(file) { ArrayList() } + for ((index, prevElement) in prevElements.withIndex()) { + if (prevElement.isAncestor(element, strict = false)) continue@ScopeLoop + if (element.isAncestor(prevElement)) { + prevElements[index] = element + continue@ScopeLoop + } + } + prevElements.add(element) + } + } + + //TODO: code is quite similar to PartialBodyResolveFilter.isValueNeeded + private fun KtExpression.mayTypeAffectAncestors(): Boolean { + val parent = this.parent + when (parent) { + is KtBlockExpression -> { + return this == parent.statements.last() && parent.mayTypeAffectAncestors() + } + + is KtDeclarationWithBody -> { + if (this == parent.bodyExpression) { + return !parent.hasBlockBody() && !parent.hasDeclaredReturnType() + } + } + + is KtContainerNode -> { + val grandParent = parent.parent + return when (parent.node.elementType) { + KtNodeTypes.CONDITION, KtNodeTypes.BODY -> false + KtNodeTypes.THEN, KtNodeTypes.ELSE -> (grandParent as KtExpression).mayTypeAffectAncestors() + KtNodeTypes.LOOP_RANGE, KtNodeTypes.INDICES -> true + else -> true // something else unknown + } + } + } + return true // we don't know } private fun PsiModifierListOwner.isPrivateOrLocal(): Boolean { diff --git a/idea/testData/findUsages/kotlin/conventions/components/dataClass.4.kt b/idea/testData/findUsages/kotlin/conventions/components/dataClass.4.kt new file mode 100644 index 00000000000..81db92e4c12 --- /dev/null +++ b/idea/testData/findUsages/kotlin/conventions/components/dataClass.4.kt @@ -0,0 +1,31 @@ +package pack4 + +import pack.A + +fun takeExtFun(p: A.() -> Unit) { +} + +fun takeFun1(p: (A) -> Unit) { +} + +fun takeFun2(p: ((A?, Int) -> Unit)?) { +} + +fun takeFun3(p: (List) -> Unit) { +} + +fun takeFuns(p: MutableList<(A) -> Unit>) { + p[0] = { val (x, y) = it } +} + +fun x(p1: T, p2: (T) -> Unit){} + +fun foo(p: A) { + takeExtFun { val (x, y) = this } + + takeFun1 { val (x, y) = it } + takeFun2 { a, n -> val (x, y) = a!! } + takeFun3 { val (x, y) = it[0] } + + x(p) { val (x, y) = it } +} diff --git a/idea/testData/findUsages/kotlin/conventions/components/dataClass.5.kt b/idea/testData/findUsages/kotlin/conventions/components/dataClass.5.kt new file mode 100644 index 00000000000..e0982de43e4 --- /dev/null +++ b/idea/testData/findUsages/kotlin/conventions/components/dataClass.5.kt @@ -0,0 +1,11 @@ +package pack5 + +import pack.A + +var Any.v: (A) -> Unit + get() = TODO() + set(value) = TODO() + +fun f() { + "".v = { val (x, y ) = it } +} \ No newline at end of file diff --git a/idea/testData/findUsages/kotlin/conventions/components/dataClass.results.txt b/idea/testData/findUsages/kotlin/conventions/components/dataClass.results.txt index 9b30d96c0b5..7bfd35e73c9 100644 --- a/idea/testData/findUsages/kotlin/conventions/components/dataClass.results.txt +++ b/idea/testData/findUsages/kotlin/conventions/components/dataClass.results.txt @@ -15,4 +15,11 @@ [dataClass.2.kt] Value read 8 val (x, y, z) = a [dataClass.2.kt] Value read 9 val (x1, y1, z1) = f() [dataClass.3.kt] Value read 12 val (x, y, z) = a -[dataClass.3.kt] Value read 15 val (x1, y1, z1) = a1 \ No newline at end of file +[dataClass.3.kt] Value read 15 val (x1, y1, z1) = a1 +[dataClass.4.kt] Value read 18 p[0] = { val (x, y) = it } +[dataClass.4.kt] Value read 24 takeExtFun { val (x, y) = this } +[dataClass.4.kt] Value read 26 takeFun1 { val (x, y) = it } +[dataClass.4.kt] Value read 27 takeFun2 { a, n -> val (x, y) = a!! } +[dataClass.4.kt] Value read 28 takeFun3 { val (x, y) = it[0] } +[dataClass.4.kt] Value read 30 x(p) { val (x, y) = it } +[dataClass.5.kt] Value read 10 "".v = { val (x, y ) = it } \ No newline at end of file