Ignore sub-queries for other operators with the same receiver (KT-18566)

ExpressionsOfTypeProcessor searches for all occurence of expression
with given type. It start from usages of the class, searches for sub-classes,
declarations that return those classes, usages of these declarations,
and so on.

During this search, find usages for all operators that return the
subject type is executed as sub-queries. Full search for such queries
can't give addition types. And it also shouldn't give additional scopes
for search, because same scopes should be located by operands. In other
words, if sub-query can spot the scope of usage starting from the same
type, the original query should also process same scope.

 #KT-18566 Fixed
This commit is contained in:
Nikolay Krasko
2017-06-15 16:38:21 +03:00
parent 0f3dff44ac
commit 44d3b8fb1a
12 changed files with 168 additions and 54 deletions
@@ -43,7 +43,6 @@ import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.idea.references.KtDestructuringDeclarationReference
import org.jetbrains.kotlin.idea.search.excludeFileTypes
@@ -67,6 +66,7 @@ import java.util.*
class ExpressionsOfTypeProcessor(
private val typeToSearch: FuzzyType,
private val classToSearch: PsiClass?,
private val searchScope: SearchScope,
private val project: Project,
private val possibleMatchHandler: (KtExpression) -> Unit,
@@ -120,7 +120,7 @@ class ExpressionsOfTypeProcessor(
}
private val tasks = ArrayDeque<Task>()
private val taskSet = HashSet<Any>()
private val taskSet = HashSet<Task>()
private val scopesToUsePlainSearch = LinkedHashMap<KtFile, ArrayList<PsiElement>>()
@@ -130,7 +130,7 @@ class ExpressionsOfTypeProcessor(
ExpressionsOfTypeProcessor.Mode.ALWAYS_PLAIN -> true
ExpressionsOfTypeProcessor.Mode.PLAIN_WHEN_NEEDED -> searchScope is LocalSearchScope // for local scope it's faster to use plain search
}
if (usePlainSearch) {
if (usePlainSearch || classToSearch == null) {
possibleMatchesInScopeHandler(searchScope)
return
}
@@ -138,15 +138,13 @@ class ExpressionsOfTypeProcessor(
// optimization
if (runReadAction { searchScope is GlobalSearchScope && !FileTypeIndex.containsFileOfType(KotlinFileType.INSTANCE, searchScope) }) return
val psiClass = runReadAction { detectClassToSearch() }
// for class from library always use plain search because we cannot search usages in compiled code (we could though)
if (psiClass == null || !runReadAction { psiClass.isValid && ProjectRootsUtil.isInProjectSource(psiClass) }) {
if (classToSearch == null || !runReadAction { classToSearch.isValid && ProjectRootsUtil.isInProjectSource(classToSearch) }) {
possibleMatchesInScopeHandler(searchScope)
return
}
addClassToProcess(psiClass)
addClassToProcess(classToSearch)
processTasks()
@@ -161,16 +159,6 @@ class ExpressionsOfTypeProcessor(
}
}
private fun detectClassToSearch(): PsiClass? {
val classDescriptor = typeToSearch.type.constructor.declarationDescriptor ?: return null
val classDeclaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, classDescriptor)
return when (classDeclaration) {
is PsiClass -> classDeclaration
is KtClassOrObject -> classDeclaration.toLightClass()
else -> null
}
}
private fun addTask(task: Task) {
if (taskSet.add(task)) {
tasks.push(task)
@@ -208,7 +196,6 @@ class ExpressionsOfTypeProcessor(
return true
}
private fun addNonKotlinClassToProcess(classToSearch: PsiClass) {
if (!checkPsiClass(classToSearch)) {
return
@@ -398,7 +385,10 @@ class ExpressionsOfTypeProcessor(
}
@Suppress("NAME_SHADOWING")
data class ProcessCallableUsagesTask(val declaration: PsiElement, val processor: ReferenceProcessor, val scope: SearchScope) : Task {
data class ProcessCallableUsagesTask(
val declaration: PsiElement,
val processor: ReferenceProcessor,
val scope: SearchScope) : Task {
override fun perform() {
if (scope is LocalSearchScope) {
testLog { "Searched imported static member $declaration in ${scope.scope.toList()}" }
@@ -23,12 +23,14 @@ import com.intellij.psi.search.*
import com.intellij.util.Processor
import org.jetbrains.kotlin.asJava.elements.KtLightMethod
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.caches.resolve.getJavaOrKotlinMemberDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinRequestResultProcessor
import org.jetbrains.kotlin.idea.search.restrictToKotlinSources
@@ -192,7 +194,6 @@ abstract class OperatorReferenceSearcher<TReferenceElement : KtElement>(
}
//TODO: check no light elements here
private object SearchesInProgress : ThreadLocal<HashSet<PsiElement>>() {
override fun initialValue() = HashSet<PsiElement>()
}
@@ -207,14 +208,27 @@ abstract class OperatorReferenceSearcher<TReferenceElement : KtElement>(
}
fun run() {
val receiverType = runReadAction { extractReceiverType() } ?: return
val psiClass = runReadAction { receiverType.toPsiClass() }
val inProgress = SearchesInProgress.get()
if (!inProgress.add(targetDeclaration)) return //TODO: it's not quite correct
if (psiClass != null) {
if (!inProgress.add(psiClass)) {
testLog { "ExpressionOfTypeProcessor is already started for ${runReadAction { psiClass.qualifiedName }}. Exit for operator ${logPresentation(targetDeclaration)}." }
return
}
}
else {
if (!inProgress.add(targetDeclaration)) {
testLog { "ExpressionOfTypeProcessor is already started for operator ${logPresentation(targetDeclaration)}. Exit." }
return //TODO: it's not quite correct
}
}
try {
val receiverType = runReadAction { extractReceiverType() } ?: return
ExpressionsOfTypeProcessor(
receiverType,
psiClass,
searchScope,
project,
possibleMatchHandler = { expression -> processPossibleReceiverExpression(expression) },
@@ -222,7 +236,17 @@ abstract class OperatorReferenceSearcher<TReferenceElement : KtElement>(
).run()
}
finally {
inProgress.remove(targetDeclaration)
inProgress.remove(if (psiClass != null) psiClass else targetDeclaration)
}
}
private fun FuzzyType.toPsiClass(): PsiClass? {
val classDescriptor = type.constructor.declarationDescriptor ?: return null
val classDeclaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, classDescriptor)
return when (classDeclaration) {
is PsiClass -> classDeclaration
is KtClassOrObject -> classDeclaration.toLightClass()
else -> null
}
}
@@ -312,16 +336,18 @@ abstract class OperatorReferenceSearcher<TReferenceElement : KtElement>(
is LocalSearchScope -> {
scope
.map { element ->
" " + when (element) {
is KtFunctionLiteral -> element.text
is KtWhenEntry -> {
if (element.isElse)
"KtWhenEntry \"else\""
else
"KtWhenEntry \"" + element.conditions.joinToString(", ") { it.text } + "\""
" " + runReadAction {
when (element) {
is KtFunctionLiteral -> element.text
is KtWhenEntry -> {
if (element.isElse)
"KtWhenEntry \"else\""
else
"KtWhenEntry \"" + element.conditions.joinToString(", ") { it.text } + "\""
}
is KtNamedDeclaration -> element.node.elementType.toString() + ":" + element.name
else -> element.toString()
}
is KtNamedDeclaration -> element.node.elementType.toString() + ":" + element.name
else -> element.toString()
}
}
.toList()
@@ -333,4 +359,4 @@ abstract class OperatorReferenceSearcher<TReferenceElement : KtElement>(
}
}
}
}
@@ -1,5 +1,6 @@
Checked type of u1
Checked type of u2
ExpressionOfTypeProcessor is already started for UnaryNot. Exit for operator UnaryNot.not().
Resolved !u1
Searched references to UnaryNot
Searched references to UnaryNot.not() in non-Java files
@@ -4,6 +4,7 @@ Checked type of a2
Checked type of n1
Checked type of n2
Checked type of n2
ExpressionOfTypeProcessor is already started for A. Exit for operator parameter a of A(val a: A?, val n: Int).
Resolved (a1, n1)
Resolved (a2, n2)
Resolved (a2, n2)
@@ -2,6 +2,7 @@ Checked type of a1
Checked type of b
Checked type of n
Checked type of s
ExpressionOfTypeProcessor is already started for A. Exit for operator parameter b of A(val b: B, val n: Int).
Resolved (a1, s)
Resolved (b, n)
Searched references to A
+1
View File
@@ -1,4 +1,5 @@
Checked type of a
ExpressionOfTypeProcessor is already started for A. Exit for operator A.inc().
Resolved ++a
Resolved a++
Searched references to A
+2 -20
View File
@@ -1,39 +1,21 @@
Checked type of a
Checked type of a
Checked type of a1
Checked type of a1
Checked type of a2
Checked type of a2
Resolved A(0) + A(1)
ExpressionOfTypeProcessor is already started for A. Exit for operator A.plus(a: A).
ExpressionOfTypeProcessor is already started for A. Exit for operator A.plus(m: Int).
Resolved A(0) + A(1)
Resolved A(0) + A(1) + 2
Resolved A(0) + A(1) + 2
Resolved A(0) + A(1) + 2
Resolved A(0) + A(1) + 2
Resolved A(0) + A(1) + 2
Resolved a += 1
Resolved a += 1
Resolved a += A(1)
Resolved a += A(1)
Resolved a1 + 1
Resolved a1 + 1
Searched references to A
Searched references to A
Searched references to A.plus(a: A) in non-Java files
Searched references to A.plus(a: A) in non-Java files
Searched references to A.plus(m: Int) in non-Java files
Searched references to A.plus(m: Int) in non-Java files
Searched references to a in non-Java files
Searched references to a in non-Java files
Searched references to a1 in non-Java files
Searched references to a1 in non-Java files
Searched references to a2 in non-Java files
Searched references to a2 in non-Java files
Searched references to parameter a of A.plus(a: A) in non-Java files
Searched references to parameter a of A.plus(a: A) in non-Java files
Searched references to parameter array of test(array: Array<A>) in non-Java files
Searched references to parameter array of test(array: Array<A>) in non-Java files
Used plain search of A.plus(a: A) in LocalSearchScope:
CLASS:A
Used plain search of A.plus(m: Int) in LocalSearchScope:
CLASS:A
@@ -0,0 +1,39 @@
// PSI_ELEMENT: org.jetbrains.kotlin.psi.KtNamedFunction
// OPTIONS: usages
open class Diction {
operator fun <caret>minus(other: Diction): Diction { return Diction() }
operator fun plus(other: Diction): Diction { return Diction() }
}
operator fun Diction.times(other: Diction) = Diction()
class A
operator fun A.div(other: A) = Diction()
fun indirectDiction() = A() / A()
fun indirectPlusDiction() = indirectDiction() + indirectDiction()
val t = { d: Diction -> d + d }
val tt = { t(indirectDiction()) }
fun test1(d1: Diction, d2: Diction) {
val a = d1 + d2
val b = d1 - d2
val c = d1 * d2
val d = b - c
}
fun test2() {
val dInT2 = indirectDiction()
dInT2 - dInT2
}
fun test3() {
val dInT3 = indirectPlusDiction()
dInT3 - dInT3
}
fun test4() {
tt() - tt()
}
@@ -0,0 +1,61 @@
Checked type of a
Checked type of a
Checked type of b
Checked type of b
Checked type of c
Checked type of c
Checked type of d
Checked type of d
Checked type of dInT2
Checked type of dInT3
Checked type of div(other: A)
Checked type of indirectDiction()
Checked type of indirectDiction()
Checked type of indirectDiction()
Checked type of indirectPlusDiction()
Checked type of indirectPlusDiction()
Checked type of t
Checked type of t
Checked type of times(other: Diction)
Checked type of tt
Checked type of tt
ExpressionOfTypeProcessor is already started for Diction. Exit for operator Diction.minus(other: Diction).
ExpressionOfTypeProcessor is already started for Diction. Exit for operator Diction.plus(other: Diction).
ExpressionOfTypeProcessor is already started for Diction. Exit for operator times(other: Diction).
Resolved A() / A()
Resolved b - c
Resolved d1 - d2
Resolved dInT2 - dInT2
Resolved dInT3 - dInT3
Resolved tt() - tt()
Searched references to A
Searched references to Diction
Searched references to Diction.minus(other: Diction) in non-Java files
Searched references to Diction.plus(other: Diction) in non-Java files
Searched references to a in non-Java files
Searched references to b in non-Java files
Searched references to c in non-Java files
Searched references to d in non-Java files
Searched references to dInT2 in non-Java files
Searched references to dInT3 in non-Java files
Searched references to div(other: A) in non-Java files
Searched references to indirectDiction() in non-Java files
Searched references to indirectPlusDiction() in non-Java files
Searched references to parameter d of <anonymous>d: Diction in non-Java files
Searched references to parameter d1 of test1(d1: Diction, d2: Diction) in non-Java files
Searched references to parameter d2 of test1(d1: Diction, d2: Diction) in non-Java files
Searched references to parameter other of Diction.minus(other: Diction) in non-Java files
Searched references to parameter other of Diction.plus(other: Diction) in non-Java files
Searched references to parameter other of div(other: A) in non-Java files
Searched references to parameter other of times(other: Diction) in non-Java files
Searched references to t in non-Java files
Searched references to times(other: Diction) in non-Java files
Searched references to tt in non-Java files
Used plain search of Diction.minus(other: Diction) in LocalSearchScope:
CLASS:Diction
FUN:times
{ d: Diction -> d + d }
{ t(indirectDiction()) }
Used plain search of div(other: A) in LocalSearchScope:
CLASS:A
FUN:div
@@ -0,0 +1,5 @@
Function call 22 val b = d1 - d2
Function call 24 val d = b - c
Function call 29 dInT2 - dInT2
Function call 34 dInT3 - dInT3
Function call 38 tt() - tt()
@@ -1,3 +1,4 @@
ExpressionOfTypeProcessor is already started for A. Exit for operator A.unaryMinus().
Resolved -A(1)
Searched references to A
Searched references to A.unaryMinus() in non-Java files
@@ -169,6 +169,12 @@ public class FindUsagesTestGenerated extends AbstractFindUsagesTest {
doTest(fileName);
}
@TestMetadata("severalOperators.0.kt")
public void testSeveralOperators() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/conventions/severalOperators.0.kt");
doTest(fileName);
}
@TestMetadata("unaryMinus.0.kt")
public void testUnaryMinus() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/conventions/unaryMinus.0.kt");