Optimization of parameter usages search

This commit is contained in:
Valentin Kipyatkov
2015-07-30 12:18:02 +03:00
parent 1d1d834901
commit 9bb2fe9a67
7 changed files with 153 additions and 69 deletions
@@ -179,13 +179,24 @@ public class JetParameter extends JetNamedDeclarationStub<KotlinParameterStub> i
@NotNull
@Override
public SearchScope getUseScope() {
PsiElement parent = getParent();
if (parent != null) {
PsiElement grandparent = parent.getParent();
if (grandparent instanceof JetFunction && !(grandparent instanceof JetFunctionLiteral)) {
return grandparent.getUseScope();
}
JetDeclaration function = getOwnerFunction();
if (function != null && !(function instanceof JetFunctionLiteral)) {
return function.getUseScope();
}
return super.getUseScope();
}
@NotNull
public SearchScope getUseScopeExceptNamedArguments() {
return super.getUseScope();
}
@Nullable
public JetFunction getOwnerFunction() {
PsiElement parent = getParent();
if (parent == null) return null;
PsiElement grandparent = parent.getParent();
if (!(grandparent instanceof JetFunction)) return null;
return (JetFunction) grandparent;
}
}
@@ -32,7 +32,9 @@ import org.jetbrains.kotlin.lexer.JetLexer
import org.jetbrains.kotlin.lexer.JetTokens
import java.util.ArrayDeque
class KotlinFilterLexer(table: OccurrenceConsumer): BaseFilterLexer(JetLexer(), table) {
val KOTLIN_NAMED_ARGUMENT_SEARCH_CONTEXT: Short = 0x20
class KotlinFilterLexer(private val occurrenceConsumer: OccurrenceConsumer): BaseFilterLexer(JetLexer(), occurrenceConsumer) {
private val codeTokens = TokenSet.orSet(
TokenSet.create(*ALL_SEARCHABLE_OPERATIONS.toTypedArray()),
TokenSet.create(JetTokens.IDENTIFIER, JetTokens.FIELD_IDENTIFIER)
@@ -46,15 +48,30 @@ class KotlinFilterLexer(table: OccurrenceConsumer): BaseFilterLexer(JetLexer(),
)
private val MAX_PREV_TOKENS = 2
private val previousTokens = ArrayDeque<IElementType>(MAX_PREV_TOKENS)
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) {
in codeTokens -> addOccurrenceInToken(UsageSearchContext.IN_CODE.toInt())
JetTokens.EQ -> {
if (prevTokens.peekFirst() == JetTokens.IDENTIFIER) {
val prevPrev = prevTokens.elementAtOrNull(1)
if (prevPrev == JetTokens.COMMA || prevPrev == JetTokens.LPAR) {
occurrenceConsumer.addOccurrence(bufferSequence, null, prevTokenStart, prevTokenEnd, KOTLIN_NAMED_ARGUMENT_SEARCH_CONTEXT.toInt())
}
}
}
JetTokens.LPAR -> if (isMultiDeclarationPosition()) addOccurrenceInToken(UsageSearchContext.IN_CODE.toInt())
JetTokens.LPAR -> {
if (isMultiDeclarationPosition()) {
addOccurrenceInToken(UsageSearchContext.IN_CODE.toInt())
}
}
in codeTokens -> addOccurrenceInToken(UsageSearchContext.IN_CODE.toInt())
in JetTokens.STRINGS -> scanWordsInToken(UsageSearchContext.IN_STRINGS + UsageSearchContext.IN_FOREIGN_LANGUAGES, false, true)
@@ -66,26 +83,29 @@ class KotlinFilterLexer(table: OccurrenceConsumer): BaseFilterLexer(JetLexer(),
!in skipTokens -> scanWordsInToken(UsageSearchContext.IN_PLAIN_TEXT.toInt(), false, false)
}
if (tokenType != TokenType.WHITE_SPACE) {
if (previousTokens.size() == MAX_PREV_TOKENS) {
previousTokens.removeLast()
if (tokenType != TokenType.WHITE_SPACE && tokenType !in commentTokens) {
if (prevTokens.size() == MAX_PREV_TOKENS) {
prevTokens.removeLast()
}
previousTokens.addFirst(tokenType)
prevTokens.addFirst(tokenType)
prevTokenStart = tokenStart
prevTokenEnd = tokenEnd
}
myDelegate.advance()
}
private fun isMultiDeclarationPosition(): Boolean {
val first = previousTokens.peekFirst()
val first = prevTokens.peekFirst()
if (first == JetTokens.VAL_KEYWORD || first == JetTokens.VAR_KEYWORD) return true
return first == JetTokens.LPAR && previousTokens.elementAtOrNull(1) == JetTokens.FOR_KEYWORD
return first == JetTokens.LPAR && prevTokens.elementAtOrNull(1) == JetTokens.FOR_KEYWORD
}
}
class KotlinIdIndexer: LexerBasedIdIndexer() {
override fun createLexer(consumer: OccurrenceConsumer): Lexer = KotlinFilterLexer(consumer)
override fun getVersion() = 2
}
class KotlinTodoIndexer: LexerBasedTodoIndexer(), IdAndToDoScannerBasedOnFilterLexer {
@@ -18,15 +18,21 @@ package org.jetbrains.kotlin.idea.search.usagesSearch
import com.intellij.psi.PsiNamedElement
import com.intellij.psi.PsiReference
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.UsageSearchContext
import com.intellij.util.Processor
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.kotlin.asJava.LightClassUtil
import org.jetbrains.kotlin.asJava.LightClassUtil.PropertyAccessorsPsiMethods
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.references.JetSimpleNameReference
import org.jetbrains.kotlin.idea.references.matchesTarget
import org.jetbrains.kotlin.idea.search.KOTLIN_NAMED_ARGUMENT_SEARCH_CONTEXT
import org.jetbrains.kotlin.idea.search.and
import org.jetbrains.kotlin.idea.search.usagesSearch.UsagesSearchFilter.False
import org.jetbrains.kotlin.idea.search.usagesSearch.UsagesSearchFilter.True
import org.jetbrains.kotlin.idea.stubindex.JetSourceFilterScope
import org.jetbrains.kotlin.lexer.JetSingleValueToken
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.name.Name
@@ -196,7 +202,7 @@ class ClassDeclarationsUsagesSearchHelper(
for (decl in target.element.effectiveDeclarations()) {
if ((decl is JetNamedFunction && functionUsages) || ((decl is JetProperty || decl is JetParameter) && propertyUsages)) {
items.add(declHelper.newItem(target.retarget(decl as JetNamedDeclaration)))
items.add(declHelper.newItem(target.withTarget(decl as JetNamedDeclaration)))
}
}
@@ -237,10 +243,46 @@ val isPropertyReadOnlyUsage = (PsiReference::isPropertyReadOnlyUsage).searchFilt
class PropertyUsagesSearchHelper(
public val readUsages: Boolean = true,
public val writeUsages: Boolean = true,
public val namedArgumentUsages: Boolean = true,
public override val selfUsages: Boolean = true,
public override val overrideUsages: Boolean = true,
skipImports: Boolean = false
) : DefaultSearchHelper<JetNamedDeclaration>(skipImports), OverrideSearchHelper {
override fun makeItemList(target: UsagesSearchTarget<JetNamedDeclaration>): List<UsagesSearchRequestItem> {
val element = target.element
if (element is JetParameter) {
val realUsagesScope = element.useScopeExceptNamedArguments
val realUsagesTarget = target.withScope(realUsagesScope and target.effectiveScope)
val realUsagesFilter = makeFilter(target) and !(PsiReference::isNamedArgumentUsage).searchFilter
val realUsagesRequest = UsagesSearchRequestItem(realUsagesTarget, makeWordList(target), realUsagesFilter, makeAdditionalSearchDelegate(target.element))
if (!namedArgumentUsages) {
return listOf(realUsagesRequest)
}
val function = element.ownerFunction
?: return listOf(realUsagesRequest) // parameter in for or something like that
if (function.nameAsName?.isSpecial ?: true) { // function literal or anonymous function
return listOf(realUsagesRequest)
}
var scope = target.effectiveScope
if (scope is GlobalSearchScope) {
scope = JetSourceFilterScope.kotlinSources(scope, element.project)
}
val additionalFilter1 = AdditionalFileFilter(element.name!!, KOTLIN_NAMED_ARGUMENT_SEARCH_CONTEXT, true)
val additionalFilter2 = AdditionalFileFilter(function.name!!, UsageSearchContext.IN_CODE, true)
val namedArgsRequest = UsagesSearchRequestItem(target.withScope(scope),
listOf(element.name!!),
(PsiReference::isNamedArgumentUsage).searchFilter,
additionalFileFilters = listOf(additionalFilter1, additionalFilter2))
return listOf(realUsagesRequest, namedArgsRequest)
}
return super<DefaultSearchHelper>.makeItemList(target)
}
override fun makeWordList(target: UsagesSearchTarget<JetNamedDeclaration>): List<String> {
return with(target.element) {
ContainerUtil.createMaybeSingletonList(getName()) +
@@ -250,12 +292,26 @@ class PropertyUsagesSearchHelper(
}
override fun makeFilter(target: UsagesSearchTarget<JetNamedDeclaration>): UsagesSearchFilter {
val readWriteUsage = when {
val readWriteFilter = when {
readUsages && writeUsages -> True
readUsages -> isPropertyReadOnlyUsage
writeUsages -> !isPropertyReadOnlyUsage
else -> False
}
return isTargetOrOverrideUsage and readWriteUsage and isFilteredImport
var result = isTargetOrOverrideUsage and readWriteFilter and isFilteredImport
if (!namedArgumentUsages) {
result = result and !(PsiReference::isNamedArgumentUsage).searchFilter
}
return result
}
companion object {
}
}
private fun PsiReference.isNamedArgumentUsage(): Boolean {
if (this !is JetSimpleNameReference) return false
return expression.parent is JetValueArgumentName
}
@@ -16,40 +16,25 @@
package org.jetbrains.kotlin.idea.search.usagesSearch
import com.intellij.psi.PsiReference
import com.intellij.util.QueryFactory
import com.intellij.psi.search.SearchScope
import com.intellij.psi.search.SearchRequestCollector
import com.intellij.psi.search.SearchSession
import com.intellij.psi.search.PsiSearchHelper
import com.intellij.util.Query
import com.intellij.util.MergeQuery
import com.intellij.psi.search.SearchRequestQuery
import com.intellij.util.UniqueResultsQuery
import com.intellij.psi.search.searches.ReferenceDescriptor
import com.intellij.util.containers.ContainerUtil
import com.intellij.psi.search.UsageSearchContext
import com.intellij.psi.PsiElement
import com.intellij.util.Processor
import com.intellij.openapi.application.QueryExecutorBase
import com.intellij.psi.PsiReferenceService
import com.intellij.openapi.progress.ProgressManager
import com.intellij.psi.ReferenceRange
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.idea.search.usagesSearch.UsagesSearchFilter.*
import org.jetbrains.kotlin.idea.search.and
import com.intellij.psi.impl.search.PsiSearchHelperImpl
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.impl.cache.impl.id.IdIndexEntry
import java.util.Collections
import com.intellij.openapi.roots.FileIndexFacade
import com.intellij.util.indexing.FileBasedIndex
import com.intellij.psi.impl.cache.impl.id.IdIndex
import org.jetbrains.kotlin.idea.util.application.runReadAction
import com.intellij.psi.search.TextOccurenceProcessor
import com.intellij.psi.PsiManager
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.*
import com.intellij.psi.impl.PsiManagerEx
import com.intellij.psi.impl.cache.impl.id.IdIndex
import com.intellij.psi.impl.cache.impl.id.IdIndexEntry
import com.intellij.psi.impl.search.PsiSearchHelperImpl
import com.intellij.psi.search.*
import com.intellij.psi.search.searches.ReferenceDescriptor
import com.intellij.util.*
import com.intellij.util.containers.ContainerUtil
import com.intellij.util.indexing.FileBasedIndex
import org.jetbrains.kotlin.idea.search.and
import org.jetbrains.kotlin.idea.search.usagesSearch.UsagesSearchFilter.False
import org.jetbrains.kotlin.idea.util.application.runReadAction
import java.util.Collections
public data class UsagesSearchLocation(
val inCode: Boolean = true,
@@ -81,7 +66,10 @@ public data class UsagesSearchTarget<out T : PsiElement>(
val location: UsagesSearchLocation = UsagesSearchLocation.DEFAULT,
val restrictByTargetScope: Boolean = true
) {
fun <U: PsiElement> retarget(element: U) =
fun <U: PsiElement> withTarget(element: U) =
UsagesSearchTarget(element, scope, location, restrictByTargetScope)
fun withScope(scope: SearchScope) =
UsagesSearchTarget(element, scope, location, restrictByTargetScope)
val effectiveScope: SearchScope
@@ -100,11 +88,18 @@ public interface UsagesSearchFilter {
fun accepts(ref: PsiReference, item: UsagesSearchRequestItem): Boolean
}
public data class UsagesSearchRequestItem(
public class UsagesSearchRequestItem(
val target: UsagesSearchTarget<PsiElement>,
val words: List<String>,
val filter: UsagesSearchFilter,
val additionalSearchDelegate: ((Processor<PsiReference>) -> Boolean)?
val additionalSearchDelegate: ((Processor<PsiReference>) -> Boolean)? = null,
val additionalFileFilters: Collection<AdditionalFileFilter> = emptyList()
)
public data class AdditionalFileFilter(
val word: String,
val searchContext: Short,
val caseSensitively: Boolean
)
public data class UsagesSearchRequest(val project: Project, val items: List<UsagesSearchRequestItem>) {
@@ -153,7 +148,16 @@ public class KotlinPsiSearchHelper(private val project: Project): PsiSearchHelpe
}
public fun processFilesWithText(item: UsagesSearchRequestItem, consumer: Processor<PsiReference>): Boolean {
val scope = runReadAction { item.target.effectiveScope }
var scope = runReadAction { item.target.effectiveScope }
for (filter in item.additionalFileFilters) {
if (scope !is GlobalSearchScope) break
val processor = CommonProcessors.CollectProcessor<VirtualFile>()
processFilesWithText(scope, filter.searchContext, filter.caseSensitively, filter.word, processor)
if (processor.results.isEmpty()) return true
scope = GlobalSearchScope.filesScope(project, processor.results)
}
return item.words.all { word ->
val textProcessor = ResultTextProcessorImpl(item, consumer)
processElementsWithWord(textProcessor, scope, word, item.target.location.searchContext, true)
@@ -16,19 +16,14 @@
package org.jetbrains.kotlin.idea.findUsages
import com.intellij.find.findUsages.JavaMethodFindUsagesOptions
import com.intellij.openapi.project.Project
import com.intellij.find.findUsages.JavaClassFindUsagesOptions
import org.jetbrains.kotlin.idea.search.usagesSearch.ClassUsagesSearchHelper
import org.jetbrains.kotlin.idea.search.usagesSearch.UsagesSearchTarget
import com.intellij.psi.PsiNamedElement
import org.jetbrains.kotlin.idea.search.usagesSearch.UsagesSearchLocation
import com.intellij.find.findUsages.FindUsagesOptions
import org.jetbrains.kotlin.idea.search.usagesSearch.PropertyUsagesSearchHelper
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.idea.search.usagesSearch.ClassDeclarationsUsagesSearchHelper
import org.jetbrains.kotlin.idea.search.usagesSearch.FunctionUsagesSearchHelper
import com.intellij.find.findUsages.JavaClassFindUsagesOptions
import com.intellij.find.findUsages.JavaMethodFindUsagesOptions
import com.intellij.find.findUsages.JavaVariableFindUsagesOptions
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiNamedElement
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.idea.search.usagesSearch.*
public class KotlinClassFindUsagesOptions(project: Project) : JavaClassFindUsagesOptions(project) {
public var searchConstructorUsages: Boolean = true
@@ -101,6 +96,7 @@ fun KotlinPropertyFindUsagesOptions.toHelper(): PropertyUsagesSearchHelper =
PropertyUsagesSearchHelper(
selfUsages = isUsages,
overrideUsages = isUsages,
namedArgumentUsages = isUsages,
readUsages = isReadAccess,
writeUsages = isWriteAccess,
skipImports = isSkipImportStatements
@@ -40,7 +40,6 @@ import org.jetbrains.kotlin.descriptors.annotations.Annotated
import org.jetbrains.kotlin.idea.JetBundle
import org.jetbrains.kotlin.idea.findUsages.KotlinFindUsagesHandlerFactory
import org.jetbrains.kotlin.idea.findUsages.handlers.KotlinFindClassUsagesHandler
import org.jetbrains.kotlin.idea.project.ProjectStructureUtil
import org.jetbrains.kotlin.idea.search.usagesSearch.*
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.lexer.JetTokens
@@ -220,7 +219,7 @@ public class UnusedSymbolInspection : AbstractKotlinInspection() {
val searchHelper: UsagesSearchHelper<out JetNamedDeclaration> = when (declaration) {
is JetClassOrObject -> ClassUsagesSearchHelper(constructorUsages = true, nonConstructorUsages = true, skipImports = true)
is JetNamedFunction -> FunctionUsagesSearchHelper(skipImports = true)
is JetProperty, is JetParameter -> PropertyUsagesSearchHelper(skipImports = true)
is JetProperty, is JetParameter -> PropertyUsagesSearchHelper(skipImports = true, namedArgumentUsages = false)
else -> DefaultSearchHelper<JetNamedDeclaration>()
}
@@ -1,5 +1,3 @@
[kotlinPrivatePropertyUsages2.0.kt] Named argument (9: 33) public class ServerEx(): Server(foo = "!") {
[kotlinPrivatePropertyUsages2.0.kt] Value read (10: 45) override fun processRequest() = "foo" + foo // this reference is found as a side effect of big use scope of constructor parameter:
[kotlinPrivatePropertyUsages2.0.kt] Value read (6: 33) open fun processRequest() = foo
[kotlinPrivatePropertyUsages2.1.kt] Named argument (5: 24) println(Server(foo = "!").foo)
[kotlinPrivatePropertyUsages2.1.kt] Value read (5: 35) println(Server(foo = "!").foo)
[kotlinPrivatePropertyUsages2.1.kt] Named argument (5: 24) println(Server(foo = "!").foo)