diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/ExpressionsOfTypeProcessor.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/ExpressionsOfTypeProcessor.kt index c8153c5f63b..42467f58cbd 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/ExpressionsOfTypeProcessor.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/ExpressionsOfTypeProcessor.kt @@ -22,6 +22,7 @@ import com.intellij.lang.java.JavaLanguage import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.project.Project +import com.intellij.openapi.util.TextRange import com.intellij.psi.* import com.intellij.psi.search.FileTypeIndex import com.intellij.psi.search.GlobalSearchScope @@ -102,6 +103,10 @@ class ExpressionsOfTypeProcessor( } } } + + private fun PsiModifierListOwner.isPrivate() = hasModifierProperty(PsiModifier.PRIVATE) + + private fun PsiModifierListOwner.isLocal() = parents.any { it is PsiCodeBlock } } // note: a Task must define equals & hashCode! @@ -184,13 +189,52 @@ class ExpressionsOfTypeProcessor( possibleMatchesInScopeHandler(searchScope) } + private fun checkPsiClass(psiClass: PsiClass): Boolean { + // we don't filter out private classes because we can inherit public class from private inside the same visibility scope + if (psiClass.isLocal()) { + return false + } + + val qualifiedName = runReadAction { psiClass.qualifiedName } + if (qualifiedName == null || qualifiedName.isEmpty()) { + return false + } + + return true + } + + + private fun addNonKotlinClassToProcess(classToSearch: PsiClass) { + if (!checkPsiClass(classToSearch)) { + return + } + + addClassToProcess(classToSearch) + } + private fun addClassToProcess(classToSearch: PsiClass) { data class ProcessClassUsagesTask(val classToSearch: PsiClass) : Task { override fun perform() { testLog?.add("Searched references to ${logPresentation(classToSearch)}") val scope = GlobalSearchScope.allScope(project).excludeFileTypes(XmlFileType.INSTANCE) // ignore usages in XML - they don't affect us searchReferences(classToSearch, scope) { reference -> - if (processClassUsage(reference)) return@searchReferences true + val element = reference.element + val wasProcessed = when (element.language) { + KotlinLanguage.INSTANCE -> processClassUsageInKotlin(element) + JavaLanguage.INSTANCE -> processClassUsageInJava(element) + else -> { + if (element.language.displayName == "Groovy") { + processClassUsageInLanguageWithPsiClass(element) + true + } + else { + // If there's no PsiClass - consider processed + element.getParentOfType(true) == null + } + } + } + + if (wasProcessed) return@searchReferences true if (mode != Mode.ALWAYS_SMART) { downShiftToPlainSearch(reference) @@ -241,6 +285,12 @@ class ExpressionsOfTypeProcessor( addTask(ProcessCallableUsagesTask(declaration, processor)) } + private fun addPsiMemberTask(member: PsiMember) { + if (!member.isPrivate() && !member.isLocal()) { + addCallableDeclarationOfOurType(member) + } + } + private fun addCallableDeclarationOfOurType(declaration: PsiElement) { addCallableDeclarationToProcess(declaration, searchScope.restrictToKotlinSources(), ReferenceProcessor.CallableOfOurType) } @@ -275,6 +325,10 @@ class ExpressionsOfTypeProcessor( } private fun addSamInterfaceToProcess(psiClass: PsiClass) { + if (!checkPsiClass(psiClass)) { + return + } + data class ProcessSamInterfaceTask(val psiClass: PsiClass) : Task { override fun perform() { val scope = GlobalSearchScope.projectScope(project).excludeFileTypes(KotlinFileType.INSTANCE, XmlFileType.INSTANCE) @@ -298,20 +352,6 @@ class ExpressionsOfTypeProcessor( addTask(ProcessSamInterfaceTask(psiClass)) } - /** - * Process usage of our class or one of its inheritors - */ - private fun processClassUsage(reference: PsiReference): Boolean { - val element = reference.element - return when (element.language) { - KotlinLanguage.INSTANCE -> processClassUsageInKotlin(element) - - JavaLanguage.INSTANCE -> processClassUsageInJava(element) - - else -> false // we don't know anything about usages in other languages - so we downgrade to slow algorithm in this case - } - } - private fun processClassUsageInKotlin(element: PsiElement): Boolean { //TODO: type aliases @@ -476,15 +516,15 @@ class ExpressionsOfTypeProcessor( break@ParentsLoop // ignore local usages is PsiMethod -> { - if (prev == parent.returnTypeElement && !parent.isPrivateOrLocal()) { // usage in return type of a method - addCallableDeclarationOfOurType(parent) + if (prev == parent.returnTypeElement) { // usage in return type of a method + addPsiMemberTask(parent) } break@ParentsLoop } is PsiField -> { - if (prev == parent.typeElement && !parent.isPrivateOrLocal()) { // usage in type of a field - addCallableDeclarationOfOurType(parent) + if (prev == parent.typeElement) { // usage in type of a field + addPsiMemberTask(parent) } break@ParentsLoop } @@ -492,9 +532,7 @@ class ExpressionsOfTypeProcessor( is PsiReferenceList -> { // usage in extends/implements list if (parent.role == PsiReferenceList.Role.EXTENDS_LIST || parent.role == PsiReferenceList.Role.IMPLEMENTS_LIST) { val psiClass = parent.parent as PsiClass - if (!psiClass.isLocal()) { // we don't filter out private classes because we can inherit public class from private in Java - addClassToProcess(psiClass) - } + addNonKotlinClassToProcess(psiClass) } break@ParentsLoop } @@ -502,19 +540,7 @@ class ExpressionsOfTypeProcessor( //TODO: if Java parameter has Kotlin functional type then we should process method usages is PsiParameter -> { if (prev == parent.typeElement) { // usage in parameter type - check if the method is in SAM interface - val method = parent.declarationScope as? PsiMethod - if (method != null && method.hasModifierProperty(PsiModifier.ABSTRACT)) { - val psiClass = method.containingClass - if (psiClass != null) { - testLog?.add("Resolved java class to descriptor: ${psiClass.qualifiedName}") - - val resolutionFacade = KotlinCacheService.getInstance(project).getResolutionFacadeByFile(psiClass.containingFile, JvmPlatform) - val classDescriptor = psiClass.resolveToDescriptor(resolutionFacade) as? JavaClassDescriptor - if (classDescriptor != null && SingleAbstractMethodUtils.getSingleAbstractMethodOrNull(classDescriptor) != null) { - addSamInterfaceToProcess(psiClass) - } - } - } + processParameterInSamClass(parent) } break@ParentsLoop } @@ -526,6 +552,100 @@ class ExpressionsOfTypeProcessor( return true } + private fun processClassUsageInLanguageWithPsiClass(element: PsiElement) { + fun checkReferenceInTypeElement(typeElement: PsiTypeElement?, element: PsiElement): Boolean { + val typeTextRange = typeElement?.textRange + return (typeTextRange != null && element.textRange in typeTextRange) + } + + fun processParameter(parameter: PsiParameter): Boolean { + if (checkReferenceInTypeElement(parameter.typeElement, element)) { + processParameterInSamClass(parameter) + return true + } + + return false + } + + fun processMethod(method: PsiMethod): Boolean { + if (checkReferenceInTypeElement(method.returnTypeElement, element)) { + addPsiMemberTask(method) + return true + } + + val parameters = method.parameterList.parameters + for (parameter in parameters) { + if (processParameter(parameter)) { + return true + } + } + + return false + } + + fun processField(field: PsiField): Boolean { + if (checkReferenceInTypeElement(field.typeElement, element)) { + addPsiMemberTask(field) + return true + } + + return false + } + + fun processClass(psiClass: PsiClass) { + if (!checkPsiClass(psiClass)) { + return + } + + val elementTextRange: TextRange? = element.textRange + if (elementTextRange != null) { + val superList = listOf(psiClass.extendsList, psiClass.implementsList) + for (psiReferenceList in superList) { + val superListRange: TextRange? = psiReferenceList?.textRange + if (superListRange != null && elementTextRange in superListRange) { + addNonKotlinClassToProcess(psiClass) + return + } + } + } + + if (psiClass.fields.any { processField(it) }) { + return + } + + if (psiClass.methods.any { processMethod(it) }) { + return + } + + return + } + + val psiClass = element.getParentOfType(true) + if (psiClass != null) { + processClass(psiClass) + } + } + + private fun processParameterInSamClass(psiParameter: PsiParameter): Boolean { + val method = psiParameter.declarationScope as? PsiMethod ?: return false + + if (method.hasModifierProperty(PsiModifier.ABSTRACT)) { + val psiClass = method.containingClass + if (psiClass != null) { + testLog?.add("Resolved java class to descriptor: ${psiClass.qualifiedName}") + + val resolutionFacade = KotlinCacheService.getInstance(project).getResolutionFacadeByFile(psiClass.containingFile, JvmPlatform) + val classDescriptor = psiClass.resolveToDescriptor(resolutionFacade) as? JavaClassDescriptor + if (classDescriptor != null && SingleAbstractMethodUtils.getSingleAbstractMethodOrNull(classDescriptor) != null) { + addSamInterfaceToProcess(psiClass) + return true + } + } + } + + return false + } + /** * Process expression which may have type of our class (or our class used anywhere inside that type) */ @@ -664,12 +784,6 @@ class ExpressionsOfTypeProcessor( return true // we don't know } - private fun PsiModifierListOwner.isPrivateOrLocal(): Boolean { - return hasModifierProperty(PsiModifier.PRIVATE) || isLocal() - } - - private fun PsiModifierListOwner.isLocal() = parents.any { it is PsiCodeBlock } - private fun KotlinType.containsTypeOrDerivedInside(type: FuzzyType): Boolean { return type.checkIsSuperTypeOf(this) != null || arguments.any { it.type.containsTypeOrDerivedInside(type) } } @@ -683,7 +797,7 @@ class ExpressionsOfTypeProcessor( } } - private fun searchReferences(element: PsiElement,scope: SearchScope, processor: (PsiReference) -> Boolean) { + private fun searchReferences(element: PsiElement, scope: SearchScope, processor: (PsiReference) -> Boolean) { val parameters = ReferencesSearch.SearchParameters(element, scope, false) searchReferences(parameters, processor) } diff --git a/idea/testData/findUsages/java/findJavaMethodUsages/JavaWithGroovyInvoke.0.groovy b/idea/testData/findUsages/java/findJavaMethodUsages/JavaWithGroovyInvoke.0.groovy index ec2ab33426c..6a3daa9add4 100644 --- a/idea/testData/findUsages/java/findJavaMethodUsages/JavaWithGroovyInvoke.0.groovy +++ b/idea/testData/findUsages/java/findJavaMethodUsages/JavaWithGroovyInvoke.0.groovy @@ -1,6 +1,6 @@ class GroovyClass extends JavaWithGroovyInvoke_0 { - def fieldNoType = new JavaWithGroovyInvoke_0.OtherJavaClass() - def JavaWithGroovyInvoke_0.OtherJavaClass fieldWithType = new JavaWithGroovyInvoke_0.OtherJavaClass() + public def fieldNoType = new JavaWithGroovyInvoke_0.OtherJavaClass() + public def JavaWithGroovyInvoke_0.OtherJavaClass fieldWithType = fieldNoType def methodNoType() { new JavaWithGroovyInvoke_0.OtherJavaClass() diff --git a/idea/testData/findUsages/java/findJavaMethodUsages/JavaWithGroovyInvoke.log b/idea/testData/findUsages/java/findJavaMethodUsages/JavaWithGroovyInvoke.log index cfc71b0056b..c3e8fcc1870 100644 --- a/idea/testData/findUsages/java/findJavaMethodUsages/JavaWithGroovyInvoke.log +++ b/idea/testData/findUsages/java/findJavaMethodUsages/JavaWithGroovyInvoke.log @@ -1,5 +1,16 @@ -Downgrade to plain text search: Unsupported reference: 'JavaWithGroovyInvoke_0' in JavaWithGroovyInvoke.0.groovy line 1 column 27 +Resolved c() +Resolved fieldWithType() +Resolved methodWithType() +Resolved o() +Resolved o() +Resolved o.methodNoType()() +Resolved o.methodWithType()() +Resolved o.methodWithType()() +Searched references to GroovyClass +Searched references to GroovyClass.fieldWithType in non-Java files +Searched references to GroovyClass.methodWithType in non-Java files Searched references to JavaWithGroovyInvoke_0 -Used plain search of JavaWithGroovyInvoke_0.invoke() in LocalSearchScope: - JetFile: JavaWithGroovyInvoke.0.kt -Used plain search of JavaWithGroovyInvoke_0.invoke() in whole search scope \ No newline at end of file +Searched references to JavaWithGroovyInvoke_0.OtherJavaClass +Searched references to parameter c of f(c: JavaWithGroovyInvoke_0) in non-Java files +Searched references to parameter o of foo(o: JavaWithGroovyInvoke_0.OtherJavaClass) in non-Java files +Searched references to parameter o of gr(o: GroovyClass) in non-Java files \ No newline at end of file diff --git a/idea/testData/findUsages/java/findJavaMethodUsages/javaInvoke.0.java b/idea/testData/findUsages/java/findJavaMethodUsages/javaInvoke.0.java index 6d83e30be2d..523d18050c8 100644 --- a/idea/testData/findUsages/java/findJavaMethodUsages/javaInvoke.0.java +++ b/idea/testData/findUsages/java/findJavaMethodUsages/javaInvoke.0.java @@ -4,4 +4,6 @@ public class JavaClass { public void invoke() { } + + public static class OtherJavaClass extends JavaClass {} } \ No newline at end of file diff --git a/idea/testData/findUsages/java/findJavaMethodUsages/javaInvoke.0.kt b/idea/testData/findUsages/java/findJavaMethodUsages/javaInvoke.0.kt index 603d71b954a..875286ad6d3 100644 --- a/idea/testData/findUsages/java/findJavaMethodUsages/javaInvoke.0.kt +++ b/idea/testData/findUsages/java/findJavaMethodUsages/javaInvoke.0.kt @@ -1,3 +1,7 @@ fun f(c: JavaClass) { c() +} + +fun foo(o: JavaClass.OtherJavaClass) { + o() } \ No newline at end of file diff --git a/idea/testData/findUsages/java/findJavaMethodUsages/javaInvoke.log b/idea/testData/findUsages/java/findJavaMethodUsages/javaInvoke.log index f53c233ccf2..24cd8131ccc 100644 --- a/idea/testData/findUsages/java/findJavaMethodUsages/javaInvoke.log +++ b/idea/testData/findUsages/java/findJavaMethodUsages/javaInvoke.log @@ -1,3 +1,6 @@ Resolved c() +Resolved o() Searched references to JavaClass -Searched references to parameter c of f(c: JavaClass) in non-Java files \ No newline at end of file +Searched references to JavaClass.OtherJavaClass +Searched references to parameter c of f(c: JavaClass) in non-Java files +Searched references to parameter o of foo(o: JavaClass.OtherJavaClass) in non-Java files \ No newline at end of file diff --git a/idea/testData/findUsages/java/findJavaMethodUsages/javaInvoke.results.txt b/idea/testData/findUsages/java/findJavaMethodUsages/javaInvoke.results.txt index f1711ad4791..844cbfd51d6 100644 --- a/idea/testData/findUsages/java/findJavaMethodUsages/javaInvoke.results.txt +++ b/idea/testData/findUsages/java/findJavaMethodUsages/javaInvoke.results.txt @@ -1 +1,2 @@ -Implicit 'invoke' 2 c() \ No newline at end of file +Implicit 'invoke' 2 c() +Implicit 'invoke' 6 o() \ No newline at end of file diff --git a/idea/testData/findUsages/kotlin/conventions/invoke.0.kt b/idea/testData/findUsages/kotlin/conventions/invoke.0.kt index 8988752c597..bb591f08656 100644 --- a/idea/testData/findUsages/kotlin/conventions/invoke.0.kt +++ b/idea/testData/findUsages/kotlin/conventions/invoke.0.kt @@ -20,14 +20,14 @@ fun test() { listOf(Obj)[0](1) } -class C(): B(12) { - override fun invoke(i: Int) {} -} - fun cTest(c: C) { c(5) some(12, "Irrelevant usage") } +class C(): B(12) { + override fun invoke(i: Int) {} +} + fun some(i: Int, s: String) {}