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.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<PsiClass>(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<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)
|
||||
*/
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+2
-2
@@ -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()
|
||||
|
||||
+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
|
||||
Used plain search of JavaWithGroovyInvoke_0.invoke() in LocalSearchScope:
|
||||
JetFile: JavaWithGroovyInvoke.0.kt
|
||||
Used plain search of JavaWithGroovyInvoke_0.invoke() in whole search scope
|
||||
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
|
||||
@@ -4,4 +4,6 @@
|
||||
public class JavaClass {
|
||||
public void <caret>invoke() {
|
||||
}
|
||||
|
||||
public static class OtherJavaClass extends JavaClass {}
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
fun f(c: JavaClass) {
|
||||
c()
|
||||
}
|
||||
|
||||
fun foo(o: JavaClass.OtherJavaClass) {
|
||||
o()
|
||||
}
|
||||
@@ -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
|
||||
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)
|
||||
}
|
||||
|
||||
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) {}
|
||||
|
||||
Reference in New Issue
Block a user