Handling of 2 more class of cases:

1. Function has parameter of functional type with our data class inside
2. Value of data class type used in a call with lambda
This commit is contained in:
Valentin Kipyatkov
2016-08-26 18:56:32 +03:00
parent ca2cd3d656
commit 5b3102058c
4 changed files with 158 additions and 47 deletions
@@ -39,7 +39,6 @@ import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.references.KtDestructuringDeclarationReference
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinRequestResultProcessor
import org.jetbrains.kotlin.idea.search.restrictToKotlinSources
import org.jetbrains.kotlin.idea.search.unionSafe
import org.jetbrains.kotlin.idea.search.usagesSearch.DestructuringDeclarationUsageSearch.*
import org.jetbrains.kotlin.idea.util.FuzzyType
import org.jetbrains.kotlin.idea.util.fuzzyExtensionReceiverType
@@ -125,9 +124,14 @@ private class Processor(
private val plainSearchHandler: (SearchScope) -> Unit
) {
private val project = target.project
private val declarationsToSearch = ArrayList<PsiElement>()
private val declarationsToSearchSet = HashSet<PsiElement>()
private var scopeToUsePlainSearch: SearchScope = LocalSearchScope.EMPTY
// 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
//TODO: what about Scala and other JVM-languages?
private val declarationUsageScope = GlobalSearchScope.projectScope(project).restrictToKotlinSources() //TODO: search in libraries?
private val declarationsToProcess = ArrayDeque<PsiElement>()
private val declarationsToProcessSet = HashSet<PsiElement>()
private val scopesToUsePlainSearch = LinkedHashMap<KtFile, ArrayList<PsiElement>>()
fun run() {
val dataClassDescriptor = dataType.type.constructor.declarationDescriptor ?: return
@@ -160,20 +164,20 @@ private class Processor(
(classToSearch as? KtLightClass)?.kotlinOrigin?.let { usePlainSearch(it) }
}
// we use index instead of iterator because elements are added during iteration
var index = 0
while (index < declarationsToSearch.size) {
val declaration = declarationsToSearch[index++]
processDeclarationOfTypeWithDataClass(declaration)
}
processDeclarations()
plainSearchHandler(scopeToUsePlainSearch)
val scopeElements = scopesToUsePlainSearch.values.flatMap { it }.toTypedArray()
plainSearchHandler(LocalSearchScope(scopeElements))
}
//TODO: check if it's operator (too expensive)
private fun addDeclarationToProcess(declaration: PsiElement) {
if (declarationsToSearchSet.add(declaration)) {
declarationsToSearch.add(declaration)
/**
* Adds declaration whose type is our data class (or data class used anywhere inside that type)
* or which has parameter of functional type with our data class used inside
*/
private fun addCallableDeclarationToProcess(declaration: PsiElement) {
if (declarationsToProcessSet.add(declaration)) {
declarationsToProcess.push(declaration)
}
}
@@ -205,7 +209,18 @@ private class Processor(
is KtCallableDeclaration -> {
when (typeRef) {
typeRefParent.typeReference -> {
addDeclarationToProcess(typeRefParent)
addCallableDeclarationToProcess(typeRefParent)
if (typeRefParent is KtParameter) {
val usedInsideFunctionalType = parent.parents.takeWhile { it != typeRef }.any { it is KtFunctionType }
if (usedInsideFunctionalType) {
val function = (typeRefParent.parent as? KtParameterList)?.parent as? KtFunction
if (function != null) {
addCallableDeclarationToProcess(function)
}
}
}
return true
}
@@ -288,7 +303,7 @@ private class Processor(
is PsiMethod, is PsiField -> {
if (!(parent as PsiModifierListOwner).isPrivateOrLocal()) {
addDeclarationToProcess(parent)
addCallableDeclarationToProcess(parent)
}
break@ParentsLoop
}
@@ -298,29 +313,27 @@ private class Processor(
return true
}
/**
* Process declaration whose type is our data class (or data class used anywhere inside that type)
*/
private fun processDeclarationOfTypeWithDataClass(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
//TODO: what about Scala and other JVM-languages?
val declarationUsageScope = GlobalSearchScope.projectScope(declaration.project).restrictToKotlinSources()
ReferencesSearch.search(declaration, declarationUsageScope).forEach { reference ->
if (reference is KtDestructuringDeclarationReference) { // declaration usage in form of destructuring declaration
val entries = reference.element.entries
val componentIndex = when (declaration) {
is KtParameter -> declaration.dataClassComponentFunction()?.name?.asString()?.let { getComponentIndex(it) }
is KtFunction -> declaration.name?.let { getComponentIndex(it) }
private fun processDeclarations() {
while (declarationsToProcess.isNotEmpty()) {
val declaration = declarationsToProcess.pop()
ReferencesSearch.search(declaration, declarationUsageScope).forEach { reference ->
if (reference is KtDestructuringDeclarationReference) { // declaration usage in form of destructuring declaration
val entries = reference.element.entries
val componentIndex = when (declaration) {
is KtParameter -> declaration.dataClassComponentFunction()?.name?.asString()?.let { getComponentIndex(it) }
is KtFunction -> declaration.name?.let { getComponentIndex(it) }
//TODO: java component functions (see KT-13605)
else -> null
else -> null
}
if (componentIndex != null && componentIndex <= entries.size) {
addCallableDeclarationToProcess(entries[componentIndex - 1])
}
}
if (componentIndex != null && componentIndex <= entries.size) {
processDeclarationOfTypeWithDataClass(entries[componentIndex - 1])
else {
(reference.element as? KtReferenceExpression)?.let { processSuspiciousExpression(it) }
}
}
else {
(reference.element as? KtReferenceExpression)?.let { processSuspiciousExpression(it) }
}
}
}
@@ -328,11 +341,13 @@ private class Processor(
* Process expression which may have type of our data class (or data class used anywhere inside that type)
*/
private fun processSuspiciousExpression(expression: KtExpression) {
var affectedScope: PsiElement = expression
ParentsLoop@
for (container in expression.parentsWithSelf) {
if (container !is KtExpression) continue
val parent = container.parent
for (element in expression.parentsWithSelf) {
affectedScope = element
if (element !is KtExpression) continue
val parent = element.parent
when (parent) {
is KtDestructuringDeclaration -> {
processSuspiciousDeclaration(parent)
@@ -340,10 +355,9 @@ private class Processor(
}
is KtWithExpressionInitializer -> {
if (container != parent.initializer) continue@ParentsLoop
processSuspiciousDeclaration(parent)
if (element == parent.initializer) {
processSuspiciousDeclaration(parent)
}
break@ParentsLoop
}
@@ -353,9 +367,19 @@ private class Processor(
(forExpression.destructuringParameter ?: 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 data class
affectedScope.forEachDescendantOfType<KtFunction> {
if (it.nameIdentifier == null) {
usePlainSearch(it)
}
}
}
@@ -377,15 +401,53 @@ private class Processor(
val descriptor = declaration.resolveToDescriptorIfAny() as? CallableDescriptor ?: return
val type = descriptor.returnType
if (type != null && type.containsTypeOrDerivedInside(dataType)) {
addDeclarationToProcess(declaration)
addCallableDeclarationToProcess(declaration)
}
}
}
private fun usePlainSearch(scopeElement: KtElement) {
scopeToUsePlainSearch = LocalSearchScope(scopeElement)
.intersectWith(searchScope)
.unionSafe(scopeToUsePlainSearch)
val file = scopeElement.getContainingKtFile()
val restricted = LocalSearchScope(scopeElement).intersectWith(searchScope) as LocalSearchScope
ScopeLoop@
for (element in restricted.scope) {
val prevElements = scopesToUsePlainSearch.getOrPut(file) { ArrayList() }
for ((index, prevElement) in prevElements.withIndex()) {
if (prevElement.isAncestor(element, strict = false)) continue@ScopeLoop
if (element.isAncestor(prevElement)) {
prevElements[index] = element
continue@ScopeLoop
}
}
prevElements.add(element)
}
}
//TODO: code is quite similar to PartialBodyResolveFilter.isValueNeeded
private fun KtExpression.mayTypeAffectAncestors(): Boolean {
val parent = this.parent
when (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 PsiModifierListOwner.isPrivateOrLocal(): Boolean {
@@ -0,0 +1,31 @@
package pack4
import pack.A
fun takeExtFun(p: A.() -> Unit) {
}
fun takeFun1(p: (A) -> Unit) {
}
fun takeFun2(p: ((A?, Int) -> Unit)?) {
}
fun takeFun3(p: (List<A>) -> Unit) {
}
fun takeFuns(p: MutableList<(A) -> Unit>) {
p[0] = { val (x, y) = it }
}
fun <T> x(p1: T, p2: (T) -> Unit){}
fun foo(p: A) {
takeExtFun { val (x, y) = this }
takeFun1 { val (x, y) = it }
takeFun2 { a, n -> val (x, y) = a!! }
takeFun3 { val (x, y) = it[0] }
x(p) { val (x, y) = it }
}
@@ -0,0 +1,11 @@
package pack5
import pack.A
var Any.v: (A) -> Unit
get() = TODO()
set(value) = TODO()
fun f() {
"".v = { val (x, y ) = it }
}
@@ -15,4 +15,11 @@
[dataClass.2.kt] Value read 8 val (x, y, z) = a
[dataClass.2.kt] Value read 9 val (x1, y1, z1) = f()
[dataClass.3.kt] Value read 12 val (x, y, z) = a
[dataClass.3.kt] Value read 15 val (x1, y1, z1) = a1
[dataClass.3.kt] Value read 15 val (x1, y1, z1) = a1
[dataClass.4.kt] Value read 18 p[0] = { val (x, y) = it }
[dataClass.4.kt] Value read 24 takeExtFun { val (x, y) = this }
[dataClass.4.kt] Value read 26 takeFun1 { val (x, y) = it }
[dataClass.4.kt] Value read 27 takeFun2 { a, n -> val (x, y) = a!! }
[dataClass.4.kt] Value read 28 takeFun3 { val (x, y) = it[0] }
[dataClass.4.kt] Value read 30 x(p) { val (x, y) = it }
[dataClass.5.kt] Value read 10 "".v = { val (x, y ) = it }