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:
Nikolay Krasko
2017-04-18 17:54:36 +03:00
parent 102310e3c2
commit 111f0b1e66
8 changed files with 189 additions and 54 deletions
@@ -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)
}
@@ -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()
@@ -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
View File
@@ -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) {}