[FIR IDE] Separate find usages logic from descriptors

This commit is contained in:
Igor Yakovlev
2020-08-21 01:04:35 +03:00
parent b102042dd8
commit e30f09d513
76 changed files with 1422 additions and 661 deletions
@@ -79,7 +79,6 @@ wrapped.into.a.reference.object.to.be.modified.when.captured.in.a.closure=Wrappe
smart.cast.to.0.for.1.call=Smart cast to {0} (for {1} call)
smart.cast.to.0=Smart cast to {0}
replace.overloaded.operator.with.function.call=Replace overloaded operator with function call
searching.for.implicit.usages=Searching for implicit usages...
class.initializer=<class initializer>
object.0=object{0}
show.non.public=Show non-public
@@ -1,29 +0,0 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.search
import com.intellij.psi.impl.search.IndexPatternBuilder
import com.intellij.psi.tree.IElementType
import org.jetbrains.kotlin.lexer.KtTokens
abstract class IndexPatternBuilderAdapter : IndexPatternBuilder {
override fun getCommentStartDelta(tokenType: IElementType, tokenText: CharSequence): Int {
return when (tokenType) {
KtTokens.EOL_COMMENT -> 2
KtTokens.BLOCK_COMMENT -> 2
KtTokens.DOC_COMMENT -> 3
else -> 0
}
}
override fun getCharsAllowedInContinuationPrefix(tokenType: IElementType): String {
return when (tokenType) {
KtTokens.BLOCK_COMMENT -> "*"
KtTokens.DOC_COMMENT -> "*"
else -> ""
}
}
}
@@ -1,47 +0,0 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.search
import com.intellij.lexer.Lexer
import com.intellij.psi.PsiFile
import com.intellij.psi.tree.IElementType
import com.intellij.psi.tree.TokenSet
import org.jetbrains.kotlin.kdoc.lexer.KDocTokens
import org.jetbrains.kotlin.lexer.KotlinLexer
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtFile
class KotlinIndexPatternBuilder : IndexPatternBuilderAdapter() {
private companion object {
private val TODO_COMMENT_TOKENS = TokenSet.orSet(KtTokens.COMMENTS, TokenSet.create(KDocTokens.KDOC))
}
override fun getCommentTokenSet(file: PsiFile): TokenSet? {
return if (file is KtFile) TODO_COMMENT_TOKENS else null
}
override fun getIndexingLexer(file: PsiFile): Lexer? {
return if (file is KtFile) KotlinLexer() else null
}
override fun getCommentStartDelta(tokenType: IElementType?): Int = 0
override fun getCommentEndDelta(tokenType: IElementType?): Int = when (tokenType) {
KtTokens.BLOCK_COMMENT -> "*/".length
else -> 0
}
}
@@ -1,149 +0,0 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.search
import com.google.common.collect.ImmutableSet
import com.intellij.lexer.Lexer
import com.intellij.psi.TokenType
import com.intellij.psi.impl.cache.impl.BaseFilterLexer
import com.intellij.psi.impl.cache.impl.IdAndToDoScannerBasedOnFilterLexer
import com.intellij.psi.impl.cache.impl.OccurrenceConsumer
import com.intellij.psi.impl.cache.impl.id.LexerBasedIdIndexer
import com.intellij.psi.impl.cache.impl.todo.LexerBasedTodoIndexer
import com.intellij.psi.search.UsageSearchContext
import com.intellij.psi.tree.IElementType
import com.intellij.psi.tree.TokenSet
import org.jetbrains.kotlin.kdoc.lexer.KDocTokens
import org.jetbrains.kotlin.lexer.KotlinLexer
import org.jetbrains.kotlin.lexer.KtToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.types.expressions.OperatorConventions
import java.util.*
const val KOTLIN_NAMED_ARGUMENT_SEARCH_CONTEXT: Short = 0x20
private val ALL_SEARCHABLE_OPERATIONS: ImmutableSet<KtToken> = ImmutableSet
.builder<KtToken>()
.addAll(OperatorConventions.UNARY_OPERATION_NAMES.keys)
.addAll(OperatorConventions.BINARY_OPERATION_NAMES.keys)
.addAll(OperatorConventions.ASSIGNMENT_OPERATIONS.keys)
.addAll(OperatorConventions.COMPARISON_OPERATIONS)
.addAll(OperatorConventions.EQUALS_OPERATIONS)
.addAll(OperatorConventions.IN_OPERATIONS)
.add(KtTokens.LBRACKET)
.add(KtTokens.BY_KEYWORD)
.build()
class KotlinFilterLexer(private val occurrenceConsumer: OccurrenceConsumer) : BaseFilterLexer(KotlinLexer(), occurrenceConsumer) {
private companion object {
private val CODE_TOKENS = TokenSet.orSet(
TokenSet.create(*ALL_SEARCHABLE_OPERATIONS.toTypedArray()),
TokenSet.create(KtTokens.IDENTIFIER)
)
private val COMMENT_TOKENS = TokenSet.orSet(KtTokens.COMMENTS, TokenSet.create(KDocTokens.KDOC))
private const val MAX_PREV_TOKENS = 2
}
private val prevTokens = ArrayDeque<IElementType>(MAX_PREV_TOKENS)
private var prevTokenStart = -1
private var prevTokenEnd = -1
override fun advance() {
val tokenType = myDelegate.tokenType
when (tokenType) {
KtTokens.EQ -> {
if (prevTokens.peekFirst() == KtTokens.IDENTIFIER) {
val prevPrev = prevTokens.elementAtOrNull(1)
if (prevPrev == KtTokens.COMMA || prevPrev == KtTokens.LPAR) {
occurrenceConsumer.addOccurrence(
bufferSequence,
null,
prevTokenStart,
prevTokenEnd,
KOTLIN_NAMED_ARGUMENT_SEARCH_CONTEXT.toInt()
)
}
}
}
KtTokens.LPAR -> {
if (isMultiDeclarationPosition()) {
addOccurrenceInToken(UsageSearchContext.IN_CODE.toInt())
}
}
KtTokens.IDENTIFIER -> {
if (myDelegate.tokenText.startsWith("`")) {
scanWordsInToken(UsageSearchContext.IN_CODE.toInt(), false, false)
} else {
addOccurrenceInToken(UsageSearchContext.IN_CODE.toInt())
if (myDelegate.tokenText == "TODO") {
// Heuristics to reduce mismatches between indexer and searcher. The searcher returns only occurrences of TO_DO
// as the callee of a call expression, but we can't tell calls and other usages apart based on limited lexer context,
// so we just exclude occurrences in declaration names (and even that doesn't work precisely because it doesn't handle
// declarations with type parameters)
val prevToken = prevTokens.peekFirst()
if (prevToken != KtTokens.FUN_KEYWORD && prevToken != KtTokens.VAR_KEYWORD && prevToken != KtTokens.VAL_KEYWORD && prevToken != KtTokens.CLASS_KEYWORD) {
advanceTodoItemCountsInToken()
}
}
}
}
in CODE_TOKENS -> addOccurrenceInToken(UsageSearchContext.IN_CODE.toInt())
in KtTokens.STRINGS -> scanWordsInToken(UsageSearchContext.IN_STRINGS + UsageSearchContext.IN_FOREIGN_LANGUAGES, false, true)
in COMMENT_TOKENS -> {
scanWordsInToken(UsageSearchContext.IN_COMMENTS.toInt(), false, false)
advanceTodoItemCountsInToken()
}
}
if (tokenType != TokenType.WHITE_SPACE && tokenType !in COMMENT_TOKENS) {
if (prevTokens.size == MAX_PREV_TOKENS) {
prevTokens.removeLast()
}
prevTokens.addFirst(tokenType)
prevTokenStart = tokenStart
prevTokenEnd = tokenEnd
}
myDelegate.advance()
}
private fun isMultiDeclarationPosition(): Boolean {
val first = prevTokens.peekFirst()
if (first == KtTokens.VAL_KEYWORD || first == KtTokens.VAR_KEYWORD) return true
return first == KtTokens.LPAR && prevTokens.elementAtOrNull(1) == KtTokens.FOR_KEYWORD
}
}
class KotlinIdIndexer : LexerBasedIdIndexer() {
override fun createLexer(consumer: OccurrenceConsumer): Lexer = KotlinFilterLexer(consumer)
override fun getVersion() = 3
}
class KotlinTodoIndexer : LexerBasedTodoIndexer(), IdAndToDoScannerBasedOnFilterLexer {
override fun getVersion() = 2
override fun createLexer(consumer: OccurrenceConsumer) = KotlinFilterLexer(consumer)
}
@@ -1,255 +0,0 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.search
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiModifier
import com.intellij.psi.search.PsiShortNamesCache
import com.intellij.psi.util.CachedValue
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.asJava.ImpreciseResolveResult
import org.jetbrains.kotlin.asJava.ImpreciseResolveResult.*
import org.jetbrains.kotlin.idea.caches.project.getNullableModuleInfo
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener
import org.jetbrains.kotlin.idea.compiler.IDELanguageSettingsProvider
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.project.findAnalyzerServices
import org.jetbrains.kotlin.idea.stubindex.KotlinTypeAliasShortNameIndex
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.ImportPath
import java.util.concurrent.atomic.AtomicInteger
/**
* Can quickly check whether a short name reference in a given file can resolve to the class/interface/type alias
* with the given qualified name.
*/
class PsiBasedClassResolver @TestOnly constructor(private val targetClassFqName: String) {
private val targetShortName = targetClassFqName.substringAfterLast('.')
private val targetPackage = targetClassFqName.substringBeforeLast('.', "")
/**
* Qualified names of packages which contain classes with the same short name as the target class.
*/
private val conflictingPackages = mutableListOf<String>()
/**
* Qualified names of packages which contain typealiases with the same short name as the target class
* (which may or may not resolve to the target class).
*/
private val packagesWithTypeAliases = mutableListOf<String>()
private var forceAmbiguity: Boolean = false
private var forceAmbiguityForInnerAnnotations: Boolean = false
private var forceAmbiguityForNonAnnotations: Boolean = false
companion object {
@get:TestOnly
val attempts = AtomicInteger()
@get:TestOnly
val trueHits = AtomicInteger()
@get:TestOnly
val falseHits = AtomicInteger()
private val PSI_BASED_CLASS_RESOLVER_KEY = Key<CachedValue<PsiBasedClassResolver>>("PsiBasedClassResolver")
fun getInstance(target: PsiClass): PsiBasedClassResolver {
target.getUserData(PSI_BASED_CLASS_RESOLVER_KEY)?.let { return it.value }
val cachedValue = CachedValuesManager.getManager(target.project).createCachedValue(
{
CachedValueProvider.Result(
PsiBasedClassResolver(target),
KotlinCodeBlockModificationListener.getInstance(target.project).kotlinOutOfCodeBlockTracker
)
}, false
)
target.putUserData(PSI_BASED_CLASS_RESOLVER_KEY, cachedValue)
return cachedValue.value
}
}
private constructor(target: PsiClass) : this(target.qualifiedName ?: "") {
if (target.qualifiedName == null || target.containingClass != null || targetPackage.isEmpty()) {
forceAmbiguity = true
return
}
runReadAction {
findPotentialClassConflicts(target)
findPotentialTypeAliasConflicts(target)
}
}
private fun findPotentialClassConflicts(target: PsiClass) {
val candidates = PsiShortNamesCache.getInstance(target.project).getClassesByName(targetShortName, target.project.allScope())
for (candidate in candidates) {
// An inner class can be referenced by short name in subclasses without an explicit import
if (candidate.containingClass != null && !candidate.hasModifierProperty(PsiModifier.PRIVATE)) {
if (candidate.isAnnotationType) {
forceAmbiguityForInnerAnnotations = true
} else {
forceAmbiguityForNonAnnotations = true
}
break
}
if (candidate.qualifiedName == target.qualifiedName) {
// File with same FQ name in another module, don't bother with analyzing dependencies
if (candidate.navigationElement.containingFile != target.navigationElement.containingFile) {
forceAmbiguity = true
break
}
} else {
candidate.qualifiedName?.substringBeforeLast('.', "")?.let { candidatePackage ->
if (candidatePackage == "")
forceAmbiguity = true
else
conflictingPackages.add(candidatePackage)
}
}
}
}
private fun findPotentialTypeAliasConflicts(target: PsiClass) {
val candidates = KotlinTypeAliasShortNameIndex.getInstance().get(targetShortName, target.project, target.project.allScope())
for (candidate in candidates) {
packagesWithTypeAliases.add(candidate.containingKtFile.packageFqName.asString())
}
}
@TestOnly
fun addConflict(fqName: String) {
conflictingPackages.add(fqName.substringBeforeLast('.'))
}
/**
* Checks if a reference with the short name of [targetClassFqName] in the given file will resolve
* to the target class.
*
* @return true if it will definitely resolve to that class, false if it will definitely resolve to something else,
* null if full resolve is required to answer that question.
*/
fun canBeTargetReference(ref: KtSimpleNameExpression): ImpreciseResolveResult {
attempts.incrementAndGet()
// The names can be different if the target was imported via an import alias
if (ref.getReferencedName() != targetShortName) {
return UNSURE
}
// Names in expressions can conflict with local declarations and methods of implicit receivers,
// so we can't find out what they refer to without a full resolve.
val userType = ref.getStrictParentOfType<KtUserType>() ?: return UNSURE
val parentAnnotation = userType.getParentOfTypeAndBranch<KtAnnotationEntry> { typeReference }
if (forceAmbiguityForNonAnnotations && parentAnnotation == null) return UNSURE
//For toplevel declarations it's fine to resolve by imports
val declaration = parentAnnotation?.getParentOfType<KtDeclaration>(true)
if (forceAmbiguityForInnerAnnotations && declaration?.parent !is KtFile) return UNSURE
if (forceAmbiguity) return UNSURE
val qualifiedCheckResult = checkQualifiedReferenceToTarget(ref)
if (qualifiedCheckResult != null) return qualifiedCheckResult.returnValue
val file = ref.containingKtFile
var result: Result = Result.NothingFound
when (file.packageFqName.asString()) {
targetPackage -> result = result.changeTo(Result.Found)
in conflictingPackages -> result = result.changeTo(Result.FoundOther)
in packagesWithTypeAliases -> return UNSURE
}
for (importPath in file.getDefaultImports()) {
result = analyzeSingleImport(result, importPath.fqName, importPath.isAllUnder, importPath.alias?.asString())
if (result == Result.Ambiguity) return UNSURE
}
for (importDirective in file.importDirectives) {
result = analyzeSingleImport(result, importDirective.importedFqName, importDirective.isAllUnder, importDirective.aliasName)
if (result == Result.Ambiguity) return UNSURE
}
if (result.returnValue == MATCH) {
trueHits.incrementAndGet()
} else if (result.returnValue == NO_MATCH) {
falseHits.incrementAndGet()
}
return result.returnValue
}
private fun analyzeSingleImport(result: Result, importedFqName: FqName?, isAllUnder: Boolean, aliasName: String?): Result {
if (!isAllUnder) {
if (importedFqName?.asString() == targetClassFqName &&
(aliasName == null || aliasName == targetShortName)
) {
return result.changeTo(Result.Found)
} else if (importedFqName?.shortName()?.asString() == targetShortName &&
importedFqName.parent().asString() in conflictingPackages &&
aliasName == null
) {
return result.changeTo(Result.FoundOther)
} else if (importedFqName?.shortName()?.asString() == targetShortName &&
importedFqName.parent().asString() in packagesWithTypeAliases &&
aliasName == null
) {
return Result.Ambiguity
} else if (aliasName == targetShortName) {
return result.changeTo(Result.FoundOther)
}
} else {
when {
importedFqName?.asString() == targetPackage -> return result.changeTo(Result.Found)
importedFqName?.asString() in conflictingPackages -> return result.changeTo(Result.FoundOther)
importedFqName?.asString() in packagesWithTypeAliases -> return Result.Ambiguity
}
}
return result
}
private fun checkQualifiedReferenceToTarget(ref: KtSimpleNameExpression): Result? {
// A qualified name can resolve to the target element even if it's not imported,
// but it can also resolve to something else e.g. if the file defines a class with the same name
// as the top-level package of the target class.
val qualifier = (ref.parent as? KtUserType)?.qualifier
if (qualifier != null) {
if (qualifier.text == targetPackage) return Result.Ambiguity
return Result.FoundOther
}
return null
}
enum class Result(val returnValue: ImpreciseResolveResult) {
NothingFound(NO_MATCH),
Found(MATCH),
FoundOther(NO_MATCH),
Ambiguity(UNSURE)
}
private fun Result.changeTo(newResult: Result): Result {
if (this == Result.NothingFound || this.returnValue == newResult.returnValue) {
return newResult
}
return Result.Ambiguity
}
}
private fun KtFile.getDefaultImports(): List<ImportPath> {
val moduleInfo = getNullableModuleInfo() ?: return emptyList()
return TargetPlatformDetector.getPlatform(this).findAnalyzerServices(project).getDefaultImports(
IDELanguageSettingsProvider.getLanguageVersionSettings(moduleInfo, project),
includeLowPriorityImports = true
)
}
@@ -1,46 +0,0 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.search.declarationsSearch
import com.intellij.psi.PsiAnonymousClass
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiModifier
import com.intellij.psi.search.searches.ClassInheritorsSearch
import com.intellij.util.EmptyQuery
import com.intellij.util.Query
import org.jetbrains.kotlin.asJava.toLightClassWithBuiltinMapping
import org.jetbrains.kotlin.idea.caches.lightClasses.KtFakeLightClass
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.KtClassOrObject
fun HierarchySearchRequest<*>.searchInheritors(): Query<PsiClass> {
val psiClass: PsiClass = when (originalElement) {
is KtClassOrObject -> runReadAction { originalElement.toLightClassWithBuiltinMapping() ?: KtFakeLightClass(originalElement) }
is PsiClass -> originalElement
else -> null
} ?: return EmptyQuery.getEmptyQuery()
return ClassInheritorsSearch.search(
psiClass,
searchScope,
searchDeeply,
/* checkInheritance = */ true,
/* includeAnonymous = */ true
)
}
fun PsiClass.isInheritable(): Boolean = !(this is PsiAnonymousClass || hasModifierProperty(PsiModifier.FINAL))
@@ -1,120 +0,0 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.search.declarationsSearch
import com.intellij.openapi.application.QueryExecutorBase
import com.intellij.openapi.progress.ProgressIndicatorProvider
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.search.SearchScope
import com.intellij.util.*
import org.jetbrains.kotlin.psi.psiUtil.contains
import java.util.*
interface DeclarationSearchRequest<in T> {
val project: Project
val searchScope: SearchScope
}
interface SearchRequestWithElement<T : PsiElement> : DeclarationSearchRequest<T> {
val originalElement: T
override val project: Project get() = originalElement.project
}
abstract class DeclarationsSearch<T : PsiElement, R : DeclarationSearchRequest<T>> : QueryFactory<T, R>() {
init {
registerExecutor(
object : QueryExecutorBase<T, R>(true) {
override fun processQuery(queryParameters: R, consumer: Processor<in T>) {
doSearch(queryParameters, consumer)
}
}
)
}
final override fun registerExecutor(executor: QueryExecutor<T, R>) {
super.registerExecutor(executor)
}
protected abstract fun doSearch(request: R, consumer: Processor<in T>)
protected open fun isApplicable(request: R): Boolean = true
fun search(request: R): Query<T> = if (isApplicable(request)) createUniqueResultsQuery(request) else EmptyQuery.getEmptyQuery<T>()
}
class HierarchySearchRequest<T : PsiElement>(
override val originalElement: T,
override val searchScope: SearchScope,
val searchDeeply: Boolean = true
) : SearchRequestWithElement<T> {
fun <U : PsiElement> copy(newOriginalElement: U): HierarchySearchRequest<U> =
HierarchySearchRequest(newOriginalElement, searchScope, searchDeeply)
}
interface HierarchyTraverser<T> {
fun nextElements(current: T): Iterable<T>
fun shouldDescend(element: T): Boolean
fun forEach(initialElement: T, body: (T) -> Unit) {
val stack = Stack<T>()
val processed = HashSet<T>()
stack.push(initialElement)
while (!stack.isEmpty()) {
ProgressIndicatorProvider.checkCanceled()
val current = stack.pop()!!
if (!processed.add(current)) continue
for (next in nextElements(current)) {
ProgressIndicatorProvider.checkCanceled()
body(next)
if (shouldDescend(next)) {
stack.push(next)
}
}
}
}
}
fun <T : PsiElement> Processor<in T>.consumeHierarchy(request: SearchRequestWithElement<T>, traverser: HierarchyTraverser<T>) {
traverser.forEach(request.originalElement) { element ->
if (element in request.searchScope) {
process(element)
}
}
}
abstract class HierarchySearch<T : PsiElement>(
private val traverser: HierarchyTraverser<T>
) : DeclarationsSearch<T, HierarchySearchRequest<T>>() {
protected open fun doSearchAll(request: HierarchySearchRequest<T>, consumer: Processor<in T>) {
consumer.consumeHierarchy(request, traverser)
}
protected abstract fun doSearchDirect(request: HierarchySearchRequest<T>, consumer: Processor<in T>)
override fun doSearch(request: HierarchySearchRequest<T>, consumer: Processor<in T>) {
if (request.searchDeeply) {
doSearchAll(request, consumer)
} else {
doSearchDirect(request, consumer)
}
}
}
@@ -1,219 +0,0 @@
/*
* Copyright 2000-2017 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.search.declarationsSearch
import com.intellij.psi.*
import com.intellij.psi.search.SearchScope
import com.intellij.psi.search.searches.AllOverridingMethodsSearch
import com.intellij.psi.search.searches.DirectClassInheritorsSearch
import com.intellij.psi.search.searches.FunctionalExpressionSearch
import com.intellij.psi.search.searches.OverridingMethodsSearch
import com.intellij.psi.util.MethodSignatureUtil
import com.intellij.psi.util.PsiUtil
import com.intellij.psi.util.TypeConversionUtil
import com.intellij.util.EmptyQuery
import com.intellij.util.MergeQuery
import com.intellij.util.Processor
import com.intellij.util.Query
import org.jetbrains.kotlin.asJava.getRepresentativeLightMethod
import org.jetbrains.kotlin.asJava.toLightMethods
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.isOverridable
import org.jetbrains.kotlin.idea.caches.lightClasses.KtFakeLightClass
import org.jetbrains.kotlin.idea.caches.lightClasses.KtFakeLightMethod
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.getDeepestSuperDeclarations
import org.jetbrains.kotlin.idea.core.isOverridable
import org.jetbrains.kotlin.idea.search.allScope
import org.jetbrains.kotlin.idea.search.excludeKotlinSources
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.types.substitutions.getTypeSubstitutor
import org.jetbrains.kotlin.util.findCallableMemberBySignature
import java.util.*
fun PsiElement.isOverridableElement(): Boolean = when (this) {
is PsiMethod -> PsiUtil.canBeOverridden(this)
is KtDeclaration -> isOverridable()
else -> false
}
fun HierarchySearchRequest<*>.searchOverriders(): Query<PsiMethod> {
val psiMethods = runReadAction { originalElement.toLightMethods() }
if (psiMethods.isEmpty()) return EmptyQuery.getEmptyQuery()
return psiMethods
.map { psiMethod -> KotlinPsiMethodOverridersSearch.search(copy(psiMethod)) }
.reduce { query1, query2 -> MergeQuery(query1, query2) }
}
object KotlinPsiMethodOverridersSearch : HierarchySearch<PsiMethod>(PsiMethodOverridingHierarchyTraverser) {
fun searchDirectOverriders(psiMethod: PsiMethod): Iterable<PsiMethod> {
fun PsiMethod.isAcceptable(inheritor: PsiClass, baseMethod: PsiMethod, baseClass: PsiClass): Boolean =
when {
hasModifierProperty(PsiModifier.STATIC) -> false
baseMethod.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) ->
JavaPsiFacade.getInstance(project).arePackagesTheSame(baseClass, inheritor)
else -> true
}
val psiClass = psiMethod.containingClass ?: return Collections.emptyList()
val classToMethod = LinkedHashMap<PsiClass, PsiMethod>()
val classTraverser = object : HierarchyTraverser<PsiClass> {
override fun nextElements(current: PsiClass): Iterable<PsiClass> =
DirectClassInheritorsSearch.search(
current,
current.project.allScope(),
/* includeAnonymous = */ true
)
override fun shouldDescend(element: PsiClass): Boolean =
element.isInheritable() && !classToMethod.containsKey(element)
}
classTraverser.forEach(psiClass) { inheritor ->
val substitutor = TypeConversionUtil.getSuperClassSubstitutor(psiClass, inheritor, PsiSubstitutor.EMPTY)
val signature = psiMethod.getSignature(substitutor)
val candidate = MethodSignatureUtil.findMethodBySuperSignature(inheritor, signature, false)
if (candidate != null && candidate.isAcceptable(inheritor, psiMethod, psiClass)) {
classToMethod[inheritor] = candidate
}
}
return classToMethod.values
}
override fun isApplicable(request: HierarchySearchRequest<PsiMethod>): Boolean =
runReadAction { request.originalElement.isOverridableElement() }
override fun doSearchDirect(request: HierarchySearchRequest<PsiMethod>, consumer: Processor<in PsiMethod>) {
searchDirectOverriders(request.originalElement).forEach { method -> consumer.process(method) }
}
}
object PsiMethodOverridingHierarchyTraverser : HierarchyTraverser<PsiMethod> {
override fun nextElements(current: PsiMethod): Iterable<PsiMethod> = KotlinPsiMethodOverridersSearch.searchDirectOverriders(current)
override fun shouldDescend(element: PsiMethod): Boolean = PsiUtil.canBeOverridden(element)
}
fun PsiElement.toPossiblyFakeLightMethods(): List<PsiMethod> {
if (this is PsiMethod) return listOf(this)
val element = unwrapped ?: return emptyList()
val lightMethods = element.toLightMethods()
if (lightMethods.isNotEmpty()) return lightMethods
return if (element is KtNamedDeclaration) listOfNotNull(KtFakeLightMethod.get(element)) else emptyList()
}
private fun forEachKotlinOverride(
ktClass: KtClass,
members: List<KtNamedDeclaration>,
scope: SearchScope,
processor: (superMember: PsiElement, overridingMember: PsiElement) -> Boolean
): Boolean {
val baseClassDescriptor = runReadAction { ktClass.unsafeResolveToDescriptor() as ClassDescriptor }
val baseDescriptors =
runReadAction { members.mapNotNull { it.unsafeResolveToDescriptor() as? CallableMemberDescriptor }.filter { it.isOverridable } }
if (baseDescriptors.isEmpty()) return true
HierarchySearchRequest(ktClass, scope, true).searchInheritors().forEach(Processor { psiClass ->
val inheritor = psiClass.unwrapped as? KtClassOrObject ?: return@Processor true
runReadAction {
val inheritorDescriptor = inheritor.unsafeResolveToDescriptor() as ClassDescriptor
val substitutor =
getTypeSubstitutor(baseClassDescriptor.defaultType, inheritorDescriptor.defaultType) ?: return@runReadAction true
baseDescriptors.forEach {
val superMember = it.source.getPsi()!!
val overridingDescriptor = (it.substitute(substitutor) as? CallableMemberDescriptor)?.let { memberDescriptor ->
inheritorDescriptor.findCallableMemberBySignature(memberDescriptor)
}
val overridingMember = overridingDescriptor?.source?.getPsi()
if (overridingMember != null) {
if (!processor(superMember, overridingMember)) return@runReadAction false
}
}
true
}
})
return true
}
fun KtNamedDeclaration.forEachOverridingElement(
scope: SearchScope = runReadAction { useScope },
processor: (superMember: PsiElement, overridingMember: PsiElement) -> Boolean
): Boolean {
val ktClass = runReadAction { containingClassOrObject as? KtClass } ?: return true
toLightMethods().forEach { baseMethod ->
if (!OverridingMethodsSearch.search(baseMethod, scope.excludeKotlinSources(), true).all { processor(baseMethod, it) }) return false
}
return forEachKotlinOverride(ktClass, listOf(this), scope) { baseElement, overrider -> processor(baseElement, overrider) }
}
fun PsiMethod.forEachOverridingMethod(
scope: SearchScope = runReadAction { useScope },
processor: (PsiMethod) -> Boolean
): Boolean {
if (this !is KtFakeLightMethod) {
if (!OverridingMethodsSearch.search(this, scope.excludeKotlinSources(), true).forEach(processor)) return false
}
val ktMember = this.unwrapped as? KtNamedDeclaration ?: return true
val ktClass = runReadAction { ktMember.containingClassOrObject as? KtClass } ?: return true
return forEachKotlinOverride(ktClass, listOf(ktMember), scope) { _, overrider ->
val lightMethods = runReadAction { overrider.toPossiblyFakeLightMethods().distinctBy { it.unwrapped } }
lightMethods.all { processor(it) }
}
}
fun PsiMethod.forEachImplementation(
scope: SearchScope = runReadAction { useScope },
processor: (PsiElement) -> Boolean
): Boolean {
return forEachOverridingMethod(scope, processor)
&& FunctionalExpressionSearch.search(this, scope.excludeKotlinSources()).forEach(processor)
}
fun PsiClass.forEachDeclaredMemberOverride(processor: (superMember: PsiElement, overridingMember: PsiElement) -> Boolean) {
val scope = runReadAction { useScope }
if (this !is KtFakeLightClass) {
AllOverridingMethodsSearch.search(this, scope.excludeKotlinSources()).all { processor(it.first, it.second) }
}
val ktClass = unwrapped as? KtClass ?: return
val members = ktClass.declarations.filterIsInstance<KtNamedDeclaration>() +
ktClass.primaryConstructorParameters.filter { it.hasValOrVar() }
forEachKotlinOverride(ktClass, members, scope, processor)
}
fun findDeepestSuperMethodsNoWrapping(method: PsiElement): List<PsiElement> {
val element = method.unwrapped
return when (element) {
is PsiMethod -> element.findDeepestSuperMethods().toList()
is KtCallableDeclaration -> {
val descriptor = element.resolveToDescriptorIfAny() as? CallableMemberDescriptor ?: return emptyList()
descriptor.getDeepestSuperDeclarations(false).mapNotNull {
it.source.getPsi() ?: DescriptorToSourceUtilsIde.getAnyDeclaration(element.project, it)
}
}
else -> emptyList()
}
}
fun findDeepestSuperMethodsKotlinAware(method: PsiElement) =
findDeepestSuperMethodsNoWrapping(method).mapNotNull { it.getRepresentativeLightMethod() }
@@ -1,27 +0,0 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.search.ideaExtensions
import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.KtImportAlias
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector
class KotlinImportAliasGotoDeclarationHandler : GotoDeclarationHandler {
override fun getGotoDeclarationTargets(sourceElement: PsiElement?, offset: Int, editor: Editor?): Array<PsiElement>? {
val importAlias = sourceElement?.parent as? KtImportAlias ?: return null
val result =
runReadAction {
importAlias.importDirective?.importedReference?.getQualifiedElementSelector()?.mainReference?.multiResolve(false)
} ?: return null
return result.mapNotNull { it.element }.toTypedArray()
}
}
@@ -1,74 +0,0 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.search.ideaExtensions
import com.intellij.codeInsight.highlighting.JavaReadWriteAccessDetector
import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
import org.jetbrains.kotlin.asJava.elements.KtLightMethod
import org.jetbrains.kotlin.idea.references.ReferenceAccess
import org.jetbrains.kotlin.idea.references.readWriteAccess
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
class KotlinReadWriteAccessDetector : ReadWriteAccessDetector() {
companion object {
val INSTANCE = KotlinReadWriteAccessDetector()
}
override fun isReadWriteAccessible(element: PsiElement) = element is KtVariableDeclaration || element is KtParameter
override fun isDeclarationWriteAccess(element: PsiElement) = isReadWriteAccessible(element)
override fun getReferenceAccess(referencedElement: PsiElement, reference: PsiReference): Access {
if (!isReadWriteAccessible(referencedElement)) {
return Access.Read
}
val refTarget = reference.resolve()
if (refTarget is KtLightMethod) {
val origin = refTarget.kotlinOrigin
val declaration: KtNamedDeclaration = when (origin) {
is KtPropertyAccessor -> origin.getNonStrictParentOfType<KtProperty>()
is KtProperty, is KtParameter -> origin as KtNamedDeclaration
else -> null
} ?: return Access.ReadWrite
return when (refTarget.name) {
JvmAbi.getterName(declaration.name!!) -> return Access.Read
JvmAbi.setterName(declaration.name!!) -> return Access.Write
else -> Access.ReadWrite
}
}
return getExpressionAccess(reference.element)
}
override fun getExpressionAccess(expression: PsiElement): Access {
if (expression !is KtExpression) { //TODO: there should be a more correct scheme of access type detection for cross-language references
return JavaReadWriteAccessDetector().getExpressionAccess(expression)
}
return when (expression.readWriteAccess(useResolveForReadWrite = true)) {
ReferenceAccess.READ -> Access.Read
ReferenceAccess.WRITE -> Access.Write
ReferenceAccess.READ_WRITE -> Access.ReadWrite
}
}
}
@@ -1,49 +0,0 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.search.ideaExtensions
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElement
import com.intellij.psi.search.*
import org.jetbrains.kotlin.fileClasses.javaFileFacadeFqName
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.search.excludeFileTypes
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtFile
class KotlinReferenceScopeOptimizer : ScopeOptimizer {
override fun getRestrictedUseScope(element: PsiElement): SearchScope? {
if (element is KtCallableDeclaration && element.parent is KtFile) {
return getRestrictedScopeForTopLevelCallable(element)
}
return null
}
private fun getRestrictedScopeForTopLevelCallable(callable: KtCallableDeclaration): GlobalSearchScope? {
val useScope = callable.useScope as? GlobalSearchScope ?: return null
val file = callable.parent as KtFile
val packageName = file.packageFqName.takeUnless { it.isRoot } ?: return null
val project = file.project
val searchHelper = PsiSearchHelper.getInstance(project)
val kotlinScope = GlobalSearchScope.getScopeRestrictedByFileTypes(useScope, KotlinFileType.INSTANCE)
val javaScope = GlobalSearchScope.getScopeRestrictedByFileTypes(useScope, JavaFileType.INSTANCE)
val restScope = useScope.excludeFileTypes(KotlinFileType.INSTANCE, JavaFileType.INSTANCE) as GlobalSearchScope
val kotlinFiles = mutableListOf<VirtualFile>()
searchHelper.processCandidateFilesForText(kotlinScope, UsageSearchContext.IN_CODE, true, packageName.asString()) {
kotlinFiles.add(it)
}
val javaFiles = mutableListOf<VirtualFile>()
searchHelper.processCandidateFilesForText(javaScope, UsageSearchContext.IN_CODE, true, file.javaFileFacadeFqName.asString()) {
javaFiles.add(it)
}
return GlobalSearchScope.filesScope(project, kotlinFiles + javaFiles).uniteWith(restScope)
}
}
@@ -1,49 +0,0 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.search.ideaExtensions
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.psi.PsiElement
import com.intellij.psi.impl.cache.CacheManager
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.ScopeOptimizer
import com.intellij.psi.search.SearchScope
import com.intellij.psi.search.UsageSearchContext
import org.jetbrains.kotlin.fileClasses.javaFileFacadeFqName
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.search.excludeFileTypes
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtFile
class KotlinReferenceScopeOptimizer : ScopeOptimizer {
override fun getRestrictedUseScope(element: PsiElement): SearchScope? {
if (element is KtCallableDeclaration && element.parent is KtFile) {
return getRestrictedScopeForTopLevelCallable(element)
}
return null
}
private fun getRestrictedScopeForTopLevelCallable(callable: KtCallableDeclaration): GlobalSearchScope? {
val useScope = callable.useScope as? GlobalSearchScope ?: return null
val file = callable.parent as KtFile
val packageName = file.packageFqName.takeUnless { it.isRoot } ?: return null
val project = file.project
val cacheManager = CacheManager.SERVICE.getInstance(project)
val kotlinScope = GlobalSearchScope.getScopeRestrictedByFileTypes(useScope, KotlinFileType.INSTANCE)
val javaScope = GlobalSearchScope.getScopeRestrictedByFileTypes(useScope, JavaFileType.INSTANCE)
val restScope = useScope.excludeFileTypes(KotlinFileType.INSTANCE, JavaFileType.INSTANCE) as GlobalSearchScope
//TODO: use all components of package name?
val shortPackageName = packageName.shortName().identifier
val kotlinFiles = cacheManager.getVirtualFilesWithWord(shortPackageName, UsageSearchContext.IN_CODE, kotlinScope, true)
val javaFacadeName = file.javaFileFacadeFqName.shortName().identifier
val javaFiles = cacheManager.getVirtualFilesWithWord(javaFacadeName, UsageSearchContext.IN_CODE, javaScope, true)
return GlobalSearchScope.filesScope(project, (kotlinFiles + javaFiles).asList()).uniteWith(restScope)
}
}
@@ -1,408 +0,0 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.search.ideaExtensions
import com.intellij.openapi.application.QueryExecutorBase
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.*
import com.intellij.psi.impl.cache.CacheManager
import com.intellij.psi.search.*
import com.intellij.psi.search.searches.MethodReferencesSearch
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.util.Processor
import com.intellij.util.containers.nullize
import org.jetbrains.kotlin.asJava.LightClassUtil
import org.jetbrains.kotlin.asJava.elements.KtLightField
import org.jetbrains.kotlin.asJava.elements.KtLightMember
import org.jetbrains.kotlin.asJava.elements.KtLightMethod
import org.jetbrains.kotlin.asJava.elements.KtLightParameter
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.asJava.toLightElements
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.search.*
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions.Companion.Empty
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions.Companion.calculateEffectiveScope
import org.jetbrains.kotlin.idea.search.usagesSearch.dataClassComponentFunction
import org.jetbrains.kotlin.idea.search.usagesSearch.filterDataClassComponentsIfDisabled
import org.jetbrains.kotlin.idea.search.usagesSearch.getClassNameForCompanionObject
import org.jetbrains.kotlin.idea.search.usagesSearch.operators.OperatorReferenceSearcher
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.expectedDeclarationIfAny
import org.jetbrains.kotlin.idea.util.isExpectDeclaration
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier
import org.jetbrains.kotlin.psi.psiUtil.parents
import java.util.*
data class KotlinReferencesSearchOptions(
val acceptCallableOverrides: Boolean = false,
val acceptOverloads: Boolean = false,
val acceptExtensionsOfDeclarationClass: Boolean = false,
val acceptCompanionObjectMembers: Boolean = false,
val acceptImportAlias: Boolean = true,
val searchForComponentConventions: Boolean = true,
val searchForOperatorConventions: Boolean = true,
val searchNamedArguments: Boolean = true,
val searchForExpectedUsages: Boolean = true
) {
fun anyEnabled(): Boolean = acceptCallableOverrides || acceptOverloads || acceptExtensionsOfDeclarationClass
companion object {
val Empty = KotlinReferencesSearchOptions()
internal fun calculateEffectiveScope(
elementToSearch: PsiNamedElement,
parameters: ReferencesSearch.SearchParameters
): SearchScope {
val kotlinOptions = (parameters as? KotlinAwareReferencesSearchParameters)?.kotlinOptions ?: Empty
val elements = if (elementToSearch is KtDeclaration && !isOnlyKotlinSearch(parameters.scopeDeterminedByUser)) {
elementToSearch.toLightElements().filterDataClassComponentsIfDisabled(kotlinOptions).nullize()
} else {
null
} ?: listOf(elementToSearch)
return elements.fold(parameters.effectiveSearchScope) { scope, e ->
scope.unionSafe(parameters.effectiveSearchScope(e))
}
}
}
}
interface KotlinAwareReferencesSearchParameters {
val kotlinOptions: KotlinReferencesSearchOptions
}
class KotlinReferencesSearchParameters(
elementToSearch: PsiElement,
scope: SearchScope = runReadAction { elementToSearch.project.allScope() },
ignoreAccessScope: Boolean = false,
optimizer: SearchRequestCollector? = null,
override val kotlinOptions: KotlinReferencesSearchOptions = Empty
) : ReferencesSearch.SearchParameters(elementToSearch, scope, ignoreAccessScope, optimizer), KotlinAwareReferencesSearchParameters
class KotlinMethodReferencesSearchParameters(
elementToSearch: PsiMethod,
scope: SearchScope = runReadAction { elementToSearch.project.allScope() },
strictSignatureSearch: Boolean = true,
override val kotlinOptions: KotlinReferencesSearchOptions = Empty
) : MethodReferencesSearch.SearchParameters(elementToSearch, scope, strictSignatureSearch), KotlinAwareReferencesSearchParameters
class KotlinAliasedImportedElementSearcher : QueryExecutorBase<PsiReference, ReferencesSearch.SearchParameters>(true) {
override fun processQuery(parameters: ReferencesSearch.SearchParameters, consumer: Processor<in PsiReference?>) {
val kotlinOptions = (parameters as? KotlinAwareReferencesSearchParameters)?.kotlinOptions ?: Empty
if (!kotlinOptions.acceptImportAlias) return
val element = parameters.elementToSearch
if (!element.isValid) return
val unwrappedElement = element.namedUnwrappedElement ?: return
val name = unwrappedElement.name
if (name == null || StringUtil.isEmptyOrSpaces(name)) return
val effectiveSearchScope = calculateEffectiveScope(unwrappedElement, parameters)
val collector = parameters.optimizer
val session = collector.searchSession
collector.searchWord(name, effectiveSearchScope, UsageSearchContext.IN_CODE, true, element, AliasProcessor(element, session))
}
private class AliasProcessor(
private val myTarget: PsiElement,
private val mySession: SearchSession
) : RequestResultProcessor(myTarget) {
override fun processTextOccurrence(element: PsiElement, offsetInElement: Int, consumer: Processor<in PsiReference>): Boolean {
val importStatement = element.parent as? KtImportDirective ?: return true
val importAlias = importStatement.alias?.name ?: return true
val reference = importStatement.importedReference?.getQualifiedElementSelector()?.mainReference ?: return true
if (!reference.isReferenceTo(myTarget)) {
return true
}
val collector = SearchRequestCollector(mySession)
val fileScope: SearchScope = LocalSearchScope(element.containingFile)
collector.searchWord(importAlias, fileScope, UsageSearchContext.IN_CODE, true, myTarget)
return PsiSearchHelper.getInstance(element.project).processRequests(collector, consumer)
}
}
}
class KotlinReferencesSearcher : QueryExecutorBase<PsiReference, ReferencesSearch.SearchParameters>() {
override fun processQuery(queryParameters: ReferencesSearch.SearchParameters, consumer: Processor<in PsiReference>) {
val processor = QueryProcessor(queryParameters, consumer)
runReadAction { processor.processInReadAction() }
processor.executeLongRunningTasks()
}
private class QueryProcessor(val queryParameters: ReferencesSearch.SearchParameters, val consumer: Processor<in PsiReference>) {
private val kotlinOptions = (queryParameters as? KotlinAwareReferencesSearchParameters)?.kotlinOptions ?: Empty
private val longTasks = ArrayList<() -> Unit>()
fun executeLongRunningTasks() {
longTasks.forEach { it() }
}
fun processInReadAction() {
val element = queryParameters.elementToSearch
if (!element.isValid) return
val unwrappedElement = element.namedUnwrappedElement ?: return
val elementToSearch =
if (kotlinOptions.searchForExpectedUsages && unwrappedElement is KtDeclaration && unwrappedElement.hasActualModifier()) {
unwrappedElement.expectedDeclarationIfAny() as? PsiNamedElement
} else {
null
} ?: unwrappedElement
val effectiveSearchScope = calculateEffectiveScope(elementToSearch, queryParameters)
val refFilter: (PsiReference) -> Boolean = when (elementToSearch) {
is KtParameter -> ({ ref: PsiReference -> !ref.isNamedArgumentReference()/* they are processed later*/ })
else -> ({ true })
}
val resultProcessor = KotlinRequestResultProcessor(elementToSearch, filter = refFilter, options = kotlinOptions)
val name = elementToSearch.name
if (kotlinOptions.anyEnabled() || elementToSearch is KtNamedDeclaration && elementToSearch.isExpectDeclaration()) {
if (name != null) {
// Check difference with default scope
queryParameters.optimizer.searchWord(
name, effectiveSearchScope, UsageSearchContext.IN_CODE, true, elementToSearch, resultProcessor
)
}
}
val classNameForCompanionObject = elementToSearch.getClassNameForCompanionObject()
if (classNameForCompanionObject != null) {
queryParameters.optimizer.searchWord(
classNameForCompanionObject, effectiveSearchScope, UsageSearchContext.ANY, true, elementToSearch, resultProcessor
)
}
if (elementToSearch is KtParameter && kotlinOptions.searchNamedArguments) {
searchNamedArguments(elementToSearch)
}
if (!(elementToSearch is KtElement && isOnlyKotlinSearch(effectiveSearchScope))) {
searchLightElements(element)
}
if (element is KtFunction || element is PsiMethod) {
val referenceSearcher = OperatorReferenceSearcher.create(
element, effectiveSearchScope, consumer, queryParameters.optimizer, kotlinOptions
)
if (referenceSearcher != null) {
longTasks.add { referenceSearcher.run() }
}
}
if (kotlinOptions.searchForComponentConventions) {
when (element) {
is KtParameter -> {
val componentFunctionDescriptor = element.dataClassComponentFunction()
if (componentFunctionDescriptor != null) {
val containingClass = element.getStrictParentOfType<KtClassOrObject>()?.toLightClass()
searchDataClassComponentUsages(containingClass, componentFunctionDescriptor, kotlinOptions)
}
}
is KtLightParameter -> {
val componentFunctionDescriptor = element.kotlinOrigin?.dataClassComponentFunction()
if (componentFunctionDescriptor != null) {
searchDataClassComponentUsages(element.method.containingClass, componentFunctionDescriptor, kotlinOptions)
}
}
}
}
}
private fun searchNamedArguments(parameter: KtParameter) {
val parameterName = parameter.name ?: return
val function = parameter.ownerFunction as? KtFunction ?: return
if (function.nameAsName?.isSpecial != false) return
val project = function.project
var namedArgsScope = function.useScope.intersectWith(queryParameters.scopeDeterminedByUser)
if (namedArgsScope is GlobalSearchScope) {
namedArgsScope = KotlinSourceFilterScope.sourcesAndLibraries(namedArgsScope, project)
val filesWithFunctionName = CacheManager.SERVICE.getInstance(project).getVirtualFilesWithWord(
function.name!!, UsageSearchContext.IN_CODE, namedArgsScope, true
)
namedArgsScope = GlobalSearchScope.filesScope(project, filesWithFunctionName.asList())
}
val processor = KotlinRequestResultProcessor(parameter, filter = { it.isNamedArgumentReference() })
queryParameters.optimizer.searchWord(
parameterName,
namedArgsScope,
KOTLIN_NAMED_ARGUMENT_SEARCH_CONTEXT,
true,
parameter,
processor
)
}
private fun searchLightElements(element: PsiElement) {
when (element) {
is KtClassOrObject -> {
processKtClassOrObject(element)
}
is KtNamedFunction, is KtSecondaryConstructor -> {
val name = (element as KtFunction).name
if (name != null) {
val methods = LightClassUtil.getLightClassMethods(element)
for (method in methods) {
searchNamedElement(method)
}
}
processStaticsFromCompanionObject(element)
}
is KtProperty -> {
val propertyMethods = LightClassUtil.getLightClassPropertyMethods(element)
propertyMethods.allDeclarations.forEach { searchNamedElement(it) }
processStaticsFromCompanionObject(element)
}
is KtParameter -> {
searchPropertyAccessorMethods(element)
if (element.getStrictParentOfType<KtPrimaryConstructor>() != null) {
// Simple parameters without val and var shouldn't be processed here because of local search scope
val methods = LightClassUtil.getLightClassPropertyMethods(element)
methods.allDeclarations.filterDataClassComponentsIfDisabled(kotlinOptions).forEach { searchNamedElement(it) }
}
}
is KtLightMethod -> {
val declaration = element.kotlinOrigin
if (declaration is KtProperty || (declaration is KtParameter && declaration.hasValOrVar())) {
searchNamedElement(declaration as PsiNamedElement)
processStaticsFromCompanionObject(declaration)
} else if (declaration is KtPropertyAccessor) {
val property = declaration.getStrictParentOfType<KtProperty>()
searchNamedElement(property)
} else if (declaration is KtFunction) {
processStaticsFromCompanionObject(declaration)
if (element.isMangled) {
searchNamedElement(declaration) { it.restrictToKotlinSources() }
}
}
}
is KtLightParameter -> {
val origin = element.kotlinOrigin ?: return
searchPropertyAccessorMethods(origin)
}
}
}
private fun searchPropertyAccessorMethods(origin: KtParameter) {
origin.toLightElements().filterDataClassComponentsIfDisabled(kotlinOptions).forEach { searchNamedElement(it) }
}
private fun processKtClassOrObject(element: KtClassOrObject) {
val className = element.name ?: return
val lightClass = element.toLightClass() ?: return
searchNamedElement(lightClass, className)
if (element is KtObjectDeclaration && element.isCompanion()) {
LightClassUtil.getLightFieldForCompanionObject(element)?.let { searchNamedElement(it) }
if (kotlinOptions.acceptCompanionObjectMembers) {
val originLightClass = element.getStrictParentOfType<KtClass>()?.toLightClass()
if (originLightClass != null) {
val lightDeclarations: List<KtLightMember<*>?> =
originLightClass.methods.map { it as? KtLightMethod } + originLightClass.fields.map { it as? KtLightField }
for (declaration in element.declarations) {
lightDeclarations
.firstOrNull { it?.kotlinOrigin == declaration }
?.let { searchNamedElement(it) }
}
}
}
}
}
private fun searchDataClassComponentUsages(
containingClass: PsiClass?,
componentFunctionDescriptor: FunctionDescriptor,
kotlinOptions: KotlinReferencesSearchOptions
) {
val componentFunction = containingClass?.methods?.firstOrNull {
it.name == componentFunctionDescriptor.name.asString() && it.parameterList.parametersCount == 0
}
if (componentFunction != null) {
searchNamedElement(componentFunction)
val searcher = OperatorReferenceSearcher.create(
componentFunction, queryParameters.effectiveSearchScope, consumer, queryParameters.optimizer, kotlinOptions
)
longTasks.add { searcher!!.run() }
}
}
private fun processStaticsFromCompanionObject(element: KtDeclaration) {
findStaticMethodsFromCompanionObject(element).forEach { searchNamedElement(it) }
}
private fun findStaticMethodsFromCompanionObject(declaration: KtDeclaration): List<PsiMethod> {
val originObject = declaration.parents
.dropWhile { it is KtClassBody }
.firstOrNull() as? KtObjectDeclaration ?: return emptyList()
if (!originObject.isCompanion()) return emptyList()
val originClass = originObject.getStrictParentOfType<KtClass>()
val originLightClass = originClass?.toLightClass() ?: return emptyList()
val allMethods = originLightClass.allMethods
return allMethods.filter { it is KtLightMethod && it.kotlinOrigin == declaration }
}
private fun searchNamedElement(
element: PsiNamedElement?,
name: String? = element?.name,
modifyScope: ((SearchScope) -> SearchScope)? = null
) {
if (name != null && element != null) {
val baseScope = queryParameters.effectiveSearchScope(element)
val scope = if (modifyScope != null) modifyScope(baseScope) else baseScope
val context = UsageSearchContext.IN_CODE + UsageSearchContext.IN_FOREIGN_LANGUAGES + UsageSearchContext.IN_COMMENTS
val resultProcessor = KotlinRequestResultProcessor(
element,
queryParameters.elementToSearch.namedUnwrappedElement ?: element,
options = kotlinOptions
)
queryParameters.optimizer.searchWord(name, scope, context.toShort(), true, element, resultProcessor)
}
}
private fun PsiReference.isNamedArgumentReference(): Boolean {
return this is KtSimpleNameReference && expression.parent is KtValueArgumentName
}
}
}
@@ -1,79 +0,0 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.search.ideaExtensions
import com.intellij.openapi.progress.ProgressManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
import com.intellij.psi.PsiReferenceService
import com.intellij.psi.ReferenceRange
import com.intellij.psi.search.RequestResultProcessor
import com.intellij.util.Processor
import org.jetbrains.kotlin.idea.references.KtDestructuringDeclarationReference
import org.jetbrains.kotlin.idea.search.usagesSearch.isCallableOverrideUsage
import org.jetbrains.kotlin.idea.search.usagesSearch.isExtensionOfDeclarationClassUsage
import org.jetbrains.kotlin.idea.search.usagesSearch.isUsageInContainingDeclaration
import org.jetbrains.kotlin.psi.KtDestructuringDeclaration
import org.jetbrains.kotlin.psi.KtNamedDeclaration
class KotlinRequestResultProcessor(
private val unwrappedElement: PsiElement,
private val originalElement: PsiElement = unwrappedElement,
private val filter: (PsiReference) -> Boolean = { true },
private val options: KotlinReferencesSearchOptions = KotlinReferencesSearchOptions.Empty
) : RequestResultProcessor(unwrappedElement, originalElement, filter, options) {
private val referenceService = PsiReferenceService.getService()
override fun processTextOccurrence(element: PsiElement, offsetInElement: Int, consumer: Processor<in PsiReference>): Boolean {
val references = if (element is KtDestructuringDeclaration)
element.entries.flatMap { referenceService.getReferences(it, PsiReferenceService.Hints.NO_HINTS) }
else
referenceService.getReferences(element, PsiReferenceService.Hints.NO_HINTS)
return references.all { ref ->
ProgressManager.checkCanceled()
if (filter(ref) && ref.containsOffsetInElement(offsetInElement) && ref.isReferenceToTarget(unwrappedElement)) {
consumer.process(ref)
} else {
true
}
}
}
private fun PsiReference.containsOffsetInElement(offsetInElement: Int): Boolean {
if (this is KtDestructuringDeclarationReference) return true
return ReferenceRange.containsOffsetInElement(this, offsetInElement)
}
private fun PsiReference.isReferenceToTarget(element: PsiElement): Boolean {
if (isReferenceTo(element)) {
return true
}
if (originalElement is KtNamedDeclaration) {
if (options.acceptCallableOverrides && isCallableOverrideUsage(originalElement)) {
return true
}
if (options.acceptOverloads && isUsageInContainingDeclaration(originalElement)) {
return true
}
if (options.acceptExtensionsOfDeclarationClass && isExtensionOfDeclarationClassUsage(originalElement)) {
return true
}
}
return false
}
}
@@ -1,146 +0,0 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.search
import com.intellij.openapi.fileTypes.FileType
import com.intellij.openapi.fileTypes.FileTypeRegistry
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.psi.impl.cache.impl.id.IdIndex
import com.intellij.psi.impl.cache.impl.id.IdIndexEntry
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.PsiSearchHelper
import com.intellij.psi.search.SearchScope
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.util.CommonProcessors
import com.intellij.util.indexing.FileBasedIndex
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.types.expressions.OperatorConventions
import java.util.*
infix fun SearchScope.and(otherScope: SearchScope): SearchScope = intersectWith(otherScope)
infix fun SearchScope.or(otherScope: SearchScope): SearchScope = union(otherScope)
infix fun GlobalSearchScope.or(otherScope: SearchScope): GlobalSearchScope = union(otherScope)
operator fun SearchScope.minus(otherScope: GlobalSearchScope): SearchScope = this and !otherScope
operator fun GlobalSearchScope.not(): GlobalSearchScope = GlobalSearchScope.notScope(this)
fun SearchScope.unionSafe(other: SearchScope): SearchScope {
if (this is LocalSearchScope && this.scope.isEmpty()) {
return other
}
if (other is LocalSearchScope && other.scope.isEmpty()) {
return this
}
return this.union(other)
}
fun Project.allScope(): GlobalSearchScope = GlobalSearchScope.allScope(this)
fun Project.projectScope(): GlobalSearchScope = GlobalSearchScope.projectScope(this)
fun PsiFile.fileScope(): GlobalSearchScope = GlobalSearchScope.fileScope(this)
fun GlobalSearchScope.restrictByFileType(fileType: FileType) = GlobalSearchScope.getScopeRestrictedByFileTypes(this, fileType)
fun SearchScope.restrictByFileType(fileType: FileType): SearchScope = when (this) {
is GlobalSearchScope -> restrictByFileType(fileType)
is LocalSearchScope -> {
val elements = scope.filter { it.containingFile.fileType == fileType }
when (elements.size) {
0 -> GlobalSearchScope.EMPTY_SCOPE
scope.size -> this
else -> LocalSearchScope(elements.toTypedArray())
}
}
else -> this
}
fun GlobalSearchScope.restrictToKotlinSources() = restrictByFileType(KotlinFileType.INSTANCE)
fun SearchScope.restrictToKotlinSources() = restrictByFileType(KotlinFileType.INSTANCE)
fun SearchScope.excludeKotlinSources(): SearchScope = excludeFileTypes(KotlinFileType.INSTANCE)
fun SearchScope.excludeFileTypes(vararg fileTypes: FileType): SearchScope {
return if (this is GlobalSearchScope) {
val includedFileTypes = FileTypeRegistry.getInstance().registeredFileTypes.filter { it !in fileTypes }.toTypedArray()
GlobalSearchScope.getScopeRestrictedByFileTypes(this, *includedFileTypes)
} else {
this as LocalSearchScope
val filteredElements = scope.filter { it.containingFile.fileType !in fileTypes }
if (filteredElements.isNotEmpty())
LocalSearchScope(filteredElements.toTypedArray())
else
GlobalSearchScope.EMPTY_SCOPE
}
}
// Copied from SearchParameters.getEffectiveSearchScope()
fun ReferencesSearch.SearchParameters.effectiveSearchScope(element: PsiElement): SearchScope {
if (element == elementToSearch) return effectiveSearchScope
if (isIgnoreAccessScope) return scopeDeterminedByUser
val accessScope = PsiSearchHelper.getInstance(element.project).getUseScope(element)
return scopeDeterminedByUser.intersectWith(accessScope)
}
fun isOnlyKotlinSearch(searchScope: SearchScope): Boolean {
return searchScope is LocalSearchScope && searchScope.scope.all { it.containingFile is KtFile }
}
fun PsiSearchHelper.isCheapEnoughToSearchConsideringOperators(
name: String,
scope: GlobalSearchScope,
fileToIgnoreOccurrencesIn: PsiFile?,
progress: ProgressIndicator?
): PsiSearchHelper.SearchCostResult {
if (OperatorConventions.isConventionName(Name.identifier(name))) {
return PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES
}
return isCheapEnoughToSearch(name, scope, fileToIgnoreOccurrencesIn, progress)
}
fun findScriptsWithUsages(declaration: KtNamedDeclaration): List<KtFile> {
val project = declaration.project
val scope = PsiSearchHelper.getInstance(project).getUseScope(declaration) as? GlobalSearchScope
?: return emptyList()
val name = declaration.name.takeIf { it?.isNotBlank() == true } ?: return emptyList()
val collector = CommonProcessors.CollectProcessor(ArrayList<VirtualFile>())
runReadAction {
FileBasedIndex.getInstance().getFilesWithKey(
IdIndex.NAME,
setOf(IdIndexEntry(name, true)),
collector,
scope
)
}
return collector.results
.mapNotNull { PsiManager.getInstance(project).findFile(it) as? KtFile }
.filter { it.findScriptDefinition() != null }
.toList()
}
@@ -1,940 +0,0 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.search.usagesSearch
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.ide.highlighter.XmlFileType
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
import com.intellij.psi.*
import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference
import com.intellij.psi.search.*
import com.intellij.psi.search.searches.ClassInheritorsSearch
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.util.Processor
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.asJava.classes.KtLightClass
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.diagnostics.PsiDiagnosticUtils
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.caches.resolve.util.getJavaMemberDescriptor
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.idea.references.KtDestructuringDeclarationReference
import org.jetbrains.kotlin.idea.search.excludeFileTypes
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchParameters
import org.jetbrains.kotlin.idea.search.restrictToKotlinSources
import org.jetbrains.kotlin.idea.util.FuzzyType
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.kdoc.psi.impl.KDocName
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.sam.getSingleAbstractMethodOrNull
import org.jetbrains.kotlin.types.KotlinType
import java.util.*
//TODO: check if smart search is too expensive
class ExpressionsOfTypeProcessor(
private val typeToSearch: FuzzyType,
private val classToSearch: PsiClass?,
private val searchScope: SearchScope,
private val project: Project,
private val possibleMatchHandler: (KtExpression) -> Unit,
private val possibleMatchesInScopeHandler: (SearchScope) -> Unit
) {
/** For tests only */
enum class Mode {
ALWAYS_SMART,
ALWAYS_PLAIN,
PLAIN_WHEN_NEEDED // use plain search for LocalSearchScope and when unknown type of reference encountered
}
companion object {
@get:TestOnly
var mode = if (ApplicationManager.getApplication().isUnitTestMode) Mode.ALWAYS_SMART else Mode.PLAIN_WHEN_NEEDED
@get:TestOnly
var testLog: MutableCollection<String>? = null
inline fun testLog(s: () -> String) {
testLog?.add(s())
}
val LOG = Logger.getInstance(ExpressionsOfTypeProcessor::class.java)
fun logPresentation(element: PsiElement): String? {
return runReadAction {
if (element !is KtDeclaration && element !is PsiMember) return@runReadAction element.text
val fqName = element.getKotlinFqName()?.asString()
?: (element as? KtNamedDeclaration)?.name
when (element) {
is PsiMethod -> fqName + element.parameterList.text
is KtFunction -> fqName + element.valueParameterList!!.text
is KtParameter -> {
val owner = element.ownerFunction?.let { logPresentation(it) } ?: element.parent.toString()
"parameter ${element.name} of $owner"
}
is KtDestructuringDeclaration -> element.entries.joinToString(", ", prefix = "(", postfix = ")") { it.text }
else -> fqName
}
}
}
private fun PsiModifierListOwner.isPrivate() = hasModifierProperty(PsiModifier.PRIVATE)
private fun PsiModifierListOwner.isLocal() = parents.any { it is PsiCodeBlock }
}
// note: a Task must define equals & hashCode!
private interface Task {
fun perform()
}
private val tasks = ArrayDeque<Task>()
private val taskSet = HashSet<Task>()
private val scopesToUsePlainSearch = LinkedHashMap<KtFile, ArrayList<PsiElement>>()
fun run() {
val usePlainSearch = when (mode) {
Mode.ALWAYS_SMART -> false
Mode.ALWAYS_PLAIN -> true
Mode.PLAIN_WHEN_NEEDED -> searchScope is LocalSearchScope // for local scope it's faster to use plain search
}
if (usePlainSearch || classToSearch == null) {
possibleMatchesInScopeHandler(searchScope)
return
}
// optimization
if (runReadAction {
searchScope is GlobalSearchScope && !FileTypeIndex.containsFileOfType(
KotlinFileType.INSTANCE,
searchScope
)
}) return
// for class from library always use plain search because we cannot search usages in compiled code (we could though)
if (!runReadAction { classToSearch.isValid && ProjectRootsUtil.isInProjectSource(classToSearch) }) {
possibleMatchesInScopeHandler(searchScope)
return
}
addClassToProcess(classToSearch)
processTasks()
runReadAction {
val scopeElements = scopesToUsePlainSearch.values
.flatten()
.filter { it.isValid }
.toTypedArray()
if (scopeElements.isNotEmpty()) {
possibleMatchesInScopeHandler(LocalSearchScope(scopeElements))
}
}
}
private fun addTask(task: Task) {
if (taskSet.add(task)) {
tasks.push(task)
}
}
private fun processTasks() {
while (tasks.isNotEmpty()) {
tasks.pop().perform()
}
}
private fun downShiftToPlainSearch(reference: PsiReference) {
val message = getFallbackDiagnosticsMessage(reference)
LOG.info("ExpressionsOfTypeProcessor: $message")
testLog { "Downgrade to plain text search: $message" }
tasks.clear()
scopesToUsePlainSearch.clear()
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 { "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 ->
val element = reference.element
val wasProcessed = when (element.language) {
KotlinLanguage.INSTANCE -> processClassUsageInKotlin(element)
JavaLanguage.INSTANCE -> processClassUsageInJava(element)
else -> {
when (element.language.displayName) {
"Groovy" -> {
processClassUsageInLanguageWithPsiClass(element)
true
}
"Scala" -> false
"Clojure" -> false
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)
return@searchReferences false
}
error(getFallbackDiagnosticsMessage(reference))
}
// we must use plain search inside our class (and inheritors) because implicit 'this' can happen anywhere
(classToSearch as? KtLightClass)?.kotlinOrigin?.let { usePlainSearch(it) }
}
}
addTask(ProcessClassUsagesTask(classToSearch))
}
private fun getFallbackDiagnosticsMessage(reference: PsiReference): String {
val element = reference.element
val document = PsiDocumentManager.getInstance(project).getDocument(element.containingFile)
val lineAndCol = PsiDiagnosticUtils.offsetToLineAndColumn(document, element.startOffset)
return "Unsupported reference: '${element.text}' in ${element.containingFile
.name} line ${lineAndCol.line} column ${lineAndCol.column}"
}
private enum class ReferenceProcessor(val handler: (ExpressionsOfTypeProcessor, PsiReference) -> Boolean) {
CallableOfOurType(ExpressionsOfTypeProcessor::processReferenceToCallableOfOurType),
ProcessLambdasInCalls({ processor, reference ->
(reference.element as? KtReferenceExpression)?.let { processor.processLambdasForCallableReference(it) }
true
})
}
private class StaticMemberRequestResultProcessor(val psiMember: PsiMember, classes: List<PsiClass>) :
RequestResultProcessor(psiMember) {
val possibleClassesNames: Set<String> = runReadAction { classes.map { it.qualifiedName }.filterNotNullTo(HashSet()) }
override fun processTextOccurrence(element: PsiElement, offsetInElement: Int, consumer: Processor<in PsiReference>): Boolean {
when (element) {
is KtQualifiedExpression -> {
val selectorExpression = element.selectorExpression ?: return true
val selectorReference = element.findReferenceAt(selectorExpression.startOffsetInParent)
val references = when (selectorReference) {
is PsiMultiReference -> selectorReference.references.toList()
else -> listOf(selectorReference)
}.filterNotNull()
for (ref in references) {
ProgressManager.checkCanceled()
if (ref.isReferenceTo(psiMember)) {
consumer.process(ref)
}
}
}
is KtImportDirective -> {
if (element.isAllUnder) {
val fqName = element.importedFqName?.asString()
if (fqName != null && fqName in possibleClassesNames) {
val ref = element.importedReference
?.getQualifiedElementSelector()
?.references
?.firstOrNull()
if (ref != null) {
consumer.process(ref)
}
}
}
}
}
return true
}
}
private fun classUseScope(psiClass: PsiClass) = runReadAction {
if (!psiClass.isValid) {
throw ProcessCanceledException()
}
val file = psiClass.containingFile
if (file != null) file.useScope else psiClass.useScope
}
private fun addStaticMemberToProcess(psiMember: PsiMember, scope: SearchScope, processor: ReferenceProcessor) {
val declarationClass = runReadAction { psiMember.containingClass } ?: return
val declarationName = runReadAction { psiMember.name } ?: return
if (declarationName.isEmpty()) return
data class ProcessStaticCallableUsagesTask(
val member: PsiMember,
val memberScope: SearchScope,
val taskProcessor: ReferenceProcessor
) : Task {
override fun perform() {
// This class will look through the whole hierarchy anyway, so shouldn't be a big overhead here
val inheritanceClasses = ClassInheritorsSearch.search(
declarationClass,
classUseScope(declarationClass),
true, true, false
).findAll()
val classes = (inheritanceClasses + declarationClass).filter {
it !is KtLightClass
}
val searchRequestCollector = SearchRequestCollector(SearchSession())
val resultProcessor = StaticMemberRequestResultProcessor(member, classes)
val memberName = runReadAction { member.name }
for (klass in classes) {
val request = klass.name + "." + declarationName
testLog { "Searched references to static $memberName in non-Java files by request $request" }
searchRequestCollector.searchWord(
request,
classUseScope(klass).intersectWith(memberScope), UsageSearchContext.IN_CODE, true, member, resultProcessor
)
val qualifiedName = runReadAction { klass.qualifiedName }
if (qualifiedName != null) {
val importAllUnderRequest = "$qualifiedName.*"
testLog { "Searched references to static $memberName in non-Java files by request $importAllUnderRequest" }
searchRequestCollector.searchWord(
importAllUnderRequest,
classUseScope(klass).intersectWith(memberScope), UsageSearchContext.IN_CODE, true, member, resultProcessor
)
}
}
PsiSearchHelper.getInstance(project).processRequests(searchRequestCollector) { reference ->
if (reference.element.parents.any { it is KtImportDirective }) {
// Found declaration in import - process all file with an ordinal reference search
val containingFile = reference.element.containingFile
addCallableDeclarationToProcess(member, LocalSearchScope(containingFile), taskProcessor)
true
} else {
val processed = taskProcessor.handler(this@ExpressionsOfTypeProcessor, reference)
if (!processed) { // we don't know how to handle this reference and down-shift to plain search
downShiftToPlainSearch(reference)
}
true
}
}
}
}
addTask(ProcessStaticCallableUsagesTask(psiMember, scope, processor))
return
}
private fun addCallableDeclarationToProcess(declaration: PsiElement, scope: SearchScope, processor: ReferenceProcessor) {
if (scope !is LocalSearchScope && declaration is PsiMember &&
(declaration.modifierList?.hasModifierProperty(PsiModifier.STATIC) == true)
) {
addStaticMemberToProcess(declaration, scope, processor)
return
}
@Suppress("NAME_SHADOWING")
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()}" }
} else {
testLog { "Searched references to ${logPresentation(declaration)} in non-Java files" }
}
val searchParameters = KotlinReferencesSearchParameters(
declaration, scope, kotlinOptions = KotlinReferencesSearchOptions(searchNamedArguments = false)
)
searchReferences(searchParameters) { reference ->
val processed = processor.handler(this@ExpressionsOfTypeProcessor, reference)
if (!processed) { // we don't know how to handle this reference and down-shift to plain search
downShiftToPlainSearch(reference)
}
processed
}
}
}
addTask(ProcessCallableUsagesTask(declaration, processor, scope))
}
private fun addPsiMemberTask(member: PsiMember) {
if (!member.isPrivate() && !member.isLocal()) {
addCallableDeclarationOfOurType(member)
}
}
private fun addCallableDeclarationOfOurType(declaration: PsiElement) {
addCallableDeclarationToProcess(declaration, searchScope.restrictToKotlinSources(), ReferenceProcessor.CallableOfOurType)
}
/**
* Process references to declaration which has parameter of functional type with our class used inside
*/
private fun addCallableDeclarationToProcessLambdasInCalls(declaration: PsiElement) {
// we don't need to search usages of declarations in Java because Java doesn't have implicitly typed declarations so such usages cannot affect Kotlin code
val scope = GlobalSearchScope.projectScope(project).excludeFileTypes(JavaFileType.INSTANCE, XmlFileType.INSTANCE)
addCallableDeclarationToProcess(declaration, scope, ReferenceProcessor.ProcessLambdasInCalls)
}
/**
* Process reference to declaration whose type is our class (or our class used anywhere inside that type)
*/
private fun processReferenceToCallableOfOurType(reference: PsiReference) = when (reference.element.language) {
KotlinLanguage.INSTANCE -> {
if (reference is KtDestructuringDeclarationReference) {
// declaration usage in form of destructuring declaration entry
addCallableDeclarationOfOurType(reference.element)
} else {
(reference.element as? KtReferenceExpression)?.let { processSuspiciousExpression(it) }
}
true
}
else -> false // reference in unknown language - we don't know how to handle it
}
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)
testLog { "Searched references to ${logPresentation(psiClass)} in non-Kotlin files" }
searchReferences(psiClass, scope) { reference ->
// reference in some JVM language can be method parameter (but we don't know)
if (reference.element.language != JavaLanguage.INSTANCE) {
downShiftToPlainSearch(reference)
return@searchReferences false
}
// check if the reference is method parameter type
val parameter = ((reference as? PsiJavaCodeReferenceElement)?.parent as? PsiTypeElement)?.parent as? PsiParameter
val method = parameter?.declarationScope as? PsiMethod
if (method != null) {
addCallableDeclarationToProcessLambdasInCalls(method)
}
true
}
}
}
addTask(ProcessSamInterfaceTask(psiClass))
}
private fun processClassUsageInKotlin(element: PsiElement): Boolean {
//TODO: type aliases
when (element) {
is KtReferenceExpression -> {
when (val parent = element.parent) {
is KtUserType -> { // usage in type
return processClassUsageInUserType(parent)
}
is KtCallExpression -> {
if (element == parent.calleeExpression) { // constructor invocation
processSuspiciousExpression(parent)
return true
}
}
is KtContainerNode -> {
if (parent.node.elementType == KtNodeTypes.LABEL_QUALIFIER) {
return true // this@ClassName - it will be handled anyway because members and extensions are processed with plain search
}
}
is KtQualifiedExpression -> {
// <class name>.memberName or some.<class name>.memberName
if (element == parent.receiverExpression || parent.parent is KtQualifiedExpression) {
return true // companion object member or static member access - ignore it
}
}
is KtCallableReferenceExpression -> {
when (element) {
parent.receiverExpression -> { // usage in receiver of callable reference (before "::") - ignore it
return true
}
parent.callableReference -> { // usage after "::" in callable reference - should be reference to constructor of our class
processSuspiciousExpression(element)
return true
}
}
}
is KtClassLiteralExpression -> {
if (element == parent.receiverExpression) { // <class name>::class
processSuspiciousExpression(element)
return true
}
}
}
if (element.getStrictParentOfType<KtImportDirective>() != null) return true // ignore usage in import
val bindingContext = element.analyze(BodyResolveMode.PARTIAL)
val hasType = bindingContext.getType(element) != null
if (hasType) { // access to object or companion object
processSuspiciousExpression(element)
return true
}
}
is KDocName -> return true // ignore usage in doc-comment
}
return false // unsupported type of reference
}
private fun processClassUsageInUserType(userType: KtUserType): Boolean {
val typeRef = userType.parents.lastOrNull { it is KtTypeReference }
when (val typeRefParent = typeRef?.parent) {
is KtCallableDeclaration -> {
when (typeRef) {
typeRefParent.typeReference -> { // usage in type of callable declaration
addCallableDeclarationOfOurType(typeRefParent)
if (typeRefParent is KtParameter) { //TODO: what if functional type is declared with "FunctionN<...>"?
val usedInsideFunctionalType = userType.parents.takeWhile { it != typeRef }.any { it is KtFunctionType }
if (usedInsideFunctionalType) {
val function = (typeRefParent.parent as? KtParameterList)?.parent as? KtFunction
if (function != null) {
addCallableDeclarationOfOurType(function)
}
}
}
return true
}
typeRefParent.receiverTypeReference -> { // usage in receiver type of callable declaration
// we must use plain search inside extensions because implicit 'this' can happen anywhere
usePlainSearch(typeRefParent)
return true
}
}
}
is KtTypeProjection -> { // usage in type arguments of a call
val callExpression = (typeRefParent.parent as? KtTypeArgumentList)?.parent as? KtCallExpression
if (callExpression != null) {
processSuspiciousExpression(callExpression)
return true
}
}
is KtConstructorCalleeExpression -> { // super-class name in the list of bases
val parent = typeRefParent.parent
if (parent is KtSuperTypeCallEntry) {
val classOrObject = (parent.parent as KtSuperTypeList).parent as KtClassOrObject
val psiClass = classOrObject.toLightClass()
psiClass?.let { addClassToProcess(it) }
return true
}
}
is KtSuperTypeListEntry -> { // super-interface name in the list of bases
if (typeRef == typeRefParent.typeReference) {
val classOrObject = (typeRefParent.parent as KtSuperTypeList).parent as KtClassOrObject
val psiClass = classOrObject.toLightClass()
psiClass?.let { addClassToProcess(it) }
return true
}
}
is KtIsExpression -> { // <expr> is <class name>
val scopeOfPossibleSmartCast = typeRefParent.getParentOfType<KtDeclarationWithBody>(true)
scopeOfPossibleSmartCast?.let { usePlainSearch(it) }
return true
}
is KtWhenConditionIsPattern -> { // "is <class name>" or "!is <class name>" in when
val whenEntry = typeRefParent.parent as KtWhenEntry
if (typeRefParent.isNegated) {
val whenExpression = whenEntry.parent as KtWhenExpression
val entriesAfter = whenExpression.entries.dropWhile { it != whenEntry }.drop(1)
entriesAfter.forEach { usePlainSearch(it) }
} else {
usePlainSearch(whenEntry)
}
return true
}
is KtBinaryExpressionWithTypeRHS -> { // <expr> as <class name>
processSuspiciousExpression(typeRefParent)
return true
}
}
return false // unsupported case
}
private fun processClassUsageInJava(element: PsiElement): Boolean {
if (element !is PsiJavaCodeReferenceElement) return true // meaningless reference from Java
var prev = element
ParentsLoop@
for (parent in element.parents) {
when (parent) {
is PsiCodeBlock,
is PsiExpression ->
break@ParentsLoop // ignore local usages
is PsiMethod -> {
if (prev == parent.returnTypeElement) { // usage in return type of a method
addPsiMemberTask(parent)
}
break@ParentsLoop
}
is PsiField -> {
if (prev == parent.typeElement) { // usage in type of a field
addPsiMemberTask(parent)
}
break@ParentsLoop
}
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
addNonKotlinClassToProcess(psiClass)
}
break@ParentsLoop
}
//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
processParameterInSamClass(parent)
}
break@ParentsLoop
}
}
prev = parent
}
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 { "Resolved java class to descriptor: ${psiClass.qualifiedName}" }
val classDescriptor = psiClass.getJavaMemberDescriptor() as? JavaClassDescriptor
if (classDescriptor != null && 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)
*/
private fun processSuspiciousExpression(expression: KtExpression) {
var inScope = expression in searchScope
var affectedScope: PsiElement = expression
ParentsLoop@
for (element in expression.parentsWithSelf) {
affectedScope = element
if (element !is KtExpression) continue
if (searchScope is LocalSearchScope) { // optimization to not check every expression
inScope = inScope && element in searchScope
}
if (inScope) {
possibleMatchHandler(element)
}
when (val parent = element.parent) {
is KtDestructuringDeclaration -> { // "val (x, y) = <expr>"
processSuspiciousDeclaration(parent)
break@ParentsLoop
}
is KtDeclarationWithInitializer -> { // "val x = <expr>" or "fun f() = <expr>"
if (element == parent.initializer) {
processSuspiciousDeclaration(parent)
}
break@ParentsLoop
}
is KtContainerNode -> {
if (parent.node.elementType == KtNodeTypes.LOOP_RANGE) { // "for (x in <expr>) ..."
val forExpression = parent.parent as KtForExpression
(forExpression.destructuringDeclaration ?: forExpression.loopParameter as KtDeclaration?)?.let {
processSuspiciousDeclaration(it)
}
break@ParentsLoop
}
}
}
if (!element.mayTypeAffectAncestors()) break
}
// use plain search in all lambdas and anonymous functions inside because they parameters or receiver can be implicitly typed with our class
usePlainSearchInLambdas(affectedScope)
}
private fun processLambdasForCallableReference(expression: KtReferenceExpression) {
//TODO: receiver?
usePlainSearchInLambdas(expression.parent)
}
/**
* Process declaration which may have implicit type of our class (or our class used anywhere inside that type)
*/
private fun processSuspiciousDeclaration(declaration: KtDeclaration) {
if (declaration is KtDestructuringDeclaration) {
declaration.entries.forEach { processSuspiciousDeclaration(it) }
} else {
if (!isImplicitlyTyped(declaration)) return
testLog { "Checked type of ${logPresentation(declaration)}" }
val descriptor = declaration.resolveToDescriptorIfAny() as? CallableDescriptor ?: return
val type = descriptor.returnType
if (type != null && type.containsTypeOrDerivedInside(typeToSearch)) {
addCallableDeclarationOfOurType(declaration)
}
}
}
private fun usePlainSearchInLambdas(scope: PsiElement) {
scope.forEachDescendantOfType<KtFunction> {
if (it.nameIdentifier == null) {
usePlainSearch(it)
}
}
}
private fun usePlainSearch(scope: KtElement) {
runReadAction {
if (!scope.isValid) return@runReadAction
val file = scope.containingKtFile
val restricted = LocalSearchScope(scope).intersectWith(searchScope)
if (restricted is LocalSearchScope) {
ScopeLoop@
for (element in restricted.scope) {
val prevElements = scopesToUsePlainSearch.getOrPut(file) { ArrayList() }
for ((index, prevElement) in prevElements.withIndex()) {
if (!prevElement.isValid) continue@ScopeLoop
if (prevElement.isAncestor(element, strict = false)) continue@ScopeLoop
if (element.isAncestor(prevElement)) {
prevElements[index] = element
continue@ScopeLoop
}
}
prevElements.add(element)
}
} else {
assert(restricted == GlobalSearchScope.EMPTY_SCOPE)
}
}
}
//TODO: code is quite similar to PartialBodyResolveFilter.isValueNeeded
private fun KtExpression.mayTypeAffectAncestors(): Boolean {
when (val parent = this.parent) {
is KtBlockExpression -> {
return this == parent.statements.last() && parent.mayTypeAffectAncestors()
}
is KtDeclarationWithBody -> {
if (this == parent.bodyExpression) {
return !parent.hasBlockBody() && !parent.hasDeclaredReturnType()
}
}
is KtContainerNode -> {
val grandParent = parent.parent
return when (parent.node.elementType) {
KtNodeTypes.CONDITION, KtNodeTypes.BODY -> false
KtNodeTypes.THEN, KtNodeTypes.ELSE -> (grandParent as KtExpression).mayTypeAffectAncestors()
KtNodeTypes.LOOP_RANGE, KtNodeTypes.INDICES -> true
else -> true // something else unknown
}
}
}
return true // we don't know
}
private fun KotlinType.containsTypeOrDerivedInside(type: FuzzyType): Boolean {
return type.checkIsSuperTypeOf(this) != null || arguments.any { !it.isStarProjection && it.type.containsTypeOrDerivedInside(type) }
}
private fun isImplicitlyTyped(declaration: KtDeclaration): Boolean {
return when (declaration) {
is KtFunction -> !declaration.hasDeclaredReturnType()
is KtVariableDeclaration -> declaration.typeReference == null
is KtParameter -> declaration.typeReference == null
else -> false
}
}
private fun searchReferences(element: PsiElement, scope: SearchScope, processor: (PsiReference) -> Boolean) {
val parameters = ReferencesSearch.SearchParameters(element, scope, false)
searchReferences(parameters, processor)
}
private fun searchReferences(parameters: ReferencesSearch.SearchParameters, processor: (PsiReference) -> Boolean) {
ReferencesSearch.search(parameters).forEach(Processor { ref ->
ProgressManager.checkCanceled()
runReadAction {
if (ref.element.isValid) {
processor(ref)
} else {
true
}
}
})
}
}
@@ -1,66 +0,0 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.search.usagesSearch.operators
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
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
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
class BinaryOperatorReferenceSearcher(
targetFunction: PsiElement,
private val operationTokens: List<KtSingleValueToken>,
searchScope: SearchScope,
consumer: Processor<in PsiReference>,
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
if (binaryExpression.operationToken !in operationTokens) return
if (expression != binaryExpression.left) return
processReferenceElement(binaryExpression)
}
override fun isReferenceToCheck(ref: PsiReference): Boolean {
if (ref !is KtSimpleNameReference) return false
val element = ref.element
if (element.parent !is KtBinaryExpression) return false
return element.getReferencedNameElementType() in operationTokens
}
override fun extractReference(element: KtElement): PsiReference? {
val binaryExpression = element as? KtBinaryExpression ?: return null
if (binaryExpression.operationToken !in operationTokens) return null
return binaryExpression.operationReference.references.firstIsInstance<KtSimpleNameReference>()
}
}
@@ -1,74 +0,0 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.search.usagesSearch.operators
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
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
class ContainsOperatorReferenceSearcher(
targetFunction: PsiElement,
searchScope: SearchScope,
consumer: Processor<in PsiReference>,
optimizer: SearchRequestCollector,
options: KotlinReferencesSearchOptions
) : OperatorReferenceSearcher<KtOperationReferenceExpression>(
targetFunction,
searchScope,
consumer,
optimizer,
options,
wordsToSearch = listOf("in")
) {
private companion object {
private val OPERATION_TOKENS = setOf(KtTokens.IN_KEYWORD, KtTokens.NOT_IN)
}
override fun processPossibleReceiverExpression(expression: KtExpression) {
val parent = expression.parent
when (parent) {
is KtBinaryExpression -> {
if (parent.operationToken in OPERATION_TOKENS && expression == parent.right) {
processReferenceElement(parent.operationReference)
}
}
is KtWhenConditionInRange -> {
processReferenceElement(parent.operationReference)
}
}
}
override fun isReferenceToCheck(ref: PsiReference): Boolean {
if (ref !is KtSimpleNameReference) return false
val element = ref.element as? KtOperationReferenceExpression ?: return false
return element.getReferencedNameElementType() in OPERATION_TOKENS
}
override fun extractReference(element: KtElement): PsiReference? {
val referenceExpression = element as? KtOperationReferenceExpression ?: return null
if (referenceExpression.getReferencedNameElementType() !in OPERATION_TOKENS) return null
return referenceExpression.references.firstIsInstance<KtSimpleNameReference>()
}
}
@@ -1,85 +0,0 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.search.usagesSearch.operators
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
import com.intellij.psi.search.SearchRequestCollector
import com.intellij.psi.search.SearchScope
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.idea.search.usagesSearch.dataClassComponentFunction
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
class DestructuringDeclarationReferenceSearcher(
targetDeclaration: PsiElement,
private val componentIndex: Int,
searchScope: SearchScope,
consumer: Processor<in PsiReference>,
optimizer: SearchRequestCollector,
options: KotlinReferencesSearchOptions
) : OperatorReferenceSearcher<KtDestructuringDeclaration>(
targetDeclaration,
searchScope,
consumer,
optimizer,
options,
wordsToSearch = listOf("(")
) {
override fun resolveTargetToDescriptor(): FunctionDescriptor? {
return if (targetDeclaration is KtParameter) {
targetDeclaration.dataClassComponentFunction()
} else {
super.resolveTargetToDescriptor()
}
}
override fun extractReference(element: KtElement): PsiReference? {
val destructuringDeclaration = element as? KtDestructuringDeclaration ?: return null
val entries = destructuringDeclaration.entries
if (entries.size < componentIndex) return null
return entries[componentIndex - 1].references.firstIsInstance<KtDestructuringDeclarationReference>()
}
override fun isReferenceToCheck(ref: PsiReference) = ref is KtDestructuringDeclarationReference
override fun processPossibleReceiverExpression(expression: KtExpression) {
val parent = expression.parent
val destructuringDeclaration = when (parent) {
is KtDestructuringDeclaration -> parent
is KtContainerNode -> {
if (parent.node.elementType == KtNodeTypes.LOOP_RANGE) {
(parent.parent as KtForExpression).destructuringDeclaration
} else {
null
}
}
else -> null
}
if (destructuringDeclaration != null) {
processReferenceElement(destructuringDeclaration)
}
}
}
@@ -1,68 +0,0 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.search.usagesSearch.operators
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
import com.intellij.psi.search.SearchRequestCollector
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.utils.addToStdlib.firstIsInstance
class IndexingOperatorReferenceSearcher(
targetFunction: PsiElement,
searchScope: SearchScope,
consumer: Processor<in PsiReference>,
optimizer: SearchRequestCollector,
options: KotlinReferencesSearchOptions,
private val isSet: Boolean
) : OperatorReferenceSearcher<KtArrayAccessExpression>(
targetFunction,
searchScope,
consumer,
optimizer,
options,
wordsToSearch = listOf("[")
) {
override fun processPossibleReceiverExpression(expression: KtExpression) {
val accessExpression = expression.parent as? KtArrayAccessExpression ?: return
if (expression != accessExpression.arrayExpression) return
if (!checkAccessExpression(accessExpression)) return
processReferenceElement(accessExpression)
}
override fun isReferenceToCheck(ref: PsiReference) =
ref is KtArrayAccessReference && checkAccessExpression(ref.element)
override fun extractReference(element: KtElement): PsiReference? {
val accessExpression = element as? KtArrayAccessExpression ?: return null
if (!checkAccessExpression(accessExpression)) return null
return accessExpression.references.firstIsInstance<KtArrayAccessReference>()
}
private fun checkAccessExpression(accessExpression: KtArrayAccessExpression): Boolean {
val readWriteAccess = accessExpression.readWriteAccess(useResolveForReadWrite = false)
return if (isSet) readWriteAccess.isWrite else readWriteAccess.isRead
}
}
@@ -1,92 +0,0 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.search.usagesSearch.operators
import com.intellij.openapi.components.ServiceManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
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
import org.jetbrains.kotlin.psi.psiUtil.isExtensionDeclaration
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import org.jetbrains.uast.UMethod
import org.jetbrains.uast.UastContext
import org.jetbrains.uast.convertOpt
class InvokeOperatorReferenceSearcher(
targetFunction: PsiElement,
searchScope: SearchScope,
consumer: Processor<in PsiReference>,
optimizer: SearchRequestCollector,
options: KotlinReferencesSearchOptions
) : OperatorReferenceSearcher<KtCallExpression>(targetFunction, searchScope, consumer, optimizer, options, wordsToSearch = emptyList()) {
private val callArgumentsSize: Int?
init {
val uastContext = ServiceManager.getService<UastContext>(targetFunction.project, UastContext::class.java)
callArgumentsSize = when {
uastContext != null -> {
val uMethod = uastContext.convertOpt<UMethod>(targetDeclaration, null)
val uastParameters = uMethod?.uastParameters
if (uastParameters != null) {
val isStableNumberOfArguments = uastParameters.none { uParameter ->
@Suppress("UElementAsPsi")
uParameter.uastInitializer != null || uParameter.isVarArgs
}
if (isStableNumberOfArguments) {
val numberOfArguments = uastParameters.size
when {
targetFunction.isExtensionDeclaration() -> numberOfArguments - 1
else -> numberOfArguments
}
} else {
null
}
} else {
null
}
}
else -> null
}
}
override fun processPossibleReceiverExpression(expression: KtExpression) {
val callExpression = expression.parent as? KtCallExpression ?: return
processReferenceElement(callExpression)
}
override fun isReferenceToCheck(ref: PsiReference) = ref is KtInvokeFunctionReference
override fun extractReference(element: KtElement): PsiReference? {
val callExpression = element as? KtCallExpression ?: return null
if (callArgumentsSize != null && callArgumentsSize != callExpression.valueArguments.size) {
return null
}
return callExpression.references.firstIsInstance<KtInvokeFunctionReference>()
}
}
@@ -1,54 +0,0 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.search.usagesSearch.operators
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
import com.intellij.psi.search.SearchRequestCollector
import com.intellij.psi.search.SearchScope
import com.intellij.util.Processor
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.idea.references.KtForLoopInReference
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtForExpression
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
class IteratorOperatorReferenceSearcher(
targetFunction: PsiElement,
searchScope: SearchScope,
consumer: Processor<in PsiReference>,
optimizer: SearchRequestCollector,
options: KotlinReferencesSearchOptions
) : OperatorReferenceSearcher<KtForExpression>(targetFunction, searchScope, consumer, optimizer, options, wordsToSearch = listOf("in")) {
override fun processPossibleReceiverExpression(expression: KtExpression) {
val parent = expression.parent
if (parent.node.elementType == KtNodeTypes.LOOP_RANGE) {
processReferenceElement(parent.parent as KtForExpression)
}
}
override fun isReferenceToCheck(ref: PsiReference): Boolean {
return ref is KtForLoopInReference
}
override fun extractReference(element: KtElement): PsiReference? {
return (element as? KtForExpression)?.references?.firstIsInstance<KtForLoopInReference>()
}
}
@@ -1,374 +0,0 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.search.usagesSearch.operators
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.progress.ProgressIndicatorProvider
import com.intellij.openapi.progress.util.ProgressWrapper
import com.intellij.psi.*
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.KotlinIdeaAnalysisBundle
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.caches.resolve.util.getJavaOrKotlinMemberDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.util.hasJavaResolutionFacade
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
import org.jetbrains.kotlin.idea.search.usagesSearch.ExpressionsOfTypeProcessor
import org.jetbrains.kotlin.idea.search.usagesSearch.ExpressionsOfTypeProcessor.Companion.logPresentation
import org.jetbrains.kotlin.idea.search.usagesSearch.ExpressionsOfTypeProcessor.Companion.testLog
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
import org.jetbrains.kotlin.resolve.DataClassDescriptorResolver
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.expressions.OperatorConventions
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.util.isValidOperator
import java.util.*
abstract class OperatorReferenceSearcher<TReferenceElement : KtElement>(
protected val targetDeclaration: PsiElement,
private val searchScope: SearchScope,
private val consumer: Processor<in PsiReference>,
private val optimizer: SearchRequestCollector,
private val options: KotlinReferencesSearchOptions,
private val wordsToSearch: List<String>
) {
private val project = targetDeclaration.project
/**
* Invoked for all expressions that may have type matching receiver type of our operator
*/
protected abstract fun processPossibleReceiverExpression(expression: KtExpression)
/**
* Extract reference that may resolve to our operator (no actual resolve to be performed)
*/
protected abstract fun extractReference(element: KtElement): PsiReference?
/**
* Check if reference may potentially resolve to our operator (no actual resolve to be performed)
*/
protected abstract fun isReferenceToCheck(ref: PsiReference): Boolean
protected fun processReferenceElement(element: TReferenceElement): Boolean {
val reference = extractReference(element) ?: return true
testLog { "Resolved ${logPresentation(element)}" }
return if (reference.isReferenceTo(targetDeclaration)) {
consumer.process(reference)
} else {
true
}
}
companion object {
fun create(
declaration: PsiElement,
searchScope: SearchScope,
consumer: Processor<in PsiReference>,
optimizer: SearchRequestCollector,
options: KotlinReferencesSearchOptions
): OperatorReferenceSearcher<*>? {
return runReadAction {
if (declaration.isValid)
createInReadAction(declaration, searchScope, consumer, optimizer, options)
else
null
}
}
private fun createInReadAction(
declaration: PsiElement,
searchScope: SearchScope,
consumer: Processor<in PsiReference>,
optimizer: SearchRequestCollector,
options: KotlinReferencesSearchOptions
): OperatorReferenceSearcher<*>? {
val functionName = when (declaration) {
is KtNamedFunction -> declaration.name
is PsiMethod -> declaration.name
else -> null
} ?: return null
if (!Name.isValidIdentifier(functionName)) return null
val name = Name.identifier(functionName)
val declarationToUse = if (declaration is KtLightMethod) {
declaration.kotlinOrigin ?: return null
} else {
declaration
}
return createInReadAction(declarationToUse, name, consumer, optimizer, options, searchScope)
}
private fun createInReadAction(
declaration: PsiElement,
name: Name,
consumer: Processor<in PsiReference>,
optimizer: SearchRequestCollector,
options: KotlinReferencesSearchOptions,
searchScope: SearchScope
): OperatorReferenceSearcher<*>? {
if (DataClassDescriptorResolver.isComponentLike(name)) {
if (!options.searchForComponentConventions) return null
val componentIndex = DataClassDescriptorResolver.getComponentIndex(name.asString())
return DestructuringDeclarationReferenceSearcher(declaration, componentIndex, searchScope, consumer, optimizer, options)
}
if (!options.searchForOperatorConventions) return null
val binaryOp = OperatorConventions.BINARY_OPERATION_NAMES.inverse()[name]
val assignmentOp = OperatorConventions.ASSIGNMENT_OPERATIONS.inverse()[name]
val unaryOp = OperatorConventions.UNARY_OPERATION_NAMES.inverse()[name]
when {
binaryOp != null -> {
val counterpartAssignmentOp = OperatorConventions.ASSIGNMENT_OPERATION_COUNTERPARTS.inverse()[binaryOp]
val operationTokens = listOfNotNull(binaryOp, counterpartAssignmentOp)
return BinaryOperatorReferenceSearcher(declaration, operationTokens, searchScope, consumer, optimizer, options)
}
assignmentOp != null ->
return BinaryOperatorReferenceSearcher(declaration, listOf(assignmentOp), searchScope, consumer, optimizer, options)
unaryOp != null ->
return UnaryOperatorReferenceSearcher(declaration, unaryOp, searchScope, consumer, optimizer, options)
name == OperatorNameConventions.INVOKE ->
return InvokeOperatorReferenceSearcher(declaration, searchScope, consumer, optimizer, options)
name == OperatorNameConventions.GET ->
return IndexingOperatorReferenceSearcher(declaration, searchScope, consumer, optimizer, options, isSet = false)
name == OperatorNameConventions.SET ->
return IndexingOperatorReferenceSearcher(declaration, searchScope, consumer, optimizer, options, isSet = true)
name == OperatorNameConventions.CONTAINS ->
return ContainsOperatorReferenceSearcher(declaration, searchScope, consumer, optimizer, options)
name == OperatorNameConventions.EQUALS ->
return BinaryOperatorReferenceSearcher(
declaration,
listOf(KtTokens.EQEQ, KtTokens.EXCLEQ),
searchScope,
consumer,
optimizer,
options
)
name == OperatorNameConventions.COMPARE_TO ->
return BinaryOperatorReferenceSearcher(
declaration,
listOf(KtTokens.LT, KtTokens.GT, KtTokens.LTEQ, KtTokens.GTEQ),
searchScope,
consumer,
optimizer,
options
)
name == OperatorNameConventions.ITERATOR ->
return IteratorOperatorReferenceSearcher(declaration, searchScope, consumer, optimizer, options)
name == OperatorNameConventions.GET_VALUE || name == OperatorNameConventions.SET_VALUE || name == OperatorNameConventions.PROVIDE_DELEGATE ->
return PropertyDelegationOperatorReferenceSearcher(declaration, searchScope, consumer, optimizer, options)
else ->
return null
}
}
private object SearchesInProgress : ThreadLocal<HashSet<PsiElement>>() {
override fun initialValue() = HashSet<PsiElement>()
}
}
protected open fun resolveTargetToDescriptor(): FunctionDescriptor? {
return when {
targetDeclaration is KtDeclaration -> targetDeclaration.resolveToDescriptorIfAny(BodyResolveMode.FULL)
targetDeclaration is PsiMember && targetDeclaration.hasJavaResolutionFacade() ->
targetDeclaration.getJavaOrKotlinMemberDescriptor()
else -> null
} as? FunctionDescriptor
}
fun run() {
val receiverType = runReadAction { extractReceiverType() } ?: return
val psiClass = runReadAction { receiverType.toPsiClass() }
val inProgress = SearchesInProgress.get()
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 {
ExpressionsOfTypeProcessor(
receiverType,
psiClass,
searchScope,
project,
possibleMatchHandler = { expression -> processPossibleReceiverExpression(expression) },
possibleMatchesInScopeHandler = { searchScope -> doPlainSearch(searchScope) }
).run()
} finally {
inProgress.remove(psiClass ?: 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
}
}
private fun extractReceiverType(): FuzzyType? {
val descriptor = resolveTargetToDescriptor()?.takeIf { it.isValidOperator() } ?: return null
return if (descriptor.isExtension) {
descriptor.fuzzyExtensionReceiverType()!!
} else {
val classDescriptor = descriptor.containingDeclaration as? ClassDescriptor ?: return null
classDescriptor.defaultType.toFuzzyType(classDescriptor.typeConstructor.parameters)
}
}
private fun doPlainSearch(scope: SearchScope) {
testLog { "Used plain search of ${logPresentation(targetDeclaration)} in ${scope.logPresentation()}" }
if (scope is LocalSearchScope) {
for (element in scope.scope) {
if (element is KtElement) {
runReadAction {
if (element.isValid) {
val refs = ArrayList<PsiReference>()
val elements = element.collectDescendantsOfType<KtElement> {
val ref = extractReference(it) ?: return@collectDescendantsOfType false
refs.add(ref)
true
}
// resolve all references at once
(element.containingFile as KtFile).getResolutionFacade().analyze(elements, BodyResolveMode.PARTIAL)
refs
.filter { it.isReferenceTo(targetDeclaration) }
.forEach { consumer.process(it) }
}
}
}
}
} else {
scope as GlobalSearchScope
if (wordsToSearch.isNotEmpty()) {
val unwrappedElement = targetDeclaration.namedUnwrappedElement ?: return
val resultProcessor = KotlinRequestResultProcessor(
unwrappedElement,
filter = { ref -> isReferenceToCheck(ref) },
options = options
)
wordsToSearch.forEach {
optimizer.searchWord(
it,
scope.restrictToKotlinSources(),
UsageSearchContext.IN_CODE,
true,
unwrappedElement,
resultProcessor
)
}
} else {
val psiManager = PsiManager.getInstance(project)
// we must unwrap progress indicator because ProgressWrapper does not do anything on changing text and fraction
val progress = ProgressWrapper.unwrap(ProgressIndicatorProvider.getGlobalProgressIndicator())
progress?.pushState()
progress?.text = KotlinIdeaAnalysisBundle.message("searching.for.implicit.usages")
try {
val files = runReadAction { FileTypeIndex.getFiles(KotlinFileType.INSTANCE, scope) }
for ((index, file) in files.withIndex()) {
progress?.checkCanceled()
runReadAction {
if (file.isValid) {
progress?.fraction = index / files.size.toDouble()
progress?.text2 = file.path
val psiFile = psiManager.findFile(file) as? KtFile
if (psiFile != null) {
doPlainSearch(LocalSearchScope(psiFile))
}
}
}
}
} finally {
progress?.popState()
}
}
}
}
private fun SearchScope.logPresentation(): String {
return when (this) {
searchScope -> "whole search scope"
is LocalSearchScope -> {
scope
.map { element ->
" " + 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()
}
}
}
.toList()
.sorted()
.joinToString("\n", "LocalSearchScope:\n")
}
else -> this.displayName
}
}
}
@@ -1,50 +0,0 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.search.usagesSearch.operators
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
import com.intellij.psi.search.SearchRequestCollector
import com.intellij.psi.search.SearchScope
import com.intellij.util.Processor
import org.jetbrains.kotlin.idea.references.KtPropertyDelegationMethodsReference
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtPropertyDelegate
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
class PropertyDelegationOperatorReferenceSearcher(
targetFunction: PsiElement,
searchScope: SearchScope,
consumer: Processor<in PsiReference>,
optimizer: SearchRequestCollector,
options: KotlinReferencesSearchOptions
) : OperatorReferenceSearcher<KtPropertyDelegate>(targetFunction, searchScope, consumer, optimizer, options, wordsToSearch = listOf("by")) {
override fun processPossibleReceiverExpression(expression: KtExpression) {
(expression.parent as? KtPropertyDelegate)?.let { processReferenceElement(it) }
}
override fun isReferenceToCheck(ref: PsiReference): Boolean {
return ref is KtPropertyDelegationMethodsReference
}
override fun extractReference(element: KtElement): PsiReference? {
return (element as? KtPropertyDelegate)?.references?.firstIsInstance<KtPropertyDelegationMethodsReference>()
}
}
@@ -1,66 +0,0 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.search.usagesSearch.operators
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
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.KtUnaryExpression
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
class UnaryOperatorReferenceSearcher(
targetFunction: PsiElement,
private val operationToken: KtSingleValueToken,
searchScope: SearchScope,
consumer: Processor<in PsiReference>,
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
if (unaryExpression.operationToken != operationToken) return
processReferenceElement(unaryExpression)
}
override fun isReferenceToCheck(ref: PsiReference): Boolean {
if (ref !is KtSimpleNameReference) return false
val element = ref.element
if (element.parent !is KtUnaryExpression) return false
return element.getReferencedNameElementType() == operationToken
}
override fun extractReference(element: KtElement): PsiReference? {
val unaryExpression = element as? KtUnaryExpression ?: return null
if (unaryExpression.operationToken != operationToken) return null
return unaryExpression.operationReference.references.firstIsInstance<KtSimpleNameReference>()
}
}
@@ -16,14 +16,14 @@
package org.jetbrains.kotlin.idea.search.usagesSearch
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNamedElement
import org.jetbrains.kotlin.asJava.LightClassUtil
import org.jetbrains.kotlin.asJava.LightClassUtil.PropertyAccessorsPsiMethods
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.caches.resolve.resolveToParameterDescriptorIfAny
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
@@ -31,6 +31,11 @@ import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DataClassDescriptorResolver
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.findOriginalTopMostOverriddenDescriptors
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitClassReceiver
import org.jetbrains.kotlin.resolve.source.getPsi
import java.util.*
fun PsiNamedElement.getAccessorNames(readable: Boolean = true, writable: Boolean = true): List<String> {
@@ -87,3 +92,26 @@ fun KtParameter.isDataClassProperty(): Boolean {
return this.containingClassOrObject?.hasModifier(KtTokens.DATA_KEYWORD) ?: false
}
fun getTopMostOverriddenElementsToHighlight(target: PsiElement): List<PsiElement> {
val callableDescriptor = (target as? KtCallableDeclaration)?.resolveToDescriptorIfAny() as? CallableDescriptor
val descriptorsToHighlight = if (callableDescriptor is ParameterDescriptor)
listOf(callableDescriptor)
else
callableDescriptor?.findOriginalTopMostOverriddenDescriptors() ?: emptyList()
return descriptorsToHighlight.mapNotNull { it.source.getPsi() }.filter { it != target }
}
val KtDeclaration.descriptor: DeclarationDescriptor?
get() = if (this is KtParameter) this.descriptor else this.resolveToDescriptorIfAny(BodyResolveMode.FULL)
val KtParameter.descriptor: ValueParameterDescriptor?
get() = this.resolveToParameterDescriptorIfAny(BodyResolveMode.FULL)
fun isCallReceiverRefersToCompanionObject(element: KtElement, companionObject: KtObjectDeclaration): Boolean {
val companionObjectDescriptor = companionObject.descriptor
val bindingContext = element.analyze()
val resolvedCall = bindingContext[BindingContext.CALL, element]?.getResolvedCall(bindingContext) ?: return false
return (resolvedCall.dispatchReceiver as? ImplicitClassReceiver)?.declarationDescriptor == companionObjectDescriptor ||
(resolvedCall.extensionReceiver as? ImplicitClassReceiver)?.declarationDescriptor == companionObjectDescriptor
}
@@ -1,309 +0,0 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.search.usagesSearch
import com.intellij.openapi.application.ApplicationManager
import com.intellij.psi.*
import com.intellij.psi.search.SearchScope
import com.intellij.psi.search.searches.MethodReferencesSearch
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.psi.util.MethodSignatureUtil
import org.jetbrains.kotlin.asJava.classes.KtLightClass
import org.jetbrains.kotlin.asJava.classes.lazyPub
import org.jetbrains.kotlin.asJava.elements.KtLightElement
import org.jetbrains.kotlin.asJava.elements.KtLightMethod
import org.jetbrains.kotlin.asJava.toLightMethods
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.caches.resolve.resolveToParameterDescriptorIfAny
import org.jetbrains.kotlin.idea.caches.resolve.util.getJavaMethodDescriptor
import org.jetbrains.kotlin.idea.references.unwrappedTargets
import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest
import org.jetbrains.kotlin.idea.search.declarationsSearch.searchInheritors
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.contains
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.descriptorUtil.isTypeRefinementEnabled
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
val KtDeclaration.descriptor: DeclarationDescriptor?
get() = if (this is KtParameter) this.descriptor else this.resolveToDescriptorIfAny(BodyResolveMode.FULL)
val KtDeclaration.constructor: ConstructorDescriptor?
get() {
val context = this.analyze()
return when (this) {
is KtClassOrObject -> context[BindingContext.CLASS, this]?.unsubstitutedPrimaryConstructor
is KtFunction -> context[BindingContext.CONSTRUCTOR, this]
else -> null
}
}
val KtParameter.descriptor: ValueParameterDescriptor?
get() = this.resolveToParameterDescriptorIfAny(BodyResolveMode.FULL)
val KtParameter.propertyDescriptor: PropertyDescriptor?
get() = this.resolveToDescriptorIfAny(BodyResolveMode.FULL) as? PropertyDescriptor
fun PsiReference.checkUsageVsOriginalDescriptor(
targetDescriptor: DeclarationDescriptor,
declarationToDescriptor: (KtDeclaration) -> DeclarationDescriptor? = { it.descriptor },
checker: (usageDescriptor: DeclarationDescriptor, targetDescriptor: DeclarationDescriptor) -> Boolean
): Boolean {
return unwrappedTargets
.filterIsInstance<KtDeclaration>()
.any {
val usageDescriptor = declarationToDescriptor(it)
usageDescriptor != null && checker(usageDescriptor, targetDescriptor)
}
}
fun PsiReference.isImportUsage(): Boolean =
element.getNonStrictParentOfType<KtImportDirective>() != null
fun PsiReference.isConstructorUsage(ktClassOrObject: KtClassOrObject): Boolean = with(element) {
fun checkJavaUsage(): Boolean {
val call = getNonStrictParentOfType<PsiConstructorCall>()
return call == parent && call?.resolveConstructor()?.containingClass?.navigationElement == ktClassOrObject
}
fun checkKotlinUsage(): Boolean {
if (this !is KtElement) return false
val descriptor = getConstructorCallDescriptor() as? ConstructorDescriptor ?: return false
val declaration = DescriptorToSourceUtils.descriptorToDeclaration(descriptor.containingDeclaration)
return declaration == ktClassOrObject || (declaration is KtConstructor<*> && declaration.getContainingClassOrObject() == ktClassOrObject)
}
checkJavaUsage() || checkKotlinUsage()
}
private fun KtElement.getConstructorCallDescriptor(): DeclarationDescriptor? {
val bindingContext = this.analyze()
val constructorCalleeExpression = getNonStrictParentOfType<KtConstructorCalleeExpression>()
if (constructorCalleeExpression != null) {
return bindingContext.get(BindingContext.REFERENCE_TARGET, constructorCalleeExpression.constructorReferenceExpression)
}
val callExpression = getNonStrictParentOfType<KtCallElement>()
if (callExpression != null) {
val callee = callExpression.calleeExpression
if (callee is KtReferenceExpression) {
return bindingContext.get(BindingContext.REFERENCE_TARGET, callee)
}
}
return null
}
fun PsiElement.processDelegationCallConstructorUsages(scope: SearchScope, process: (KtCallElement) -> Boolean): Boolean {
val task = buildProcessDelegationCallConstructorUsagesTask(scope, process)
return task()
}
// should be executed under read-action, returns long-running part to be executed outside read-action
fun PsiElement.buildProcessDelegationCallConstructorUsagesTask(scope: SearchScope, process: (KtCallElement) -> Boolean): () -> Boolean {
ApplicationManager.getApplication().assertReadAccessAllowed()
val task1 = buildProcessDelegationCallKotlinConstructorUsagesTask(scope, process)
val task2 = buildProcessDelegationCallJavaConstructorUsagesTask(scope, process)
return { task1() && task2() }
}
private fun PsiElement.buildProcessDelegationCallKotlinConstructorUsagesTask(
scope: SearchScope,
process: (KtCallElement) -> Boolean
): () -> Boolean {
val element = unwrapped
if (element != null && element !in scope) return { true }
val klass = when (element) {
is KtConstructor<*> -> element.getContainingClassOrObject()
is KtClass -> element
else -> return { true }
}
if (klass !is KtClass || element !is KtDeclaration) return { true }
val descriptor = lazyPub { element.constructor }
if (!processClassDelegationCallsToSpecifiedConstructor(klass, descriptor, process)) return { false }
// long-running task, return it to execute outside read-action
return { processInheritorsDelegatingCallToSpecifiedConstructor(klass, scope, descriptor, process) }
}
private fun PsiElement.buildProcessDelegationCallJavaConstructorUsagesTask(
scope: SearchScope,
process: (KtCallElement) -> Boolean
): () -> Boolean {
if (this is KtLightElement<*, *>) return { true }
// TODO: Temporary hack to avoid NPE while KotlinNoOriginLightMethod is around
if (this is KtLightMethod && this.kotlinOrigin == null) return { true }
if (!(this is PsiMethod && isConstructor)) return { true }
val klass = containingClass ?: return { true }
val descriptor = lazyPub { getJavaMethodDescriptor() as? ConstructorDescriptor }
return { processInheritorsDelegatingCallToSpecifiedConstructor(klass, scope, descriptor, process) }
}
private fun processInheritorsDelegatingCallToSpecifiedConstructor(
klass: PsiElement,
scope: SearchScope,
lazyDescriptor: Lazy<ConstructorDescriptor?>,
process: (KtCallElement) -> Boolean
): Boolean {
return HierarchySearchRequest(klass, scope, false).searchInheritors().all {
runReadAction {
val unwrapped = it.takeIf { it.isValid }?.unwrapped
if (unwrapped is KtClass)
processClassDelegationCallsToSpecifiedConstructor(unwrapped, lazyDescriptor, process)
else
true
}
}
}
private fun processClassDelegationCallsToSpecifiedConstructor(
klass: KtClass,
lazyDescriptor: Lazy<ConstructorDescriptor?>,
process: (KtCallElement) -> Boolean
): Boolean {
for (secondaryConstructor in klass.secondaryConstructors) {
val delegationCallDescriptor =
secondaryConstructor.getDelegationCall().getConstructorCallDescriptor()
?: continue
if (lazyDescriptor.value == delegationCallDescriptor) {
if (!process(secondaryConstructor.getDelegationCall())) return false
}
}
if (!klass.isEnum()) return true
for (declaration in klass.declarations) {
if (declaration is KtEnumEntry) {
val delegationCall =
declaration.superTypeListEntries.firstOrNull() as? KtSuperTypeCallEntry
?: continue
val constructorCallDescriptor =
delegationCall.calleeExpression.getConstructorCallDescriptor()
?: continue
if (lazyDescriptor.value == constructorCallDescriptor) {
if (!process(delegationCall)) return false
}
}
}
return true
}
// Check if reference resolves to extension function whose receiver is the same as declaration's parent (or its superclass)
// Used in extension search
fun PsiReference.isExtensionOfDeclarationClassUsage(declaration: KtNamedDeclaration): Boolean {
val descriptor = declaration.descriptor ?: return false
return checkUsageVsOriginalDescriptor(descriptor) { usageDescriptor, targetDescriptor ->
when (usageDescriptor) {
targetDescriptor -> false
!is FunctionDescriptor -> false
else -> {
val receiverDescriptor =
usageDescriptor.extensionReceiverParameter?.type?.constructor?.declarationDescriptor
val containingDescriptor = targetDescriptor.containingDeclaration
containingDescriptor == receiverDescriptor
|| (containingDescriptor is ClassDescriptor
&& receiverDescriptor is ClassDescriptor
&& DescriptorUtils.isSubclass(containingDescriptor, receiverDescriptor))
}
}
}
}
// Check if reference resolves to the declaration with the same parent
// Used in overload search
fun PsiReference.isUsageInContainingDeclaration(declaration: KtNamedDeclaration): Boolean {
val descriptor = declaration.descriptor ?: return false
return checkUsageVsOriginalDescriptor(descriptor) { usageDescriptor, targetDescriptor ->
usageDescriptor != targetDescriptor
&& usageDescriptor.containingDeclaration == targetDescriptor.containingDeclaration
}
}
fun PsiReference.isCallableOverrideUsage(declaration: KtNamedDeclaration): Boolean {
val toDescriptor: (KtDeclaration) -> CallableDescriptor? = { sourceDeclaration ->
if (sourceDeclaration is KtParameter) {
// we don't treat parameters in overriding method as "override" here (overriding parameters usages are searched optionally and via searching of overriding methods first)
if (sourceDeclaration.hasValOrVar()) sourceDeclaration.propertyDescriptor else null
} else {
sourceDeclaration.descriptor as? CallableDescriptor
}
}
val targetDescriptor = toDescriptor(declaration) ?: return false
return unwrappedTargets.any {
when (it) {
is KtDeclaration -> {
val usageDescriptor = toDescriptor(it)
usageDescriptor != null && OverridingUtil.overrides(
usageDescriptor,
targetDescriptor,
usageDescriptor.module.isTypeRefinementEnabled(),
false // don't distinguish between expect and non-expect callable descriptors, KT-38298, KT-38589
)
}
is PsiMethod -> {
declaration.toLightMethods().any { superMethod -> MethodSignatureUtil.isSuperMethod(superMethod, it) }
}
else -> false
}
}
}
fun PsiElement.searchReferencesOrMethodReferences(): Collection<PsiReference> {
val lightMethods = toLightMethods()
return if (lightMethods.isNotEmpty()) {
lightMethods.flatMapTo(LinkedHashSet()) { MethodReferencesSearch.search(it) }
} else {
ReferencesSearch.search(this).findAll()
}
}
fun <T : PsiNamedElement> List<T>.filterDataClassComponentsIfDisabled(kotlinOptions: KotlinReferencesSearchOptions): List<T> {
if (kotlinOptions.searchForComponentConventions) return this
fun PsiNamedElement.isComponentElement(): Boolean {
if (this !is PsiMethod) return false
val dataClassParent = ((parent as? KtLightClass)?.kotlinOrigin as? KtClass)?.isData() == true
if (!dataClassParent) return false
if (!Name.isValidIdentifier(name)) return false
val nameIdentifier = Name.identifier(name)
if (!DataClassDescriptorResolver.isComponentLike(nameIdentifier)) return false
return true
}
return filter { !it.isComponentElement() }
}