Avoid full references search for languages without PsiClass (KT-14974)
Make smart type search work for Groovy. #KT-14974 Fixed
This commit is contained in:
+156
-42
@@ -22,6 +22,7 @@ import com.intellij.lang.java.JavaLanguage
|
|||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.diagnostic.Logger
|
import com.intellij.openapi.diagnostic.Logger
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.util.TextRange
|
||||||
import com.intellij.psi.*
|
import com.intellij.psi.*
|
||||||
import com.intellij.psi.search.FileTypeIndex
|
import com.intellij.psi.search.FileTypeIndex
|
||||||
import com.intellij.psi.search.GlobalSearchScope
|
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!
|
// note: a Task must define equals & hashCode!
|
||||||
@@ -184,13 +189,52 @@ class ExpressionsOfTypeProcessor(
|
|||||||
possibleMatchesInScopeHandler(searchScope)
|
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) {
|
private fun addClassToProcess(classToSearch: PsiClass) {
|
||||||
data class ProcessClassUsagesTask(val classToSearch: PsiClass) : Task {
|
data class ProcessClassUsagesTask(val classToSearch: PsiClass) : Task {
|
||||||
override fun perform() {
|
override fun perform() {
|
||||||
testLog?.add("Searched references to ${logPresentation(classToSearch)}")
|
testLog?.add("Searched references to ${logPresentation(classToSearch)}")
|
||||||
val scope = GlobalSearchScope.allScope(project).excludeFileTypes(XmlFileType.INSTANCE) // ignore usages in XML - they don't affect us
|
val scope = GlobalSearchScope.allScope(project).excludeFileTypes(XmlFileType.INSTANCE) // ignore usages in XML - they don't affect us
|
||||||
searchReferences(classToSearch, scope) { reference ->
|
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<PsiClass>(true) == null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wasProcessed) return@searchReferences true
|
||||||
|
|
||||||
if (mode != Mode.ALWAYS_SMART) {
|
if (mode != Mode.ALWAYS_SMART) {
|
||||||
downShiftToPlainSearch(reference)
|
downShiftToPlainSearch(reference)
|
||||||
@@ -241,6 +285,12 @@ class ExpressionsOfTypeProcessor(
|
|||||||
addTask(ProcessCallableUsagesTask(declaration, processor))
|
addTask(ProcessCallableUsagesTask(declaration, processor))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun addPsiMemberTask(member: PsiMember) {
|
||||||
|
if (!member.isPrivate() && !member.isLocal()) {
|
||||||
|
addCallableDeclarationOfOurType(member)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun addCallableDeclarationOfOurType(declaration: PsiElement) {
|
private fun addCallableDeclarationOfOurType(declaration: PsiElement) {
|
||||||
addCallableDeclarationToProcess(declaration, searchScope.restrictToKotlinSources(), ReferenceProcessor.CallableOfOurType)
|
addCallableDeclarationToProcess(declaration, searchScope.restrictToKotlinSources(), ReferenceProcessor.CallableOfOurType)
|
||||||
}
|
}
|
||||||
@@ -275,6 +325,10 @@ class ExpressionsOfTypeProcessor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun addSamInterfaceToProcess(psiClass: PsiClass) {
|
private fun addSamInterfaceToProcess(psiClass: PsiClass) {
|
||||||
|
if (!checkPsiClass(psiClass)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
data class ProcessSamInterfaceTask(val psiClass: PsiClass) : Task {
|
data class ProcessSamInterfaceTask(val psiClass: PsiClass) : Task {
|
||||||
override fun perform() {
|
override fun perform() {
|
||||||
val scope = GlobalSearchScope.projectScope(project).excludeFileTypes(KotlinFileType.INSTANCE, XmlFileType.INSTANCE)
|
val scope = GlobalSearchScope.projectScope(project).excludeFileTypes(KotlinFileType.INSTANCE, XmlFileType.INSTANCE)
|
||||||
@@ -298,20 +352,6 @@ class ExpressionsOfTypeProcessor(
|
|||||||
addTask(ProcessSamInterfaceTask(psiClass))
|
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 {
|
private fun processClassUsageInKotlin(element: PsiElement): Boolean {
|
||||||
//TODO: type aliases
|
//TODO: type aliases
|
||||||
|
|
||||||
@@ -476,15 +516,15 @@ class ExpressionsOfTypeProcessor(
|
|||||||
break@ParentsLoop // ignore local usages
|
break@ParentsLoop // ignore local usages
|
||||||
|
|
||||||
is PsiMethod -> {
|
is PsiMethod -> {
|
||||||
if (prev == parent.returnTypeElement && !parent.isPrivateOrLocal()) { // usage in return type of a method
|
if (prev == parent.returnTypeElement) { // usage in return type of a method
|
||||||
addCallableDeclarationOfOurType(parent)
|
addPsiMemberTask(parent)
|
||||||
}
|
}
|
||||||
break@ParentsLoop
|
break@ParentsLoop
|
||||||
}
|
}
|
||||||
|
|
||||||
is PsiField -> {
|
is PsiField -> {
|
||||||
if (prev == parent.typeElement && !parent.isPrivateOrLocal()) { // usage in type of a field
|
if (prev == parent.typeElement) { // usage in type of a field
|
||||||
addCallableDeclarationOfOurType(parent)
|
addPsiMemberTask(parent)
|
||||||
}
|
}
|
||||||
break@ParentsLoop
|
break@ParentsLoop
|
||||||
}
|
}
|
||||||
@@ -492,9 +532,7 @@ class ExpressionsOfTypeProcessor(
|
|||||||
is PsiReferenceList -> { // usage in extends/implements list
|
is PsiReferenceList -> { // usage in extends/implements list
|
||||||
if (parent.role == PsiReferenceList.Role.EXTENDS_LIST || parent.role == PsiReferenceList.Role.IMPLEMENTS_LIST) {
|
if (parent.role == PsiReferenceList.Role.EXTENDS_LIST || parent.role == PsiReferenceList.Role.IMPLEMENTS_LIST) {
|
||||||
val psiClass = parent.parent as PsiClass
|
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
|
addNonKotlinClassToProcess(psiClass)
|
||||||
addClassToProcess(psiClass)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break@ParentsLoop
|
break@ParentsLoop
|
||||||
}
|
}
|
||||||
@@ -502,19 +540,7 @@ class ExpressionsOfTypeProcessor(
|
|||||||
//TODO: if Java parameter has Kotlin functional type then we should process method usages
|
//TODO: if Java parameter has Kotlin functional type then we should process method usages
|
||||||
is PsiParameter -> {
|
is PsiParameter -> {
|
||||||
if (prev == parent.typeElement) { // usage in parameter type - check if the method is in SAM interface
|
if (prev == parent.typeElement) { // usage in parameter type - check if the method is in SAM interface
|
||||||
val method = parent.declarationScope as? PsiMethod
|
processParameterInSamClass(parent)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break@ParentsLoop
|
break@ParentsLoop
|
||||||
}
|
}
|
||||||
@@ -526,6 +552,100 @@ class ExpressionsOfTypeProcessor(
|
|||||||
return true
|
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<PsiClass>(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)
|
* 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
|
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 {
|
private fun KotlinType.containsTypeOrDerivedInside(type: FuzzyType): Boolean {
|
||||||
return type.checkIsSuperTypeOf(this) != null || arguments.any { it.type.containsTypeOrDerivedInside(type) }
|
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)
|
val parameters = ReferencesSearch.SearchParameters(element, scope, false)
|
||||||
searchReferences(parameters, processor)
|
searchReferences(parameters, processor)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
class GroovyClass extends JavaWithGroovyInvoke_0 {
|
class GroovyClass extends JavaWithGroovyInvoke_0 {
|
||||||
def fieldNoType = new JavaWithGroovyInvoke_0.OtherJavaClass()
|
public def fieldNoType = new JavaWithGroovyInvoke_0.OtherJavaClass()
|
||||||
def JavaWithGroovyInvoke_0.OtherJavaClass fieldWithType = new JavaWithGroovyInvoke_0.OtherJavaClass()
|
public def JavaWithGroovyInvoke_0.OtherJavaClass fieldWithType = fieldNoType
|
||||||
|
|
||||||
def methodNoType() {
|
def methodNoType() {
|
||||||
new JavaWithGroovyInvoke_0.OtherJavaClass()
|
new JavaWithGroovyInvoke_0.OtherJavaClass()
|
||||||
|
|||||||
+15
-4
@@ -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
|
Searched references to JavaWithGroovyInvoke_0
|
||||||
Used plain search of JavaWithGroovyInvoke_0.invoke() in LocalSearchScope:
|
Searched references to JavaWithGroovyInvoke_0.OtherJavaClass
|
||||||
JetFile: JavaWithGroovyInvoke.0.kt
|
Searched references to parameter c of f(c: JavaWithGroovyInvoke_0) in non-Java files
|
||||||
Used plain search of JavaWithGroovyInvoke_0.invoke() in whole search scope
|
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
|
||||||
@@ -4,4 +4,6 @@
|
|||||||
public class JavaClass {
|
public class JavaClass {
|
||||||
public void <caret>invoke() {
|
public void <caret>invoke() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class OtherJavaClass extends JavaClass {}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
fun f(c: JavaClass) {
|
fun f(c: JavaClass) {
|
||||||
c()
|
c()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun foo(o: JavaClass.OtherJavaClass) {
|
||||||
|
o()
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
Resolved c()
|
Resolved c()
|
||||||
|
Resolved o()
|
||||||
Searched references to JavaClass
|
Searched references to JavaClass
|
||||||
Searched references to parameter c of f(c: JavaClass) in non-Java files
|
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
|
||||||
@@ -1 +1,2 @@
|
|||||||
Implicit 'invoke' 2 c()
|
Implicit 'invoke' 2 c()
|
||||||
|
Implicit 'invoke' 6 o()
|
||||||
+4
-4
@@ -20,14 +20,14 @@ fun test() {
|
|||||||
listOf(Obj)[0](1)
|
listOf(Obj)[0](1)
|
||||||
}
|
}
|
||||||
|
|
||||||
class C(): B(12) {
|
|
||||||
override fun invoke(i: Int) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun cTest(c: C) {
|
fun cTest(c: C) {
|
||||||
c(5)
|
c(5)
|
||||||
|
|
||||||
some(12, "Irrelevant usage")
|
some(12, "Irrelevant usage")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class C(): B(12) {
|
||||||
|
override fun invoke(i: Int) {}
|
||||||
|
}
|
||||||
|
|
||||||
fun some(i: Int, s: String) {}
|
fun some(i: Int, s: String) {}
|
||||||
|
|||||||
Reference in New Issue
Block a user