Quick search of equals operator usages

This commit is contained in:
Valentin Kipyatkov
2016-09-24 21:24:46 +03:00
parent 309d82308f
commit ee64a1a2b7
16 changed files with 91 additions and 31 deletions

View File

@@ -192,14 +192,14 @@ object OperatorChecks : AbstractModifierChecks() {
Checks(RANGE_TO, MemberOrExtension, SingleValueParameter, NoDefaultAndVarargsCheck),
Checks(EQUALS, Member) {
fun DeclarationDescriptor.isAny() = this is ClassDescriptor && KotlinBuiltIns.isAny(this)
ensure(overriddenDescriptors.any { it.containingDeclaration.isAny() }) { "must override ''equals()'' in Any" }
ensure(containingDeclaration.isAny() || overriddenDescriptors.any { it.containingDeclaration.isAny() }) { "must override ''equals()'' in Any" }
},
Checks(COMPARE_TO, MemberOrExtension, ReturnsInt, SingleValueParameter, NoDefaultAndVarargsCheck),
Checks(BINARY_OPERATION_NAMES, MemberOrExtension, SingleValueParameter, NoDefaultAndVarargsCheck),
Checks(SIMPLE_UNARY_OPERATION_NAMES, MemberOrExtension, NoValueParameters),
Checks(listOf(INC, DEC), MemberOrExtension) {
val receiver = dispatchReceiverParameter ?: extensionReceiverParameter
ensure(receiver != null && (returnType?.let { it.isSubtypeOf(receiver.type) } ?: false)) {
ensure(receiver != null && (returnType?.isSubtypeOf(receiver.type) ?: false)) {
"receiver must be a supertype of the return type"
}
},

View File

@@ -22,6 +22,7 @@ import com.intellij.psi.search.SearchRequestCollector
import com.intellij.psi.search.SearchScope
import com.intellij.util.Processor
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions
import org.jetbrains.kotlin.lexer.KtSingleValueToken
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtElement
@@ -33,8 +34,9 @@ class BinaryOperatorReferenceSearcher(
private val operationTokens: List<KtSingleValueToken>,
searchScope: SearchScope,
consumer: Processor<PsiReference>,
optimizer: SearchRequestCollector
) : OperatorReferenceSearcher<KtBinaryExpression>(targetFunction, searchScope, consumer, optimizer, wordsToSearch = operationTokens.map { it.value }) {
optimizer: SearchRequestCollector,
options: KotlinReferencesSearchOptions
) : OperatorReferenceSearcher<KtBinaryExpression>(targetFunction, searchScope, consumer, optimizer, options, wordsToSearch = operationTokens.map { it.value }) {
override fun processPossibleReceiverExpression(expression: KtExpression) {
val binaryExpression = expression.parent as? KtBinaryExpression ?: return

View File

@@ -22,6 +22,7 @@ import com.intellij.psi.search.SearchRequestCollector
import com.intellij.psi.search.SearchScope
import com.intellij.util.Processor
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
@@ -30,8 +31,9 @@ class ContainsOperatorReferenceSearcher(
targetFunction: PsiElement,
searchScope: SearchScope,
consumer: Processor<PsiReference>,
optimizer: SearchRequestCollector
) : OperatorReferenceSearcher<KtOperationReferenceExpression>(targetFunction, searchScope, consumer, optimizer, wordsToSearch = listOf("in")) {
optimizer: SearchRequestCollector,
options: KotlinReferencesSearchOptions
) : OperatorReferenceSearcher<KtOperationReferenceExpression>(targetFunction, searchScope, consumer, optimizer, options, wordsToSearch = listOf("in")) {
private val OPERATION_TOKENS = setOf(KtTokens.IN_KEYWORD, KtTokens.NOT_IN)

View File

@@ -24,6 +24,7 @@ import com.intellij.util.Processor
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.references.KtDestructuringDeclarationReference
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
@@ -32,8 +33,9 @@ class DestructuringDeclarationReferenceSearcher(
private val componentIndex: Int,
searchScope: SearchScope,
consumer: Processor<PsiReference>,
optimizer: SearchRequestCollector
) : OperatorReferenceSearcher<KtDestructuringDeclaration>(targetDeclaration, searchScope, consumer, optimizer, wordsToSearch = listOf("(")) {
optimizer: SearchRequestCollector,
options: KotlinReferencesSearchOptions
) : OperatorReferenceSearcher<KtDestructuringDeclaration>(targetDeclaration, searchScope, consumer, optimizer, options, wordsToSearch = listOf("(")) {
override fun resolveTargetToDescriptor(): FunctionDescriptor? {
if (targetDeclaration is KtParameter) {

View File

@@ -125,10 +125,13 @@ class ExpressionsOfTypeProcessor(
return
}
val psiClass = runReadAction { detectClassToSearch() } ?: return
// 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 (!runReadAction { psiClass.isValid && ProjectRootsUtil.isInProjectSource (psiClass) }) {
if (psiClass == null || !runReadAction { psiClass.isValid && ProjectRootsUtil.isInProjectSource(psiClass) }) {
possibleMatchesInScopeHandler(searchScope)
return
}
@@ -149,8 +152,6 @@ class ExpressionsOfTypeProcessor(
}
private fun detectClassToSearch(): PsiClass? {
if (searchScope is GlobalSearchScope && !FileTypeIndex.containsFileOfType(KotlinFileType.INSTANCE, searchScope)) return null // optimization
val classDescriptor = typeToSearch.type.constructor.declarationDescriptor ?: return null
val classDeclaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, classDescriptor)
return when (classDeclaration) {

View File

@@ -23,10 +23,10 @@ import com.intellij.psi.search.SearchScope
import com.intellij.util.Processor
import org.jetbrains.kotlin.idea.references.KtArrayAccessReference
import org.jetbrains.kotlin.idea.references.readWriteAccess
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions
import org.jetbrains.kotlin.psi.KtArrayAccessExpression
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
class IndexingOperatorReferenceSearcher(
@@ -34,8 +34,9 @@ class IndexingOperatorReferenceSearcher(
searchScope: SearchScope,
consumer: Processor<PsiReference>,
optimizer: SearchRequestCollector,
options: KotlinReferencesSearchOptions,
private val isSet: Boolean
) : OperatorReferenceSearcher<KtArrayAccessExpression>(targetFunction, searchScope, consumer, optimizer, wordsToSearch = listOf("[")) {
) : OperatorReferenceSearcher<KtArrayAccessExpression>(targetFunction, searchScope, consumer, optimizer, options, wordsToSearch = listOf("[")) {
override fun processPossibleReceiverExpression(expression: KtExpression) {
val accessExpression = expression.parent as? KtArrayAccessExpression ?: return

View File

@@ -22,6 +22,7 @@ import com.intellij.psi.search.SearchRequestCollector
import com.intellij.psi.search.SearchScope
import com.intellij.util.Processor
import org.jetbrains.kotlin.idea.references.KtInvokeFunctionReference
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtExpression
@@ -31,8 +32,9 @@ class InvokeOperatorReferenceSearcher(
targetFunction: PsiElement,
searchScope: SearchScope,
consumer: Processor<PsiReference>,
optimizer: SearchRequestCollector
) : OperatorReferenceSearcher<KtCallExpression>(targetFunction, searchScope, consumer, optimizer, wordsToSearch = emptyList()) {
optimizer: SearchRequestCollector,
options: KotlinReferencesSearchOptions
) : OperatorReferenceSearcher<KtCallExpression>(targetFunction, searchScope, consumer, optimizer, options, wordsToSearch = emptyList()) {
override fun processPossibleReceiverExpression(expression: KtExpression) {
val callExpression = expression.parent as? KtCallExpression ?: return

View File

@@ -38,6 +38,7 @@ import org.jetbrains.kotlin.idea.util.FuzzyType
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.fuzzyExtensionReceiverType
import org.jetbrains.kotlin.idea.util.toFuzzyType
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
@@ -55,6 +56,7 @@ abstract class OperatorReferenceSearcher<TReferenceElement : KtElement>(
private val searchScope: SearchScope,
private val consumer: Processor<PsiReference>,
private val optimizer: SearchRequestCollector,
private val options: KotlinReferencesSearchOptions,
private val wordsToSearch: List<String>
) {
private val project = targetDeclaration.project
@@ -138,7 +140,7 @@ abstract class OperatorReferenceSearcher<TReferenceElement : KtElement>(
if (DataClassDescriptorResolver.isComponentLike(name)) {
if (!options.searchForComponentConventions) return null
val componentIndex = DataClassDescriptorResolver.getComponentIndex(name.asString())
return DestructuringDeclarationReferenceSearcher(declaration, componentIndex, searchScope, consumer, optimizer)
return DestructuringDeclarationReferenceSearcher(declaration, componentIndex, searchScope, consumer, optimizer, options)
}
if (!options.searchForOperatorConventions) return null
@@ -151,22 +153,32 @@ abstract class OperatorReferenceSearcher<TReferenceElement : KtElement>(
binaryOp != null -> {
val counterpartAssignmentOp = OperatorConventions.ASSIGNMENT_OPERATION_COUNTERPARTS.inverse()[binaryOp]
val operationTokens = listOf(binaryOp, counterpartAssignmentOp).filterNotNull()
return BinaryOperatorReferenceSearcher(declaration, operationTokens, searchScope, consumer, optimizer)
return BinaryOperatorReferenceSearcher(declaration, operationTokens, searchScope, consumer, optimizer, options)
}
assignmentOp != null -> return BinaryOperatorReferenceSearcher(declaration, listOf(assignmentOp), searchScope, consumer, optimizer)
assignmentOp != null ->
return BinaryOperatorReferenceSearcher(declaration, listOf(assignmentOp), searchScope, consumer, optimizer, options)
unaryOp != null -> return UnaryOperatorReferenceSearcher(declaration, unaryOp, searchScope, consumer, optimizer)
unaryOp != null ->
return UnaryOperatorReferenceSearcher(declaration, unaryOp, searchScope, consumer, optimizer, options)
name == OperatorNameConventions.INVOKE -> return InvokeOperatorReferenceSearcher(declaration, searchScope, consumer, optimizer)
name == OperatorNameConventions.INVOKE ->
return InvokeOperatorReferenceSearcher(declaration, searchScope, consumer, optimizer, options)
name == OperatorNameConventions.GET -> return IndexingOperatorReferenceSearcher(declaration, searchScope, consumer, optimizer, isSet = false)
name == OperatorNameConventions.GET ->
return IndexingOperatorReferenceSearcher(declaration, searchScope, consumer, optimizer, options, isSet = false)
name == OperatorNameConventions.SET -> return IndexingOperatorReferenceSearcher(declaration, searchScope, consumer, optimizer, isSet = true)
name == OperatorNameConventions.SET ->
return IndexingOperatorReferenceSearcher(declaration, searchScope, consumer, optimizer, options, isSet = true)
name == OperatorNameConventions.CONTAINS -> return ContainsOperatorReferenceSearcher(declaration, searchScope, consumer, optimizer)
name == OperatorNameConventions.CONTAINS ->
return ContainsOperatorReferenceSearcher(declaration, searchScope, consumer, optimizer, options)
else -> return null
name == OperatorNameConventions.EQUALS ->
return BinaryOperatorReferenceSearcher(declaration, listOf(KtTokens.EQEQ, KtTokens.EXCLEQ), searchScope, consumer, optimizer, options)
else ->
return null
}
}
@@ -246,7 +258,8 @@ abstract class OperatorReferenceSearcher<TReferenceElement : KtElement>(
if (wordsToSearch.isNotEmpty()) {
val unwrappedElement = targetDeclaration.namedUnwrappedElement ?: return
val resultProcessor = KotlinRequestResultProcessor(unwrappedElement,
filter = { ref -> isReferenceToCheck(ref) })
filter = { ref -> isReferenceToCheck(ref) },
options = options)
wordsToSearch.forEach {
optimizer.searchWord(it, scope.restrictToKotlinSources(), UsageSearchContext.IN_CODE, true, unwrappedElement, resultProcessor)
}

View File

@@ -22,10 +22,10 @@ import com.intellij.psi.search.SearchRequestCollector
import com.intellij.psi.search.SearchScope
import com.intellij.util.Processor
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions
import org.jetbrains.kotlin.lexer.KtSingleValueToken
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtUnaryExpression
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
@@ -34,8 +34,9 @@ class UnaryOperatorReferenceSearcher(
private val operationToken: KtSingleValueToken,
searchScope: SearchScope,
consumer: Processor<PsiReference>,
optimizer: SearchRequestCollector
) : OperatorReferenceSearcher<KtUnaryExpression>(targetFunction, searchScope, consumer, optimizer, wordsToSearch = listOf(operationToken.value)) {
optimizer: SearchRequestCollector,
options: KotlinReferencesSearchOptions
) : OperatorReferenceSearcher<KtUnaryExpression>(targetFunction, searchScope, consumer, optimizer, options, wordsToSearch = listOf(operationToken.value)) {
override fun processPossibleReceiverExpression(expression: KtExpression) {
val unaryExpression = expression.parent as? KtUnaryExpression ?: return

View File

@@ -48,7 +48,6 @@ val COMPARISON_OPERATIONS_TO_SEARCH = setOf(KtTokens.LT, KtTokens.GT)
fun Name.getOperationSymbolsToSearch(): Pair<Set<KtToken>, Class<*>>? {
when (this) {
OperatorNameConventions.COMPARE_TO -> return COMPARISON_OPERATIONS_TO_SEARCH to KtSimpleNameReference::class.java
OperatorNameConventions.EQUALS -> return EQUALS_OPERATIONS to KtSimpleNameReference::class.java
OperatorNameConventions.ITERATOR -> return IN_OPERATIONS_TO_SEARCH to KtForLoopInReference::class.java
in DELEGATE_ACCESSOR_NAMES -> return setOf(KtTokens.BY_KEYWORD) to KtPropertyDelegationMethodsReference::class.java
DelegatedPropertyResolver.PROPERTY_DELEGATED_FUNCTION_NAME -> return setOf(KtTokens.BY_KEYWORD) to KtPropertyDelegationMethodsReference::class.java

View File

@@ -36,7 +36,7 @@ class KotlinConventionMethodReferencesSearcher() : QueryExecutorBase<PsiReferenc
val identifier = Name.identifier(name)
val operatorSearcher = OperatorReferenceSearcher.create(
method, queryParameters.effectiveSearchScope, consumer, queryParameters.optimizer, KotlinReferencesSearchOptions.Empty)
method, queryParameters.effectiveSearchScope, consumer, queryParameters.optimizer, KotlinReferencesSearchOptions(acceptCallableOverrides = true))
if (operatorSearcher != null) {
operatorSearcher.run()
}

View File

@@ -0,0 +1 @@
Used plain search of kotlin.Any.equals(other: kotlin.Any?) in whole search scope

View File

@@ -0,0 +1,18 @@
// PSI_ELEMENT: org.jetbrains.kotlin.psi.KtNamedFunction
// OPTIONS: usages
// FIND_BY_REF
class A(val n: Int) {
fun foo(a: A) = a <caret>== this
override override fun equals(other: Any?): Boolean = TODO()
}
fun test() {
1 == 2
A(0) == A(1)
A(0) != A(1)
A(0) equals A(1)
A(0) === A(1)
A(0) !== A(1)
}

View File

@@ -0,0 +1,8 @@
Checked type of A.foo(a: A)
Resolved A(0) != A(1)
Resolved A(0) == A(1)
Resolved a == this
Searched references to A
Searched references to parameter a of A.foo(a: A) in non-Java files
Used plain search of A.equals(other: Any?) in LocalSearchScope:
CLASS:A

View File

@@ -0,0 +1,4 @@
Function call 13 A(0) == A(1)
Function call 14 A(0) != A(1)
Function call 15 A(0) equals A(1)
Function call 6 fun foo(a: A) = a == this

View File

@@ -96,6 +96,12 @@ public class FindUsagesTestGenerated extends AbstractFindUsagesTest {
doTest(fileName);
}
@TestMetadata("equalsNotAny.0.kt")
public void testEqualsNotAny() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/conventions/equalsNotAny.0.kt");
doTest(fileName);
}
@TestMetadata("forIteration.0.kt")
public void testForIteration() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/conventions/forIteration.0.kt");