Analyze Data Flow: Support lambdas/anonymous functions

#KT-11994 In Progress
This commit is contained in:
Alexey Sedunov
2017-05-31 21:31:23 +03:00
parent 0f44dd6ab0
commit 3f411fc93b
40 changed files with 494 additions and 29 deletions
@@ -23,8 +23,9 @@ import com.intellij.util.Processor
class KotlinSliceDereferenceUsage( class KotlinSliceDereferenceUsage(
element: PsiElement, element: PsiElement,
parent: KotlinSliceUsage parent: KotlinSliceUsage,
) : KotlinSliceUsage(element, parent) { lambdaLevel: Int
) : KotlinSliceUsage(element, parent, lambdaLevel, false) {
override fun processChildren(processor: Processor<SliceUsage>) { override fun processChildren(processor: Processor<SliceUsage>) {
// no children // no children
} }
@@ -23,13 +23,23 @@ import com.intellij.util.Processor
import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtExpression
open class KotlinSliceUsage : SliceUsage { open class KotlinSliceUsage : SliceUsage {
constructor(element: PsiElement, parent: SliceUsage) : super(element, parent) val lambdaLevel: Int
constructor(element: PsiElement, params: SliceAnalysisParams) : super(element, params) val forcedExpressionMode: Boolean
constructor(element: PsiElement, parent: SliceUsage, lambdaLevel: Int, forcedExpressionMode: Boolean) : super(element, parent) {
this.lambdaLevel = lambdaLevel
this.forcedExpressionMode = forcedExpressionMode
}
constructor(element: PsiElement, params: SliceAnalysisParams) : super(element, params) {
this.lambdaLevel = 0
this.forcedExpressionMode = false
}
override fun copy(): KotlinSliceUsage { override fun copy(): KotlinSliceUsage {
val element = usageInfo.element!! val element = usageInfo.element!!
if (parent == null) return KotlinSliceUsage(element, params) if (parent == null) return KotlinSliceUsage(element, params)
return KotlinSliceUsage(element, parent) return KotlinSliceUsage(element, parent, lambdaLevel, forcedExpressionMode)
} }
public override fun processUsagesFlownDownTo(element: PsiElement, uniqueProcessor: Processor<SliceUsage>) { public override fun processUsagesFlownDownTo(element: PsiElement, uniqueProcessor: Processor<SliceUsage>) {
@@ -53,6 +53,8 @@ object KotlinSliceUsageCellRenderer : SliceUsageCellRendererBase() {
} }
} }
append(" (Tracking enclosing lambda)".repeat(sliceUsage.lambdaLevel), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES)
val declaration = sliceUsage.element?.parents?.firstOrNull { val declaration = sliceUsage.element?.parents?.firstOrNull {
it is KtClass || it is KtClass ||
it is KtObjectDeclaration && !it.isObjectLiteral() || it is KtObjectDeclaration && !it.isObjectLiteral() ||
@@ -16,13 +16,13 @@
package org.jetbrains.kotlin.idea.slicer package org.jetbrains.kotlin.idea.slicer
import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector
import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector.Access import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector.Access
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.search.SearchScope import com.intellij.psi.search.SearchScope
import com.intellij.slicer.SliceUsage import com.intellij.slicer.SliceUsage
import com.intellij.usageView.UsageInfo import com.intellij.usageView.UsageInfo
import com.intellij.util.Processor import com.intellij.util.Processor
import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor
import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue
import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode
import org.jetbrains.kotlin.cfg.pseudocode.containingDeclarationForPseudocode import org.jetbrains.kotlin.cfg.pseudocode.containingDeclarationForPseudocode
@@ -46,6 +46,7 @@ import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.resolve.source.getPsi import org.jetbrains.kotlin.resolve.source.getPsi
import java.util.* import java.util.*
@@ -111,8 +112,11 @@ abstract class Slicer(
protected val pseudocodeCache = PseudocodeCache() protected val pseudocodeCache = PseudocodeCache()
protected fun PsiElement.passToProcessor() { protected fun PsiElement.passToProcessor(
processor.process(KotlinSliceUsage(this, parentUsage)) lambdaLevel: Int = parentUsage.lambdaLevel,
forcedExpressionMode: Boolean = false
) {
processor.process(KotlinSliceUsage(this, parentUsage, lambdaLevel, forcedExpressionMode))
} }
abstract fun processChildren() abstract fun processChildren()
@@ -123,14 +127,16 @@ class InflowSlicer(
processor: Processor<SliceUsage>, processor: Processor<SliceUsage>,
parentUsage: KotlinSliceUsage parentUsage: KotlinSliceUsage
) : Slicer(element, processor, parentUsage) { ) : Slicer(element, processor, parentUsage) {
private fun PsiElement.passToProcessorAsValue(lambdaLevel: Int = parentUsage.lambdaLevel) = passToProcessor(lambdaLevel, true)
private fun KtProperty.processProperty() { private fun KtProperty.processProperty() {
if (!canProcess()) return if (!canProcess()) return
initializer?.passToProcessor() initializer?.passToProcessorAsValue()
processVariableAccesses(parentUsage.scope.toSearchScope(), Access.Write) body@ { processVariableAccesses(parentUsage.scope.toSearchScope(), Access.Write) body@ {
val refExpression = it.element as? KtExpression ?: return@body val refExpression = it.element as? KtExpression ?: return@body
val rhs = KtPsiUtil.safeDeparenthesize(refExpression).getAssignmentByLHS()?.right ?: return@body val rhs = KtPsiUtil.safeDeparenthesize(refExpression).getAssignmentByLHS()?.right ?: return@body
rhs.passToProcessor() rhs.passToProcessorAsValue()
} }
} }
@@ -152,7 +158,7 @@ class InflowSlicer(
is ExpressionValueArgument -> resolvedArgument.valueArgument?.getArgumentExpression() is ExpressionValueArgument -> resolvedArgument.valueArgument?.getArgumentExpression()
else -> null else -> null
} ?: return@body } ?: return@body
flownExpression.passToProcessor() flownExpression.passToProcessorAsValue()
} }
} }
@@ -160,21 +166,46 @@ class InflowSlicer(
val pseudocode = pseudocodeCache[bodyExpression ?: return] ?: return val pseudocode = pseudocodeCache[bodyExpression ?: return] ?: return
pseudocode.traverse(TraversalOrder.FORWARD) { instr -> pseudocode.traverse(TraversalOrder.FORWARD) { instr ->
if (instr is ReturnValueInstruction && instr.subroutine == this) { if (instr is ReturnValueInstruction && instr.subroutine == this) {
(instr.returnExpressionIfAny?.returnedExpression ?: instr.element as? KtExpression)?.passToProcessor() (instr.returnExpressionIfAny?.returnedExpression ?: instr.element as? KtExpression)?.passToProcessorAsValue()
} }
} }
} }
private fun Instruction.passInputsToProcessor() { private fun Instruction.passInputsToProcessor() {
inputValues.forEach { it.element?.passToProcessor() } inputValues.forEach {
if (it.createdAt != null) {
it.element?.passToProcessorAsValue()
}
}
} }
private fun KtExpression.processExpression() { private fun KtExpression.processExpression() {
val lambda = when (this) {
is KtLambdaExpression -> functionLiteral
is KtNamedFunction -> if (name == null) this else null
else -> null
}
if (lambda != null) {
if (parentUsage.lambdaLevel > 0) {
lambda.passToProcessor(parentUsage.lambdaLevel - 1)
}
return
}
val pseudocode = pseudocodeCache[this] ?: return val pseudocode = pseudocodeCache[this] ?: return
val expressionValue = pseudocode.getElementValue(this) ?: return val expressionValue = pseudocode.getElementValue(this) ?: return
val createdAt = expressionValue.createdAt val createdAt = expressionValue.createdAt
when (createdAt) { when (createdAt) {
is ReadValueInstruction -> (createdAt.target.accessedDescriptor?.source?.getPsi() as? KtDeclaration)?.passToProcessor() is ReadValueInstruction -> {
if (createdAt.target == AccessTarget.BlackBox) {
val originalElement = expressionValue.element as? KtExpression ?: return
if (originalElement != this) {
originalElement.processExpression()
}
return
}
(createdAt.target.accessedDescriptor?.source?.getPsi() as? KtDeclaration)?.passToProcessor()
}
is MergeInstruction -> createdAt.passInputsToProcessor() is MergeInstruction -> createdAt.passInputsToProcessor()
@@ -183,11 +214,22 @@ class InflowSlicer(
else -> return else -> return
} }
is CallInstruction -> (createdAt.resolvedCall.resultingDescriptor.source.getPsi() as? KtDeclarationWithBody)?.passToProcessor() is CallInstruction -> {
val resolvedCall = createdAt.resolvedCall
val resultingDescriptor = resolvedCall.resultingDescriptor
if (resultingDescriptor is FunctionInvokeDescriptor) {
(resolvedCall.dispatchReceiver as? ExpressionReceiver)?.expression?.passToProcessorAsValue(parentUsage.lambdaLevel + 1)
}
else {
resultingDescriptor.source.getPsi()?.passToProcessor()
}
}
} }
} }
override fun processChildren() { override fun processChildren() {
if (parentUsage.forcedExpressionMode) return element.processExpression()
when (element) { when (element) {
is KtProperty -> element.processProperty() is KtProperty -> element.processProperty()
is KtParameter -> element.processParameter() is KtParameter -> element.processParameter()
@@ -235,10 +277,19 @@ class OutflowSlicer(
return null return null
} }
private fun KtFunction.processFunctionCalls() { private fun KtFunction.processFunction() {
processCalls(parentUsage.scope.toSearchScope()) { if (this is KtConstructor<*> || this is KtNamedFunction && name != null) {
it.element?.getCallElementForExactCallee()?.passToProcessor() processCalls(parentUsage.scope.toSearchScope()) {
it.element?.getCallElementForExactCallee()?.passToProcessor()
}
return
} }
val funExpression = when (this) {
is KtFunctionLiteral -> parent as? KtLambdaExpression
is KtNamedFunction -> this
else -> null
} ?: return
(funExpression as PsiElement).passToProcessor(parentUsage.lambdaLevel + 1, true)
} }
private fun processDereferenceIsNeeded( private fun processDereferenceIsNeeded(
@@ -256,7 +307,7 @@ class OutflowSlicer(
} ?: return } ?: return
if (receiver != null && resolvedCall.dispatchReceiver == receiver) { if (receiver != null && resolvedCall.dispatchReceiver == receiver) {
processor.process(KotlinSliceDereferenceUsage(expression, parentUsage)) processor.process(KotlinSliceDereferenceUsage(expression, parentUsage, parentUsage.lambdaLevel))
} }
} }
@@ -279,8 +330,15 @@ class OutflowSlicer(
processPseudocodeUsages { pseudoValue, instr -> processPseudocodeUsages { pseudoValue, instr ->
when (instr) { when (instr) {
is WriteValueInstruction -> (instr.target.accessedDescriptor?.source?.getPsi() as? KtDeclaration)?.passToProcessor() is WriteValueInstruction -> (instr.target.accessedDescriptor?.source?.getPsi() as? KtDeclaration)?.passToProcessor()
is CallInstruction -> instr.arguments[pseudoValue]?.source?.getPsi()?.passToProcessor() is CallInstruction -> {
is ReturnValueInstruction -> (instr.owner.correspondingElement as? KtFunction)?.passToProcessor() if (parentUsage.lambdaLevel > 0 && instr.receiverValues[pseudoValue] != null) {
instr.element.passToProcessor(parentUsage.lambdaLevel - 1)
}
else {
instr.arguments[pseudoValue]?.source?.getPsi()?.passToProcessor()
}
}
is ReturnValueInstruction -> instr.subroutine.passToProcessor()
is MagicInstruction -> when (instr.kind) { is MagicInstruction -> when (instr.kind) {
MagicKind.NOT_NULL_ASSERTION, MagicKind.CAST -> instr.outputValue.element?.passToProcessor() MagicKind.NOT_NULL_ASSERTION, MagicKind.CAST -> instr.outputValue.element?.passToProcessor()
else -> {} else -> {}
@@ -290,9 +348,11 @@ class OutflowSlicer(
} }
override fun processChildren() { override fun processChildren() {
if (parentUsage.forcedExpressionMode) return element.processExpression()
when (element) { when (element) {
is KtProperty, is KtParameter -> (element as KtDeclaration).processVariable() is KtProperty, is KtParameter -> (element as KtDeclaration).processVariable()
is KtFunction -> element.processFunctionCalls() is KtFunction -> element.processFunction()
else -> element.processExpression() else -> element.processExpression()
} }
} }
@@ -0,0 +1,9 @@
// FLOW: IN
fun foo(f: (Int) -> Int): Int {
return f(1)
}
fun test() {
val <caret>x = foo(fun(n: Int) = n)
}
@@ -0,0 +1,10 @@
8 val <bold>x = foo(fun(n: Int) = n)</bold>
8 val x = <bold>foo(fun(n: Int) = n)</bold>
3 fun <bold>foo(f: (Int) -> Int): Int {</bold>
4 return <bold>f(1)</bold>
4 [LAMBDA] return <bold>f</bold>(1)
3 [LAMBDA] fun foo(<bold>f: (Int) -> Int</bold>): Int {
8 [LAMBDA] val x = foo(<bold>fun(n: Int) = n</bold>)
8 val x = foo(<bold>fun(n: Int) = n</bold>)
8 val x = foo(fun(n: Int) = <bold>n</bold>)
8 val x = foo(fun(<bold>n: Int</bold>) = n)
@@ -0,0 +1,9 @@
// FLOW: IN
fun foo(f: (Int) -> Int): Int {
return f(1)
}
fun test() {
val <caret>x = foo(fun(n: Int): Int { return n })
}
@@ -0,0 +1,10 @@
8 val <bold>x = foo(fun(n: Int): Int { return n })</bold>
8 val x = <bold>foo(fun(n: Int): Int { return n })</bold>
3 fun <bold>foo(f: (Int) -> Int): Int {</bold>
4 return <bold>f(1)</bold>
4 [LAMBDA] return <bold>f</bold>(1)
3 [LAMBDA] fun foo(<bold>f: (Int) -> Int</bold>): Int {
8 [LAMBDA] val x = foo(<bold>fun(n: Int): Int { return n }</bold>)
8 val x = foo(<bold>fun(n: Int): Int { return n }</bold>)
8 val x = foo(fun(n: Int): Int { return <bold>n</bold> })
8 val x = foo(fun(<bold>n: Int</bold>): Int { return n })
+9
View File
@@ -0,0 +1,9 @@
// FLOW: IN
fun foo(a: Int, b: Int, f: (Int) -> (Int) -> Int): Int {
return f(a)(b)
}
fun test() {
val <caret>x = foo(1, 2) { { it } }
}
@@ -0,0 +1,12 @@
8 val <bold>x = foo(1, 2) { { it } }</bold>
8 val x = <bold>foo(1, 2) { { it } }</bold>
3 fun <bold>foo(a: Int, b: Int, f: (Int) -> (Int) -> Int): Int {</bold>
4 return <bold>f(a)(b)</bold>
4 [LAMBDA] return <bold>f(a)</bold>(b)
4 [LAMBDA] [LAMBDA] return <bold>f</bold>(a)(b)
3 [LAMBDA] [LAMBDA] fun foo(a: Int, b: Int, <bold>f: (Int) -> (Int) -> Int</bold>): Int {
8 [LAMBDA] [LAMBDA] val x = foo(1, 2) <bold>{ { it } }</bold>
8 [LAMBDA] val x = foo(1, 2) <bold>{ { it } }</bold>
8 [LAMBDA] val x = foo(1, 2) { <bold>{ it }</bold> }
8 val x = foo(1, 2) { <bold>{ it }</bold> }
8 val x = foo(1, 2) { { <bold>it</bold> } }
+9
View File
@@ -0,0 +1,9 @@
// FLOW: IN
fun foo(f: (Int) -> Int): Int {
return f(1)
}
fun test() {
val <caret>x = foo { it }
}
+9
View File
@@ -0,0 +1,9 @@
8 val <bold>x = foo { it }</bold>
8 val x = <bold>foo { it }</bold>
3 fun <bold>foo(f: (Int) -> Int): Int {</bold>
4 return <bold>f(1)</bold>
4 [LAMBDA] return <bold>f</bold>(1)
3 [LAMBDA] fun foo(<bold>f: (Int) -> Int</bold>): Int {
8 [LAMBDA] val x = foo <bold>{ it }</bold>
8 val x = foo <bold>{ it }</bold>
8 val x = foo { <bold>it</bold> }
@@ -0,0 +1,10 @@
// FLOW: IN
fun foo(f: (Int) -> Int): Int {
val x = f
return x(1)
}
fun test() {
val <caret>y = foo { it }
}
@@ -0,0 +1,11 @@
9 val <bold>y = foo { it }</bold>
9 val y = <bold>foo { it }</bold>
3 fun <bold>foo(f: (Int) -> Int): Int {</bold>
5 return <bold>x(1)</bold>
5 [LAMBDA] return <bold>x</bold>(1)
4 [LAMBDA] val <bold>x = f</bold>
4 [LAMBDA] val x = <bold>f</bold>
3 [LAMBDA] fun foo(<bold>f: (Int) -> Int</bold>): Int {
9 [LAMBDA] val y = foo <bold>{ it }</bold>
9 val y = foo <bold>{ it }</bold>
9 val y = foo { <bold>it</bold> }
@@ -0,0 +1,5 @@
// FLOW: IN
fun test() {
val <caret>x = { 1 }()
}
@@ -0,0 +1,5 @@
4 val <bold>x = { 1 }()</bold>
4 val x = <bold>{ 1 }()</bold>
4 [LAMBDA] val x = <bold>{ 1 }</bold>()
4 val x = <bold>{ 1 }</bold>()
4 val x = { <bold>1</bold> }()
@@ -0,0 +1,6 @@
// FLOW: IN
fun test() {
val f = { 1 }
val <caret>x = f()
}
@@ -0,0 +1,7 @@
5 val <bold>x = f()</bold>
5 val x = <bold>f()</bold>
5 [LAMBDA] val x = <bold>f</bold>()
4 [LAMBDA] val <bold>f = { 1 }</bold>
4 [LAMBDA] val f = <bold>{ 1 }</bold>
4 val f = <bold>{ 1 }</bold>
4 val f = { <bold>1</bold> }
+5
View File
@@ -0,0 +1,5 @@
// FLOW: IN
inline fun foo(a: Int, f: (Int) -> Int) = f(a)
fun <caret>bar(a: Int): Int = foo(a) { if (it > 0) it else return 0 }
+11
View File
@@ -0,0 +1,11 @@
5 fun <bold>bar(a: Int): Int = foo(a) { if (it > 0) it else return 0 }</bold>
5 fun bar(a: Int): Int = foo(a) { if (it > 0) it else return <bold>0</bold> }
5 fun bar(a: Int): Int = <bold>foo(a) { if (it > 0) it else return 0 }</bold>
3 inline fun <bold>foo(a: Int, f: (Int) -> Int) = f(a)</bold>
3 inline fun foo(a: Int, f: (Int) -> Int) = <bold>f(a)</bold>
3 [LAMBDA] inline fun foo(a: Int, f: (Int) -> Int) = <bold>f</bold>(a)
3 [LAMBDA] inline fun foo(a: Int, <bold>f: (Int) -> Int</bold>) = f(a)
5 [LAMBDA] fun bar(a: Int): Int = foo(a) <bold>{ if (it > 0) it else return 0 }</bold>
5 fun bar(a: Int): Int = foo(a) <bold>{ if (it > 0) it else return 0 }</bold>
5 fun bar(a: Int): Int = foo(a) { <bold>if (it > 0) it else return 0</bold> }
5 fun bar(a: Int): Int = foo(a) { if (it > 0) <bold>it</bold> else return 0 }
@@ -0,0 +1,9 @@
// FLOW: OUT
fun foo(f: (Int) -> Int): Int {
return f(1)
}
fun test() {
val x = foo(fun(n: Int) = <caret>n)
}
@@ -0,0 +1,8 @@
8 val x = foo(fun(n: Int) = <bold>n</bold>)
8 val x = foo(<bold>fun(n: Int) = n</bold>)
8 [LAMBDA] val x = foo(<bold>fun(n: Int) = n</bold>)
3 [LAMBDA] fun foo(<bold>f: (Int) -> Int</bold>): Int {
4 return <bold>f(1)</bold>
3 fun <bold>foo(f: (Int) -> Int): Int {</bold>
8 val x = <bold>foo(fun(n: Int) = n)</bold>
8 val <bold>x = foo(fun(n: Int) = n)</bold>
@@ -0,0 +1,9 @@
// FLOW: OUT
fun foo(f: (Int) -> Int): Int {
return f(1)
}
fun test() {
val x = foo(fun(n: Int): Int { return <caret>n })
}
@@ -0,0 +1,8 @@
8 val x = foo(fun(n: Int): Int { return <bold>n</bold> })
8 val x = foo(<bold>fun(n: Int): Int { return n }</bold>)
8 [LAMBDA] val x = foo(<bold>fun(n: Int): Int { return n }</bold>)
3 [LAMBDA] fun foo(<bold>f: (Int) -> Int</bold>): Int {
4 return <bold>f(1)</bold>
3 fun <bold>foo(f: (Int) -> Int): Int {</bold>
8 val x = <bold>foo(fun(n: Int): Int { return n })</bold>
8 val <bold>x = foo(fun(n: Int): Int { return n })</bold>
+9
View File
@@ -0,0 +1,9 @@
// FLOW: OUT
fun foo(a: Int, b: Int, f: (Int) -> (Int) -> Int): Int {
return f(a)(b)
}
fun test() {
val x = foo(1, 2) { { <caret>it } }
}
@@ -0,0 +1,11 @@
8 val x = foo(1, 2) { { <bold>it</bold> } }
8 val x = foo(1, 2) { <bold>{ it }</bold> }
8 [LAMBDA] val x = foo(1, 2) { <bold>{ it }</bold> }
8 [LAMBDA] val x = foo(1, 2) <bold>{ { it } }</bold>
8 [LAMBDA] [LAMBDA] val x = foo(1, 2) <bold>{ { it } }</bold>
3 [LAMBDA] [LAMBDA] fun foo(a: Int, b: Int, <bold>f: (Int) -> (Int) -> Int</bold>): Int {
4 [LAMBDA] return <bold>f(a)</bold>(b)
4 return <bold>f(a)(b)</bold>
3 fun <bold>foo(a: Int, b: Int, f: (Int) -> (Int) -> Int): Int {</bold>
8 val x = <bold>foo(1, 2) { { it } }</bold>
8 val <bold>x = foo(1, 2) { { it } }</bold>
@@ -0,0 +1,12 @@
// FLOW: OUT
fun foo(f: (Int) -> Int): Int {
return f(1)
}
fun test() {
val x = foo {
if (it < 0) return@foo <caret>0
it
}
}
@@ -0,0 +1,8 @@
9 if (it < 0) return@foo <bold>0</bold>
8 val x = foo <bold>{</bold>
8 [LAMBDA] val x = foo <bold>{</bold>
3 [LAMBDA] fun foo(<bold>f: (Int) -> Int</bold>): Int {
4 return <bold>f(1)</bold>
3 fun <bold>foo(f: (Int) -> Int): Int {</bold>
8 val x = <bold>foo {</bold>
8 val <bold>x = foo {</bold>
+9
View File
@@ -0,0 +1,9 @@
// FLOW: OUT
fun foo(f: (Int) -> Int): Int {
return f(1)
}
fun test() {
val x = foo { <caret>it }
}
+8
View File
@@ -0,0 +1,8 @@
8 val x = foo { <bold>it</bold> }
8 val x = foo <bold>{ it }</bold>
8 [LAMBDA] val x = foo <bold>{ it }</bold>
3 [LAMBDA] fun foo(<bold>f: (Int) -> Int</bold>): Int {
4 return <bold>f(1)</bold>
3 fun <bold>foo(f: (Int) -> Int): Int {</bold>
8 val x = <bold>foo { it }</bold>
8 val <bold>x = foo { it }</bold>
@@ -0,0 +1,10 @@
// FLOW: OUT
fun foo(f: (Int) -> Int): Int {
val x = f
return x(1)
}
fun test() {
val y = foo { <caret>it }
}
@@ -0,0 +1,9 @@
9 val y = foo { <bold>it</bold> }
9 val y = foo <bold>{ it }</bold>
9 [LAMBDA] val y = foo <bold>{ it }</bold>
3 [LAMBDA] fun foo(<bold>f: (Int) -> Int</bold>): Int {
4 [LAMBDA] val <bold>x = f</bold>
5 return <bold>x(1)</bold>
3 fun <bold>foo(f: (Int) -> Int): Int {</bold>
9 val y = <bold>foo { it }</bold>
9 val <bold>y = foo { it }</bold>
@@ -0,0 +1,5 @@
// FLOW: OUT
fun test() {
val x = { <caret>1 }()
}
@@ -0,0 +1,5 @@
4 val x = { <bold>1</bold> }()
4 val x = <bold>{ 1 }</bold>()
4 [LAMBDA] val x = <bold>{ 1 }</bold>()
4 val x = <bold>{ 1 }()</bold>
4 val <bold>x = { 1 }()</bold>
@@ -0,0 +1,6 @@
// FLOW: OUT
fun test() {
val f = { <caret>1 }
val x = f()
}
@@ -0,0 +1,6 @@
4 val f = { <bold>1</bold> }
4 val f = <bold>{ 1 }</bold>
4 [LAMBDA] val f = <bold>{ 1 }</bold>
4 [LAMBDA] val <bold>f = { 1 }</bold>
5 val x = <bold>f()</bold>
5 val <bold>x = f()</bold>
+9
View File
@@ -0,0 +1,9 @@
// FLOW: OUT
inline fun foo(a: Int, f: (Int) -> Int) = f(a)
fun bar(a: Int): Int = foo(a) { if (it > 0) it else return <caret>0 }
fun test() {
val x = bar(1)
}
@@ -0,0 +1,4 @@
5 fun bar(a: Int): Int = foo(a) { if (it > 0) it else return <bold>0</bold> }
5 fun <bold>bar(a: Int): Int = foo(a) { if (it > 0) it else return 0 }</bold>
8 val x = <bold>bar(1)</bold>
8 val <bold>x = bar(1)</bold>
@@ -18,11 +18,12 @@ package org.jetbrains.kotlin.idea.slicer
import com.intellij.analysis.AnalysisScope import com.intellij.analysis.AnalysisScope
import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiElement
import com.intellij.slicer.SliceAnalysisParams import com.intellij.slicer.SliceAnalysisParams
import com.intellij.slicer.SliceUsage import com.intellij.slicer.SliceUsage
import com.intellij.usages.TextChunk import com.intellij.usages.TextChunk
import com.intellij.util.CommonProcessors import com.intellij.util.CommonProcessors
import com.intellij.util.Processor
import gnu.trove.THashSet
import gnu.trove.TObjectHashingStrategy import gnu.trove.TObjectHashingStrategy
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runReadAction
@@ -33,9 +34,14 @@ import java.awt.Font
import java.io.File import java.io.File
abstract class AbstractSlicerTest : KotlinLightCodeInsightFixtureTestCase() { abstract class AbstractSlicerTest : KotlinLightCodeInsightFixtureTestCase() {
object SliceUsageHashingStrategy : TObjectHashingStrategy<SliceUsage> { object SliceUsageHashingStrategy : TObjectHashingStrategy<KotlinSliceUsage> {
override fun computeHashCode(`object`: SliceUsage) = `object`.usageInfo.hashCode() override fun computeHashCode(`object`: KotlinSliceUsage): Int {
override fun equals(o1: SliceUsage, o2: SliceUsage) = o1.usageInfo == o2.usageInfo return `object`.usageInfo.hashCode() * 31 + `object`.forcedExpressionMode.hashCode()
}
override fun equals(o1: KotlinSliceUsage, o2: KotlinSliceUsage): Boolean {
return o1.usageInfo == o2.usageInfo && o1.forcedExpressionMode == o2.forcedExpressionMode
}
} }
// Based on SliceUsage.processChildren // Based on SliceUsage.processChildren
@@ -44,13 +50,14 @@ abstract class AbstractSlicerTest : KotlinLightCodeInsightFixtureTestCase() {
val element = runReadAction { element } val element = runReadAction { element }
@Suppress("UNCHECKED_CAST")
val uniqueProcessor = CommonProcessors.UniqueProcessor( val uniqueProcessor = CommonProcessors.UniqueProcessor(
{ {
processor(it as KotlinSliceUsage) processor(it as KotlinSliceUsage)
true true
}, },
SliceUsageHashingStrategy SliceUsageHashingStrategy
) ) as Processor<SliceUsage>
runReadAction { runReadAction {
if (params.dataFlowToThis) { if (params.dataFlowToThis) {
@@ -63,7 +70,7 @@ abstract class AbstractSlicerTest : KotlinLightCodeInsightFixtureTestCase() {
} }
private fun buildTreeRepresentation(rootUsage: KotlinSliceUsage): String { private fun buildTreeRepresentation(rootUsage: KotlinSliceUsage): String {
val visitedElements = HashSet<PsiElement>() val visitedUsages = THashSet<KotlinSliceUsage>(SliceUsageHashingStrategy)
fun TextChunk.render(): String { fun TextChunk.render(): String {
var text = text var text = text
@@ -74,7 +81,7 @@ abstract class AbstractSlicerTest : KotlinLightCodeInsightFixtureTestCase() {
} }
fun process(usage: KotlinSliceUsage, indent: Int): String { fun process(usage: KotlinSliceUsage, indent: Int): String {
val isDuplicated = !visitedElements.add(usage.element) val isDuplicated = !visitedUsages.add(usage)
return buildString { return buildString {
val chunks = usage.text val chunks = usage.text
@@ -83,6 +90,7 @@ abstract class AbstractSlicerTest : KotlinLightCodeInsightFixtureTestCase() {
if (usage is KotlinSliceDereferenceUsage) { if (usage is KotlinSliceDereferenceUsage) {
append("DEREFERENCE: ") append("DEREFERENCE: ")
} }
append("[LAMBDA] ".repeat(usage.lambdaLevel))
chunks.slice(1..chunks.size - 1).joinTo( chunks.slice(1..chunks.size - 1).joinTo(
this, this,
separator = "", separator = "",
@@ -36,12 +36,30 @@ public class SlicerTestGenerated extends AbstractSlicerTest {
KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/slicer"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY); KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/slicer"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY);
} }
@TestMetadata("inflow/anonymousFunBodyExpression.kt")
public void testInflow_AnonymousFunBodyExpression() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/anonymousFunBodyExpression.kt");
doTest(fileName);
}
@TestMetadata("inflow/anonymousFunReturnExpression.kt")
public void testInflow_AnonymousFunReturnExpression() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/anonymousFunReturnExpression.kt");
doTest(fileName);
}
@TestMetadata("inflow/cast.kt") @TestMetadata("inflow/cast.kt")
public void testInflow_Cast() throws Exception { public void testInflow_Cast() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/cast.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/cast.kt");
doTest(fileName); doTest(fileName);
} }
@TestMetadata("inflow/doubleLambdaResult.kt")
public void testInflow_DoubleLambdaResult() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/doubleLambdaResult.kt");
doTest(fileName);
}
@TestMetadata("inflow/funParamerer.kt") @TestMetadata("inflow/funParamerer.kt")
public void testInflow_FunParamerer() throws Exception { public void testInflow_FunParamerer() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/funParamerer.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/funParamerer.kt");
@@ -72,6 +90,30 @@ public class SlicerTestGenerated extends AbstractSlicerTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("inflow/lambdaResult.kt")
public void testInflow_LambdaResult() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/lambdaResult.kt");
doTest(fileName);
}
@TestMetadata("inflow/lambdaResultWithAssignments.kt")
public void testInflow_LambdaResultWithAssignments() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/lambdaResultWithAssignments.kt");
doTest(fileName);
}
@TestMetadata("inflow/lambdaResultWithDirectCall.kt")
public void testInflow_LambdaResultWithDirectCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/lambdaResultWithDirectCall.kt");
doTest(fileName);
}
@TestMetadata("inflow/lambdaResultWithDirectCallViaAssignment.kt")
public void testInflow_LambdaResultWithDirectCallViaAssignment() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/lambdaResultWithDirectCallViaAssignment.kt");
doTest(fileName);
}
@TestMetadata("inflow/localVal.kt") @TestMetadata("inflow/localVal.kt")
public void testInflow_LocalVal() throws Exception { public void testInflow_LocalVal() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/localVal.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/localVal.kt");
@@ -108,6 +150,12 @@ public class SlicerTestGenerated extends AbstractSlicerTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("inflow/nonLocalReturn.kt")
public void testInflow_NonLocalReturn() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/nonLocalReturn.kt");
doTest(fileName);
}
@TestMetadata("inflow/notNullAssertion.kt") @TestMetadata("inflow/notNullAssertion.kt")
public void testInflow_NotNullAssertion() throws Exception { public void testInflow_NotNullAssertion() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/notNullAssertion.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/notNullAssertion.kt");
@@ -162,6 +210,18 @@ public class SlicerTestGenerated extends AbstractSlicerTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("outflow/anonymousFunBodyExpression.kt")
public void testOutflow_AnonymousFunBodyExpression() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/anonymousFunBodyExpression.kt");
doTest(fileName);
}
@TestMetadata("outflow/anonymousFunReturnExpression.kt")
public void testOutflow_AnonymousFunReturnExpression() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/anonymousFunReturnExpression.kt");
doTest(fileName);
}
@TestMetadata("outflow/callArgument.kt") @TestMetadata("outflow/callArgument.kt")
public void testOutflow_CallArgument() throws Exception { public void testOutflow_CallArgument() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/callArgument.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/callArgument.kt");
@@ -174,6 +234,18 @@ public class SlicerTestGenerated extends AbstractSlicerTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("outflow/doubleLambdaResult.kt")
public void testOutflow_DoubleLambdaResult() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/doubleLambdaResult.kt");
doTest(fileName);
}
@TestMetadata("outflow/explicitLambdaReturnExpression.kt")
public void testOutflow_ExplicitLambdaReturnExpression() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/explicitLambdaReturnExpression.kt");
doTest(fileName);
}
@TestMetadata("outflow/extensionIndexingDereferences.kt") @TestMetadata("outflow/extensionIndexingDereferences.kt")
public void testOutflow_ExtensionIndexingDereferences() throws Exception { public void testOutflow_ExtensionIndexingDereferences() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/extensionIndexingDereferences.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/extensionIndexingDereferences.kt");
@@ -222,6 +294,30 @@ public class SlicerTestGenerated extends AbstractSlicerTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("outflow/lambdaResult.kt")
public void testOutflow_LambdaResult() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/lambdaResult.kt");
doTest(fileName);
}
@TestMetadata("outflow/lambdaResultWithAssignments.kt")
public void testOutflow_LambdaResultWithAssignments() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/lambdaResultWithAssignments.kt");
doTest(fileName);
}
@TestMetadata("outflow/lambdaResultWithDirectCall.kt")
public void testOutflow_LambdaResultWithDirectCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/lambdaResultWithDirectCall.kt");
doTest(fileName);
}
@TestMetadata("outflow/lambdaResultWithDirectCallViaAssignment.kt")
public void testOutflow_LambdaResultWithDirectCallViaAssignment() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/lambdaResultWithDirectCallViaAssignment.kt");
doTest(fileName);
}
@TestMetadata("outflow/localVariableUsages.kt") @TestMetadata("outflow/localVariableUsages.kt")
public void testOutflow_LocalVariableUsages() throws Exception { public void testOutflow_LocalVariableUsages() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/localVariableUsages.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/localVariableUsages.kt");
@@ -234,6 +330,12 @@ public class SlicerTestGenerated extends AbstractSlicerTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("outflow/nonLocalReturn.kt")
public void testOutflow_NonLocalReturn() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/nonLocalReturn.kt");
doTest(fileName);
}
@TestMetadata("outflow/notNullAssertion.kt") @TestMetadata("outflow/notNullAssertion.kt")
public void testOutflow_NotNullAssertion() throws Exception { public void testOutflow_NotNullAssertion() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/notNullAssertion.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/notNullAssertion.kt");