From 7a269a00afe36db7e1a025a959699779e4d102cb Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Sat, 27 Aug 2016 14:14:22 +0300 Subject: [PATCH] SAM-adapters supported --- .../destructuringDeclarationUsages.kt | 129 +++++++++++++----- .../conventions/components/dataClass.0.java | 11 +- .../conventions/components/dataClass.6.kt | 5 + .../components/dataClass.results.txt | 3 +- 4 files changed, 112 insertions(+), 36 deletions(-) create mode 100644 idea/testData/findUsages/kotlin/conventions/components/dataClass.6.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 1f89f787ae2..d44f904e0e0 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 @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.idea.search.usagesSearch +import com.intellij.ide.highlighter.JavaFileType import com.intellij.lang.java.JavaLanguage import com.intellij.openapi.application.ApplicationManager import com.intellij.psi.* @@ -33,6 +34,7 @@ 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.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde @@ -45,6 +47,7 @@ import org.jetbrains.kotlin.idea.util.ProjectRootsUtil import org.jetbrains.kotlin.idea.util.fuzzyExtensionReceiverType import org.jetbrains.kotlin.idea.util.toFuzzyType import org.jetbrains.kotlin.kdoc.psi.impl.KDocName +import org.jetbrains.kotlin.load.java.sam.SingleAbstractMethodUtils import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.resolve.dataClassUtils.getComponentIndex @@ -129,8 +132,14 @@ private class Processor( //TODO: what about Scala and other JVM-languages? private val declarationUsageScope = GlobalSearchScope.projectScope(project).restrictToKotlinSources() - private val declarationsToProcess = ArrayDeque() - private val declarationsToProcessSet = HashSet() + // note: a Task must define equals & hashCode! + private interface Task { + fun perform() + } + + private val tasks = ArrayDeque() + private val taskSet = HashSet() + private val scopesToUsePlainSearch = LinkedHashMap>() fun run() { @@ -170,21 +179,71 @@ private class Processor( (classToSearch as? KtLightClass)?.kotlinOrigin?.let { usePlainSearch(it) } } - processDeclarations() + processTasks() val scopeElements = scopesToUsePlainSearch.values.flatMap { it }.toTypedArray() plainSearchHandler(LocalSearchScope(scopeElements)) } + private fun addTask(task: Task) { + if (taskSet.add(task)) { + tasks.push(task) + } + + } + + private fun processTasks() { + while (tasks.isNotEmpty()) { + tasks.pop().perform() + } + } + //TODO: check if it's operator (too expensive) /** * 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) + data class ProcessCallableUsagesTask(val declaration: PsiElement) : Task { + override fun perform() { + 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 + } + if (componentIndex != null && componentIndex <= entries.size) { + addCallableDeclarationToProcess(entries[componentIndex - 1]) + } + } + else { + (reference.element as? KtReferenceExpression)?.let { processSuspiciousExpression(it) } + } + } + } } + addTask(ProcessCallableUsagesTask(declaration)) + } + + private fun addSamInterfaceToProcess(psiClass: PsiClass) { + data class SamInterfaceTask(val psiClass: PsiClass) : Task { + override fun perform() { + //TODO: what about other JVM languages? + val scope = GlobalSearchScope.getScopeRestrictedByFileTypes(GlobalSearchScope.projectScope(project), JavaFileType.INSTANCE) + ReferencesSearch.search(psiClass, scope).forEach { reference -> + // check if the reference is method parameter type + val parameter = ((reference as? PsiJavaCodeReferenceElement)?.parent as? PsiTypeElement)?.parent as? PsiParameter + val method = parameter?.declarationScope as? PsiMethod + if (method != null) { + addCallableDeclarationToProcess(method) + } + } + } + } + addTask(SamInterfaceTask(psiClass)) } /** @@ -305,51 +364,53 @@ private class Processor( private fun processJavaDataClassUsage(element: PsiElement): Boolean { if (element !is PsiJavaCodeReferenceElement) return true // meaningless reference from Java + var prev = element ParentsLoop@ for (parent in element.parents) { when (parent) { is PsiCodeBlock, is PsiExpression, - is PsiImportStatement, - is PsiParameter -> //TODO: if Java parameter has Kotlin functional type then we should process method usages - break@ParentsLoop // ignore local usages, usages in imports and in parameter types + is PsiImportStatement -> + break@ParentsLoop // ignore local usages and usages in imports - is PsiMethod, is PsiField -> { - if (!(parent as PsiModifierListOwner).isPrivateOrLocal()) { + //TODO: if Java parameter has Kotlin functional type then we should process method usages + is PsiParameter -> { + if (prev == parent.typeElement) { + val method = parent.declarationScope as? PsiMethod + if (method != null && method.hasModifierProperty(PsiModifier.ABSTRACT)) { + val psiClass = method.containingClass + if (psiClass != null) { + val classDescriptor = psiClass.resolveToDescriptor(target.getResolutionFacade()) + if (classDescriptor != null && SingleAbstractMethodUtils.getSingleAbstractMethodOrNull(classDescriptor) != null) { + addSamInterfaceToProcess(psiClass) + } + } + } + } + break@ParentsLoop + } + + is PsiMethod -> { + if (prev == parent.returnTypeElement && !parent.isPrivateOrLocal()) { + addCallableDeclarationToProcess(parent) + } + break@ParentsLoop + } + + is PsiField -> { + if (prev == parent.typeElement && !parent.isPrivateOrLocal()) { addCallableDeclarationToProcess(parent) } break@ParentsLoop } } + + prev = parent } return true } - 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 - } - if (componentIndex != null && componentIndex <= entries.size) { - addCallableDeclarationToProcess(entries[componentIndex - 1]) - } - } - else { - (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) */ diff --git a/idea/testData/findUsages/kotlin/conventions/components/dataClass.0.java b/idea/testData/findUsages/kotlin/conventions/components/dataClass.0.java index 89b2fc5befb..967e481cc69 100644 --- a/idea/testData/findUsages/kotlin/conventions/components/dataClass.0.java +++ b/idea/testData/findUsages/kotlin/conventions/components/dataClass.0.java @@ -1,6 +1,9 @@ -import java.util.List; +import kotlin.Unit; import pack.A; +import java.util.Collections; +import java.util.List; + class JavaClass { public List getA() { A a = new A(1, "", ""); @@ -8,4 +11,10 @@ class JavaClass { } public void takeA(A a){} + + public void takeSAM(JavaSAM sam){} +} + +public interface JavaSAM { + void takeA(A a); } diff --git a/idea/testData/findUsages/kotlin/conventions/components/dataClass.6.kt b/idea/testData/findUsages/kotlin/conventions/components/dataClass.6.kt new file mode 100644 index 00000000000..af942d76b78 --- /dev/null +++ b/idea/testData/findUsages/kotlin/conventions/components/dataClass.6.kt @@ -0,0 +1,5 @@ +import pack.A + +fun foo(javaClass: JavaClass) { + javaClass.takeSAM { 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 2937248a406..b81aedc66aa 100644 --- a/idea/testData/findUsages/kotlin/conventions/components/dataClass.results.txt +++ b/idea/testData/findUsages/kotlin/conventions/components/dataClass.results.txt @@ -23,4 +23,5 @@ [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 +[dataClass.5.kt] Value read 10 "".v = { val (x, y ) = it } +[dataClass.6.kt] Value read 4 javaClass.takeSAM { val (x, y) = it }