Special handling of inline function to track values from individual calls

This commit is contained in:
Valentin Kipyatkov
2020-04-12 10:19:52 +03:00
parent 269420a0e0
commit a09a9a65ff
29 changed files with 420 additions and 130 deletions
@@ -32,10 +32,10 @@ data class ArgumentSliceProducer private constructor(
parameterDescriptor.containingDeclaration.isExtension
)
override fun produce(usage: UsageInfo, behaviour: KotlinSliceUsage.SpecialBehaviour?, parent: SliceUsage): Collection<SliceUsage>? {
override fun produce(usage: UsageInfo, mode: KotlinSliceAnalysisMode, parent: SliceUsage): Collection<SliceUsage>? {
val element = usage.element ?: return emptyList()
val argumentExpression = extractArgumentExpression(element) ?: return emptyList()
return listOf(KotlinSliceUsage(argumentExpression, parent, behaviour, forcedExpressionMode = true))
return listOf(KotlinSliceUsage(argumentExpression, parent, mode, forcedExpressionMode = true))
}
private fun extractArgumentExpression(refElement: PsiElement): PsiElement? {
@@ -15,8 +15,8 @@ import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
object CallSliceProducer : SliceProducer {
override fun produce(usage: UsageInfo, behaviour: KotlinSliceUsage.SpecialBehaviour?, parent: SliceUsage): Collection<SliceUsage>? {
if ((parent as? KotlinSliceUsage)?.behaviour is LambdaCallsBehaviour) {
override fun produce(usage: UsageInfo, mode: KotlinSliceAnalysisMode, parent: SliceUsage): Collection<SliceUsage>? {
if ((parent as? KotlinSliceUsage)?.mode?.currentBehaviour is LambdaCallsBehaviour) {
// UsageInfo produced by LambdaCallsBehaviour has full call-element and does not require any processing
return null
}
@@ -24,15 +24,22 @@ object CallSliceProducer : SliceProducer {
when (val refElement = usage.element) {
null -> {
val element = (usage.reference as? LightMemberReference)?.element ?: return emptyList()
return listOf(KotlinSliceUsage(element, parent, behaviour, false))
return listOf(KotlinSliceUsage(element, parent, mode, false))
}
is KtExpression -> {
return mutableListOf<SliceUsage>().apply {
refElement.getCallElementForExactCallee()
?.let { this += KotlinSliceUsage(it, parent, behaviour, false) }
?.let { this += KotlinSliceUsage(it, parent, mode, false) }
refElement.getCallableReferenceForExactCallee()
?.let { this += KotlinSliceUsage(it, parent, LambdaCallsBehaviour(SliceProducer.Trivial, behaviour), false) }
?.let {
this += KotlinSliceUsage(
it,
parent,
mode.withBehaviour(LambdaCallsBehaviour(SliceProducer.Trivial)),
forcedExpressionMode = true
)
}
}
}
@@ -31,6 +31,7 @@ import org.jetbrains.kotlin.idea.references.readWriteAccessWithFullExpression
import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest
import org.jetbrains.kotlin.idea.search.declarationsSearch.searchOverriders
import org.jetbrains.kotlin.idea.util.actualsForExpected
import org.jetbrains.kotlin.idea.util.hasInlineModifier
import org.jetbrains.kotlin.idea.util.isExpectDeclaration
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
@@ -141,8 +142,8 @@ class InflowSlicer(
else -> null
}
if (lambda != null) {
if (behaviour is LambdaResultInflowBehaviour) {
lambda.passToProcessor(behaviour.originalBehaviour)
if (mode.currentBehaviour is LambdaResultInflowBehaviour) {
lambda.passToProcessor(mode.dropBehaviour())
}
return
}
@@ -176,7 +177,7 @@ class InflowSlicer(
val callable = accessedDescriptor.containingDeclaration as? CallableDescriptor ?: return
when (val declaration = callable.originalSource.getPsi()) {
is KtFunctionLiteral -> {
declaration.passToProcessorAsValue(LambdaCallsBehaviour(ReceiverSliceProducer, behaviour))
declaration.passToProcessorAsValue(mode.withBehaviour(LambdaCallsBehaviour(ReceiverSliceProducer)))
}
is KtCallableDeclaration -> {
@@ -194,12 +195,12 @@ class InflowSlicer(
processCalls(functionLiteral, false, ArgumentSliceProducer(parameterDescriptor))
}
} else {
accessedDeclaration.passDeclarationToProcessorWithOverriders()
accessedDeclaration.passDeclarationToProcessorWithOverriders(createdAt.element)
}
}
else -> {
accessedDeclaration?.passDeclarationToProcessorWithOverriders()
accessedDeclaration?.passDeclarationToProcessorWithOverriders(createdAt.element)
}
}
}
@@ -215,8 +216,8 @@ class InflowSlicer(
val referencedDescriptor = bindingContext[BindingContext.REFERENCE_TARGET, callableRefExpr.callableReference] ?: return
val referencedDeclaration = (referencedDescriptor as? DeclarationDescriptorWithSource)?.originalSource?.getPsi()
?: return
if (behaviour is LambdaResultInflowBehaviour) {
referencedDeclaration.passToProcessor(behaviour.originalBehaviour)
if (mode.currentBehaviour is LambdaResultInflowBehaviour) {
referencedDeclaration.passToProcessor(mode.dropBehaviour())
}
}
@@ -228,9 +229,9 @@ class InflowSlicer(
val resultingDescriptor = resolvedCall.resultingDescriptor
if (resultingDescriptor is FunctionInvokeDescriptor) {
(resolvedCall.dispatchReceiver as? ExpressionReceiver)?.expression
?.passToProcessorAsValue(LambdaResultInflowBehaviour(behaviour))
?.passToProcessorAsValue(mode.withBehaviour(LambdaResultInflowBehaviour))
} else {
resultingDescriptor.originalSource.getPsi()?.passDeclarationToProcessorWithOverriders()
resultingDescriptor.originalSource.getPsi()?.passDeclarationToProcessorWithOverriders(createdAt.element)
}
}
}
@@ -305,19 +306,37 @@ class InflowSlicer(
}
}
private fun PsiElement.passDeclarationToProcessorWithOverriders() {
passToProcessor()
private fun PsiElement.passDeclarationToProcessorWithOverriders(callElement: KtElement) {
val newMode = if (this is KtNamedFunction && hasInlineModifier())
mode.withInlineFunctionCall(callElement, this)
else
mode
passToProcessor(newMode)
HierarchySearchRequest(this, analysisScope)
.searchOverriders()
.forEach { it.namedUnwrappedElement?.passToProcessor() }
.forEach { it.namedUnwrappedElement?.passToProcessor(newMode) }
if (this is KtCallableDeclaration && isExpectDeclaration()) {
resolveToDescriptorIfAny(BodyResolveMode.FULL)
?.actualsForExpected()
?.forEach {
(it as? DeclarationDescriptorWithSource)?.originalSource?.getPsi()?.passToProcessor()
(it as? DeclarationDescriptorWithSource)?.originalSource?.getPsi()?.passToProcessor(newMode)
}
}
}
override fun processCalls(callable: KtCallableDeclaration, includeOverriders: Boolean, sliceProducer: SliceProducer) {
if (callable is KtNamedFunction) {
val (newMode, callElement) = mode.popInlineFunctionCall(callable)
if (newMode != null && callElement != null) {
val sliceUsage = KotlinSliceUsage(callElement, parentUsage, newMode, false)
sliceProducer.produceAndProcess(sliceUsage, newMode, parentUsage, processor)
return
}
}
super.processCalls(callable, includeOverriders, sliceProducer)
}
}
@@ -0,0 +1,67 @@
/*
* 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.slicer
import org.jetbrains.kotlin.idea.findUsages.handlers.SliceUsageProcessor
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
data class KotlinSliceAnalysisMode(val behaviourStack: List<Behaviour>, val inlineCallStack: List<InlineFunctionCall>) {
fun withBehaviour(behaviour: Behaviour) = copy(behaviourStack = behaviourStack + behaviour)
fun withInlineFunctionCall(
callElement: KtElement,
function: KtNamedFunction
) = copy(inlineCallStack = inlineCallStack + InlineFunctionCall(callElement, function))
fun dropBehaviour(): KotlinSliceAnalysisMode {
check(behaviourStack.isNotEmpty())
return copy(behaviourStack = behaviourStack.dropLast(1))
}
fun popInlineFunctionCall(function: KtNamedFunction): Pair<KotlinSliceAnalysisMode?, KtElement?> {
val last = inlineCallStack.lastOrNull()
if (last?.function != function) return null to null
val newMode = copy(inlineCallStack = inlineCallStack.dropLast(1))
return newMode to last.callElement
}
val currentBehaviour: Behaviour?
get() = behaviourStack.lastOrNull()
interface Behaviour {
fun processUsages(element: KtElement, parent: KotlinSliceUsage, uniqueProcessor: SliceUsageProcessor)
val slicePresentationPrefix: String
val testPresentationPrefix: String
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
}
class InlineFunctionCall(callElement: KtElement, function: KtNamedFunction) {
private val callElementPointer = callElement.createSmartPointer()
private val functionPointer = function.createSmartPointer()
val callElement: KtElement?
get() = callElementPointer.element
val function: KtNamedFunction?
get() = functionPointer.element
override fun equals(other: Any?): Boolean {
if (this === other) return true
return other is InlineFunctionCall && other.callElement == callElement && other.function == function
}
override fun hashCode() = 0
}
companion object {
val Default = KotlinSliceAnalysisMode(emptyList(), emptyList())
}
}
@@ -14,8 +14,8 @@ import org.jetbrains.kotlin.idea.KotlinBundle
class KotlinSliceDereferenceUsage(
element: PsiElement,
parent: KotlinSliceUsage,
behaviour: SpecialBehaviour?
) : KotlinSliceUsage(element, parent, behaviour, false) {
mode: KotlinSliceAnalysisMode
) : KotlinSliceUsage(element, parent, mode, false) {
override fun processChildren(processor: Processor<in SliceUsage>) {
// no children
}
@@ -25,8 +25,8 @@ import org.jetbrains.kotlin.idea.KotlinBundle
class KotlinSliceDereferenceUsage(
element: PsiElement,
parent: KotlinSliceUsage,
behaviour: SpecialBehaviour?
) : KotlinSliceUsage(element, parent, behaviour, false) {
mode: KotlinSliceAnalysisMode
) : KotlinSliceUsage(element, parent, mode, false) {
override fun processChildren(processor: Processor<SliceUsage>) {
// no children
}
@@ -49,6 +49,7 @@ class KotlinSliceProvider : SliceLanguageSupportProvider, SliceUsageTransformer
}
val leafAnalyzer by lazy { SliceLeafAnalyzer(LEAF_ELEMENT_EQUALITY, this) }
val nullnessAnalyzer: SliceNullnessAnalyzerBase by lazy {
object : SliceNullnessAnalyzerBase(LEAF_ELEMENT_EQUALITY, this) {
override fun checkNullability(element: PsiElement?): Nullability {
@@ -72,7 +73,7 @@ class KotlinSliceProvider : SliceLanguageSupportProvider, SliceUsageTransformer
override fun transform(usage: SliceUsage): Collection<SliceUsage>? {
if (usage is KotlinSliceUsage) return null
return listOf(KotlinSliceUsage(usage.element, usage.parent, null, false))
return listOf(KotlinSliceUsage(usage.element, usage.parent, KotlinSliceAnalysisMode.Default, false))
}
override fun getExpressionAtCaret(atCaret: PsiElement, dataFlowToThis: Boolean): KtElement? {
@@ -24,57 +24,61 @@ import org.jetbrains.kotlin.idea.findUsages.handlers.SliceUsageProcessor
import org.jetbrains.kotlin.psi.KtElement
open class KotlinSliceUsage : SliceUsage {
interface SpecialBehaviour {
val originalBehaviour: SpecialBehaviour?
fun processUsages(element: KtElement, parent: KotlinSliceUsage, uniqueProcessor: SliceUsageProcessor)
val slicePresentationPrefix: String
val testPresentationPrefix: String
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
}
val behaviour: SpecialBehaviour?
val mode: KotlinSliceAnalysisMode
val forcedExpressionMode: Boolean
private var usageInfo: UsageInfo? = null
constructor(
element: PsiElement,
parent: SliceUsage,
behaviour: SpecialBehaviour?,
mode: KotlinSliceAnalysisMode,
forcedExpressionMode: Boolean,
) : super(element, parent) {
this.behaviour = behaviour
this.mode = mode
this.forcedExpressionMode = forcedExpressionMode
initializeUsageInfo()
}
constructor(element: PsiElement, params: SliceAnalysisParams) : super(element, params) {
this.behaviour = null
this.mode = KotlinSliceAnalysisMode.Default
this.forcedExpressionMode = false
initializeUsageInfo()
}
private fun initializeUsageInfo() {
val originalInfo = getUsageInfo()
if (mode != KotlinSliceAnalysisMode.Default) {
val element = originalInfo.element
if (element != null) {
usageInfo = UsageInfoWrapper(element, mode)
} else {
usageInfo = null
}
} else {
usageInfo = originalInfo
}
}
// we have to replace UsageInfo with another one whose equality takes into account mode
override fun getUsageInfo(): UsageInfo {
return usageInfo ?: super.getUsageInfo()
}
override fun copy(): KotlinSliceUsage {
val element = usageInfo.element!!
val element = getUsageInfo().element!!
return if (parent == null)
KotlinSliceUsage(element, params)
else
KotlinSliceUsage(element, parent, behaviour, forcedExpressionMode)
KotlinSliceUsage(element, parent, mode, forcedExpressionMode)
}
override fun getUsageInfo(): UsageInfo {
val originalInfo = super.getUsageInfo()
if (behaviour != null) {
val element = originalInfo.element ?: return originalInfo
// Do not let IDEA consider usages of the same anonymous function as duplicates when their levels differ
return UsageInfoWrapper(element, behaviour)
}
return originalInfo
}
override fun canBeLeaf() = element != null && behaviour == null
override fun canBeLeaf() = element != null && mode == KotlinSliceAnalysisMode.Default
public override fun processUsagesFlownDownTo(element: PsiElement, uniqueProcessor: SliceUsageProcessor) {
val ktElement = element as? KtElement ?: return
val behaviour = mode.currentBehaviour
if (behaviour != null) {
behaviour.processUsages(ktElement, this, uniqueProcessor)
} else {
@@ -84,6 +88,7 @@ open class KotlinSliceUsage : SliceUsage {
public override fun processUsagesFlownFromThe(element: PsiElement, uniqueProcessor: SliceUsageProcessor) {
val ktElement = element as? KtElement ?: return
val behaviour = mode.currentBehaviour
if (behaviour != null) {
behaviour.processUsages(ktElement, this, uniqueProcessor)
} else {
@@ -92,9 +97,9 @@ open class KotlinSliceUsage : SliceUsage {
}
@Suppress("EqualsOrHashCode")
private class UsageInfoWrapper(element: PsiElement, private val behaviour: SpecialBehaviour) : UsageInfo(element) {
private class UsageInfoWrapper(element: PsiElement, private val mode: KotlinSliceAnalysisMode) : UsageInfo(element) {
override fun equals(other: Any?): Boolean {
return other is UsageInfoWrapper && super.equals(other) && behaviour == other.behaviour
return other is UsageInfoWrapper && super.equals(other) && mode == other.mode
}
}
}
@@ -43,10 +43,8 @@ object KotlinSliceUsageCellRenderer : SliceUsageCellRendererBase() {
}
}
var behaviour = sliceUsage.behaviour
while (behaviour != null) {
for (behaviour in sliceUsage.mode.behaviourStack.reversed()) {
append(behaviour.slicePresentationPrefix, SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES)
behaviour = behaviour.originalBehaviour
}
val declaration = sliceUsage.element?.parents?.firstOrNull {
@@ -12,20 +12,17 @@ import org.jetbrains.kotlin.idea.findUsages.handlers.SliceUsageProcessor
import org.jetbrains.kotlin.psi.Call
import org.jetbrains.kotlin.psi.KtElement
data class LambdaCallsBehaviour(
val sliceProducer: SliceProducer,
override val originalBehaviour: KotlinSliceUsage.SpecialBehaviour?
) : KotlinSliceUsage.SpecialBehaviour {
data class LambdaCallsBehaviour(private val sliceProducer: SliceProducer) : KotlinSliceAnalysisMode.Behaviour {
override fun processUsages(element: KtElement, parent: KotlinSliceUsage, uniqueProcessor: SliceUsageProcessor) {
val processor = object : SliceUsageProcessor {
override fun process(sliceUsage: SliceUsage): Boolean {
if (sliceUsage is KotlinSliceUsage && sliceUsage.behaviour === this@LambdaCallsBehaviour) {
if (sliceUsage is KotlinSliceUsage && sliceUsage.mode.currentBehaviour === this@LambdaCallsBehaviour) {
val sliceElement = sliceUsage.element ?: return true
val resolvedCall = (sliceElement as? KtElement)?.resolveToCall()
if (resolvedCall?.call?.callType == Call.CallType.INVOKE) {
val newSliceUsage = KotlinSliceUsage(resolvedCall.call.callElement, parent, originalBehaviour, true)
return sliceProducer.produceAndProcess(newSliceUsage, originalBehaviour, parent, uniqueProcessor)
val originalMode = sliceUsage.mode.dropBehaviour()
val newSliceUsage = KotlinSliceUsage(resolvedCall.call.callElement, parent, originalMode, true)
return sliceProducer.produceAndProcess(newSliceUsage, originalMode, parent, uniqueProcessor)
}
}
return uniqueProcessor.process(sliceUsage)
@@ -9,10 +9,7 @@ import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.findUsages.handlers.SliceUsageProcessor
import org.jetbrains.kotlin.psi.KtElement
data class LambdaResultInflowBehaviour(
override val originalBehaviour: KotlinSliceUsage.SpecialBehaviour?
) : KotlinSliceUsage.SpecialBehaviour {
object LambdaResultInflowBehaviour : KotlinSliceAnalysisMode.Behaviour {
override fun processUsages(element: KtElement, parent: KotlinSliceUsage, uniqueProcessor: SliceUsageProcessor) {
InflowSlicer(element, uniqueProcessor, parent).processChildren(parent.forcedExpressionMode)
}
@@ -22,4 +19,7 @@ data class LambdaResultInflowBehaviour(
override val testPresentationPrefix: String
get() = "[LAMBDA IN] "
override fun equals(other: Any?) = other === this
override fun hashCode() = 0
}
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.idea.slicer
import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector.Access
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import com.intellij.usageView.UsageInfo
import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue
@@ -22,6 +23,7 @@ import org.jetbrains.kotlin.idea.findUsages.handlers.SliceUsageProcessor
import org.jetbrains.kotlin.idea.search.declarationsSearch.forEachOverridingElement
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReadWriteAccessDetector
import org.jetbrains.kotlin.idea.util.actualsForExpected
import org.jetbrains.kotlin.idea.util.hasInlineModifier
import org.jetbrains.kotlin.idea.util.isExpectDeclaration
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
@@ -200,7 +202,7 @@ class OutflowSlicer(
is CallInstruction -> {
if (!processIfReceiverValue(instruction, pseudoValue)) {
instruction.arguments[pseudoValue]?.originalSource?.getPsi()?.passToProcessor()
instruction.arguments[pseudoValue]?.originalSource?.getPsi()?.passCalledDeclarationToProcessor(instruction.element)
}
}
@@ -226,13 +228,13 @@ class OutflowSlicer(
Call.CallType.DEFAULT -> {
if (receiverValue == resolvedCall.extensionReceiver) {
val targetDeclaration = resolvedCall.resultingDescriptor.originalSource.getPsi()
(targetDeclaration as? KtCallableDeclaration)?.receiverTypeReference?.passToProcessor()
(targetDeclaration as? KtCallableDeclaration)?.receiverTypeReference?.passCalledDeclarationToProcessor(instruction.element)
}
}
Call.CallType.INVOKE -> {
if (receiverValue == resolvedCall.dispatchReceiver && behaviour is LambdaCallsBehaviour) {
instruction.element.passToProcessor(behaviour)
if (receiverValue == resolvedCall.dispatchReceiver && mode.currentBehaviour is LambdaCallsBehaviour) {
instruction.element.passCalledDeclarationToProcessor(instruction.element, mode)
}
}
@@ -258,7 +260,7 @@ class OutflowSlicer(
} ?: return
if (receiver != null && resolvedCall.dispatchReceiver == receiver) {
processor.process(KotlinSliceDereferenceUsage(expression, parentUsage, behaviour))
processor.process(KotlinSliceDereferenceUsage(expression, parentUsage, mode))
}
}
@@ -276,4 +278,20 @@ class OutflowSlicer(
}
}
}
private fun PsiElement.passCalledDeclarationToProcessor(
callElement: KtElement,
mode: KotlinSliceAnalysisMode = this@OutflowSlicer.mode
) {
var newMode = mode
when (this) {
is KtParameter -> {
val ownerFunction = ownerFunction as? KtNamedFunction
if (ownerFunction != null && ownerFunction.hasInlineModifier()) {
newMode = mode.withInlineFunctionCall(callElement, ownerFunction)
}
}
}
passToProcessor(newMode)
}
}
@@ -18,14 +18,14 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
import org.jetbrains.kotlin.resolve.source.getPsi
object ReceiverSliceProducer : SliceProducer {
override fun produce(usage: UsageInfo, behaviour: KotlinSliceUsage.SpecialBehaviour?, parent: SliceUsage): Collection<SliceUsage>? {
override fun produce(usage: UsageInfo, mode: KotlinSliceAnalysisMode, parent: SliceUsage): Collection<SliceUsage>? {
val refElement = usage.element ?: return emptyList()
when (refElement) {
is KtExpression -> {
val resolvedCall = refElement.resolveToCall() ?: return emptyList()
when (val receiver = resolvedCall.extensionReceiver) {
is ExpressionReceiver -> {
return listOf(KotlinSliceUsage(receiver.expression, parent, behaviour, forcedExpressionMode = true))
return listOf(KotlinSliceUsage(receiver.expression, parent, mode, forcedExpressionMode = true))
}
is ImplicitReceiver -> {
@@ -33,13 +33,13 @@ object ReceiverSliceProducer : SliceProducer {
?: return emptyList()
when (val callableDeclaration = callableDescriptor.originalSource.getPsi()) {
is KtFunctionLiteral -> {
val newBehaviour = LambdaCallsBehaviour(ReceiverSliceProducer, behaviour)
return listOf(KotlinSliceUsage(callableDeclaration, parent, newBehaviour, forcedExpressionMode = true))
val newMode = mode.withBehaviour(LambdaCallsBehaviour(ReceiverSliceProducer))
return listOf(KotlinSliceUsage(callableDeclaration, parent, newMode, forcedExpressionMode = true))
}
is KtCallableDeclaration -> {
val receiverTypeReference = callableDeclaration.receiverTypeReference ?: return emptyList()
return listOf(KotlinSliceUsage(receiverTypeReference, parent, behaviour, false))
return listOf(KotlinSliceUsage(receiverTypeReference, parent, mode, false))
}
else -> return emptyList()
@@ -52,7 +52,7 @@ object ReceiverSliceProducer : SliceProducer {
else -> {
val argument = (refElement.parent as? PsiCall)?.argumentList?.expressions?.getOrNull(0) ?: return emptyList()
return listOf(KotlinSliceUsage(argument, parent, behaviour, true))
return listOf(KotlinSliceUsage(argument, parent, mode, false))
}
}
}
@@ -10,13 +10,13 @@ import com.intellij.usageView.UsageInfo
import org.jetbrains.kotlin.idea.findUsages.handlers.SliceUsageProcessor
interface SliceProducer {
fun produce(usage: UsageInfo, behaviour: KotlinSliceUsage.SpecialBehaviour?, parent: SliceUsage): Collection<SliceUsage>?
fun produce(usage: UsageInfo, mode: KotlinSliceAnalysisMode, parent: SliceUsage): Collection<SliceUsage>?
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
object Trivial : SliceProducer {
override fun produce(usage: UsageInfo, behaviour: KotlinSliceUsage.SpecialBehaviour?, parent: SliceUsage): Collection<SliceUsage>? {
override fun produce(usage: UsageInfo, mode: KotlinSliceAnalysisMode, parent: SliceUsage): Collection<SliceUsage>? {
return null
}
@@ -27,11 +27,11 @@ interface SliceProducer {
fun SliceProducer.produceAndProcess(
sliceUsage: SliceUsage,
behaviour: KotlinSliceUsage.SpecialBehaviour?,
mode: KotlinSliceAnalysisMode,
parentUsage: SliceUsage,
processor: SliceUsageProcessor
): Boolean {
val result = produce(sliceUsage.usageInfo, behaviour, parentUsage) ?: listOf(sliceUsage)
val result = produce(sliceUsage.usageInfo, mode, parentUsage) ?: listOf(sliceUsage)
for (usage in result) {
if (!processor.process(usage)) return false
}
@@ -40,7 +40,7 @@ abstract class Slicer(
abstract fun processChildren(forcedExpressionMode: Boolean)
protected val analysisScope: SearchScope = parentUsage.scope.toSearchScope()
protected val behaviour: KotlinSliceUsage.SpecialBehaviour? = parentUsage.behaviour
protected val mode: KotlinSliceAnalysisMode = parentUsage.mode
protected val project = element.project
protected class PseudocodeCache {
@@ -57,24 +57,21 @@ abstract class Slicer(
protected val pseudocodeCache = PseudocodeCache()
protected fun PsiElement.passToProcessor(
behaviour: KotlinSliceUsage.SpecialBehaviour? = this@Slicer.behaviour,
forcedExpressionMode: Boolean = false,
) {
processor.process(KotlinSliceUsage(this, parentUsage, behaviour, forcedExpressionMode))
protected fun PsiElement.passToProcessor(mode: KotlinSliceAnalysisMode = this@Slicer.mode) {
processor.process(KotlinSliceUsage(this, parentUsage, mode, false))
}
protected fun PsiElement.passToProcessorAsValue(behaviour: KotlinSliceUsage.SpecialBehaviour? = this@Slicer.behaviour) {
passToProcessor(behaviour, forcedExpressionMode = true)
protected fun PsiElement.passToProcessorAsValue(mode: KotlinSliceAnalysisMode = this@Slicer.mode) {
processor.process(KotlinSliceUsage(this, parentUsage, mode, forcedExpressionMode = true))
}
protected fun processCalls(
protected open fun processCalls(
callable: KtCallableDeclaration,
includeOverriders: Boolean,
sliceProducer: SliceProducer
sliceProducer: SliceProducer,
) {
if (callable is KtFunctionLiteral || callable is KtFunction && callable.name == null) {
callable.passToProcessorAsValue(LambdaCallsBehaviour(sliceProducer, behaviour))
callable.passToProcessorAsValue(mode.withBehaviour(LambdaCallsBehaviour(sliceProducer)))
return
}
@@ -117,8 +114,8 @@ abstract class Slicer(
is KtDeclaration -> {
val usageProcessor: (UsageInfo) -> Unit = processor@ { usageInfo ->
val element = usageInfo.element ?: return@processor
val sliceUsage = KotlinSliceUsage(element, parentUsage, behaviour, false)
sliceProducer.produceAndProcess(sliceUsage, behaviour, parentUsage, processor)
val sliceUsage = KotlinSliceUsage(element, parentUsage, mode, false)
sliceProducer.produceAndProcess(sliceUsage, mode, parentUsage, processor)
}
if (includeOverriders) {
declaration.processAllUsages(options, usageProcessor)
@@ -129,12 +126,12 @@ abstract class Slicer(
is PsiMethod -> {
val sliceUsage = JavaSliceUsage.createRootUsage(declaration, parentUsage.params)
sliceProducer.produceAndProcess(sliceUsage, behaviour, parentUsage, processor)
sliceProducer.produceAndProcess(sliceUsage, mode, parentUsage, processor)
}
else -> {
val sliceUsage = KotlinSliceUsage(declaration, parentUsage, behaviour, false)
sliceProducer.produceAndProcess(sliceUsage, behaviour, parentUsage, processor)
val sliceUsage = KotlinSliceUsage(declaration, parentUsage, mode, false)
sliceProducer.produceAndProcess(sliceUsage, mode, parentUsage, processor)
}
}
}
@@ -2,7 +2,8 @@
5 val <bold>v = this</bold>
5 val v = <bold>this</bold>
4 [LAMBDA CALLS] with("A") <bold>{</bold>
9 [LAMBDA CALLS] inline fun <T, R> with(receiver: T, <bold>block: T.() -> R</bold>): R {
10 return <bold>receiver</bold>.block()
9 inline fun <T, R> with(<bold>receiver: T</bold>, block: T.() -> R): R {
9 (INLINE CALL with) [LAMBDA CALLS] inline fun <T, R> with(receiver: T, <bold>block: T.() -> R</bold>): R {
10 (INLINE CALL with) return <bold>receiver</bold>.block()
9 (INLINE CALL with) inline fun <T, R> with(<bold>receiver: T</bold>, block: T.() -> R): R {
4 with(<bold>"A"</bold>) {
@@ -1,7 +1,7 @@
5 val <bold>v = this</bold>
5 val v = <bold>this</bold>
4 [LAMBDA CALLS] with("A") <bold>{</bold>
9 [LAMBDA CALLS] inline fun <T, R> with(receiver: T, <bold>block: T.() -> R</bold>): R {
10 return <bold>receiver</bold>.block()
9 inline fun <T, R> with(<bold>receiver: T</bold>, block: T.() -> R): R {
9 (INLINE CALL with) [LAMBDA CALLS] inline fun <T, R> with(receiver: T, <bold>block: T.() -> R</bold>): R {
10 (INLINE CALL with) return <bold>receiver</bold>.block()
9 (INLINE CALL with) inline fun <T, R> with(<bold>receiver: T</bold>, block: T.() -> R): R {
4 with(<bold>"A"</bold>) {
+60
View File
@@ -0,0 +1,60 @@
// FLOW: IN
fun <caret>Any.extensionFun() {
}
fun String.foo() {
with(123) {
extensionFun()
}
with(456) {
this.extensionFun()
}
with(789) {
// no calls here
}
withNoInline(1) {
extensionFun()
}
withNoInline(2) {
// no calls here
}
"A".let {
it.extensionFun()
}
"B".let {
// no calls here
}
"D".letNoInline {
it.extensionFun()
}
"C".letNoInline {
// no calls here
}
}
inline fun <T, R> with(receiver: T, block: T.() -> R): R {
val result = receiver.block()
return result
}
inline fun <T, R> T.let(block: (T) -> R): R {
return block(this)
}
fun <T, R> withNoInline(receiver: T, block: T.() -> R): R {
val result = receiver.block()
return result
}
fun <T, R> T.letNoInline(block: (T) -> R): R {
return block(this)
}
@@ -0,0 +1,60 @@
35 <bold>"D"</bold>.letNoInline {
3 fun <bold>Any</bold>.extensionFun() {
36 <bold>it</bold>.extensionFun()
35 [LAMBDA CALLS] "D".letNoInline <bold>{</bold>
58 [LAMBDA CALLS] fun <T, R> T.letNoInline(<bold>block: (T) -> R</bold>): R {
59 return block(<bold>this</bold>)
58 fun <T, R> <bold>T</bold>.letNoInline(block: (T) -> R): R {
35 <bold>"D"</bold>.letNoInline {
39 <bold>"C"</bold>.letNoInline {
3 fun <bold>Any</bold>.extensionFun() {
36 <bold>it</bold>.extensionFun()
35 [LAMBDA CALLS] "D".letNoInline <bold>{</bold>
58 [LAMBDA CALLS] fun <T, R> T.letNoInline(<bold>block: (T) -> R</bold>): R {
59 return block(<bold>this</bold>)
58 fun <T, R> <bold>T</bold>.letNoInline(block: (T) -> R): R {
39 <bold>"C"</bold>.letNoInline {
27 <bold>"A"</bold>.let {
3 fun <bold>Any</bold>.extensionFun() {
28 <bold>it</bold>.extensionFun()
27 [LAMBDA CALLS] "A".let <bold>{</bold>
49 (INLINE CALL let) [LAMBDA CALLS] inline fun <T, R> T.let(<bold>block: (T) -> R</bold>): R {
50 (INLINE CALL let) return block(<bold>this</bold>)
49 (INLINE CALL let) inline fun <T, R> <bold>T</bold>.let(block: (T) -> R): R {
27 <bold>"A"</bold>.let {
19 withNoInline(<bold>1</bold>) {
3 fun <bold>Any</bold>.extensionFun() {
19 [LAMBDA CALLS] withNoInline(1) <bold>{</bold>
53 [LAMBDA CALLS] fun <T, R> withNoInline(receiver: T, <bold>block: T.() -> R</bold>): R {
54 val result = <bold>receiver</bold>.block()
53 fun <T, R> withNoInline(<bold>receiver: T</bold>, block: T.() -> R): R {
19 withNoInline(<bold>1</bold>) {
7 with(<bold>123</bold>) {
3 fun <bold>Any</bold>.extensionFun() {
7 [LAMBDA CALLS] with(123) <bold>{</bold>
44 (INLINE CALL with) [LAMBDA CALLS] inline fun <T, R> with(receiver: T, <bold>block: T.() -> R</bold>): R {
45 (INLINE CALL with) val result = <bold>receiver</bold>.block()
44 (INLINE CALL with) inline fun <T, R> with(<bold>receiver: T</bold>, block: T.() -> R): R {
7 with(<bold>123</bold>) {
23 withNoInline(<bold>2</bold>) {
3 fun <bold>Any</bold>.extensionFun() {
19 [LAMBDA CALLS] withNoInline(1) <bold>{</bold>
53 [LAMBDA CALLS] fun <T, R> withNoInline(receiver: T, <bold>block: T.() -> R</bold>): R {
54 val result = <bold>receiver</bold>.block()
53 fun <T, R> withNoInline(<bold>receiver: T</bold>, block: T.() -> R): R {
23 withNoInline(<bold>2</bold>) {
11 with(<bold>456</bold>) {
3 fun <bold>Any</bold>.extensionFun() {
12 <bold>this</bold>.extensionFun()
11 [LAMBDA CALLS] with(456) <bold>{</bold>
44 (INLINE CALL with) [LAMBDA CALLS] inline fun <T, R> with(receiver: T, <bold>block: T.() -> R</bold>): R {
45 (INLINE CALL with) val result = <bold>receiver</bold>.block()
44 (INLINE CALL with) inline fun <T, R> with(<bold>receiver: T</bold>, block: T.() -> R): R {
11 with(<bold>456</bold>) {
@@ -0,0 +1,13 @@
[NotNull Values]
7 with(123) <bold>{</bold>
3 fun <bold>Any</bold>.extensionFun() {
7 [LAMBDA CALLS] with(123) <bold>{</bold>
19 [LAMBDA CALLS] withNoInline(1) <bold>{</bold>
12 <bold>this</bold>.extensionFun()
3 fun <bold>Any</bold>.extensionFun() {
12 <bold>this</bold>.extensionFun()
36 <bold>it</bold>.extensionFun()
3 fun <bold>Any</bold>.extensionFun() {
28 <bold>it</bold>.extensionFun()
36 <bold>it</bold>.extensionFun()
@@ -0,0 +1,31 @@
3 fun <bold>Any</bold>.extensionFun() {
7 [LAMBDA CALLS] with(123) <bold>{</bold>
44 (INLINE CALL with) [LAMBDA CALLS] inline fun <T, R> with(receiver: T, <bold>block: T.() -> R</bold>): R {
45 (INLINE CALL with) val result = <bold>receiver</bold>.block()
44 (INLINE CALL with) inline fun <T, R> with(<bold>receiver: T</bold>, block: T.() -> R): R {
7 with(<bold>123</bold>) {
12 <bold>this</bold>.extensionFun()
11 [LAMBDA CALLS] with(456) <bold>{</bold>
44 (INLINE CALL with) [LAMBDA CALLS] inline fun <T, R> with(receiver: T, <bold>block: T.() -> R</bold>): R {
45 (INLINE CALL with) val result = <bold>receiver</bold>.block()
44 (INLINE CALL with) inline fun <T, R> with(<bold>receiver: T</bold>, block: T.() -> R): R {
11 with(<bold>456</bold>) {
19 [LAMBDA CALLS] withNoInline(1) <bold>{</bold>
53 [LAMBDA CALLS] fun <T, R> withNoInline(receiver: T, <bold>block: T.() -> R</bold>): R {
54 val result = <bold>receiver</bold>.block()
53 fun <T, R> withNoInline(<bold>receiver: T</bold>, block: T.() -> R): R {
19 withNoInline(<bold>1</bold>) {
23 withNoInline(<bold>2</bold>) {
28 <bold>it</bold>.extensionFun()
27 [LAMBDA CALLS] "A".let <bold>{</bold>
49 (INLINE CALL let) [LAMBDA CALLS] inline fun <T, R> T.let(<bold>block: (T) -> R</bold>): R {
50 (INLINE CALL let) return block(<bold>this</bold>)
49 (INLINE CALL let) inline fun <T, R> <bold>T</bold>.let(block: (T) -> R): R {
27 <bold>"A"</bold>.let {
36 <bold>it</bold>.extensionFun()
35 [LAMBDA CALLS] "D".letNoInline <bold>{</bold>
58 [LAMBDA CALLS] fun <T, R> T.letNoInline(<bold>block: (T) -> R</bold>): R {
59 return block(<bold>this</bold>)
58 fun <T, R> <bold>T</bold>.letNoInline(block: (T) -> R): R {
35 <bold>"D"</bold>.letNoInline {
39 <bold>"C"</bold>.letNoInline {
+7 -7
View File
@@ -1,18 +1,18 @@
5 fun bar(<bold>a: Int</bold>): Int = foo(a) { if (it > 0) it else return 0 }
5 fun <bold>bar(a: Int): Int = foo(a) { if (it > 0) it else return 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 IN] inline fun foo(a: Int, f: (Int) -> Int) = <bold>f</bold>(a)
3 [LAMBDA IN] inline fun foo(a: Int, <bold>f: (Int) -> Int</bold>) = f(a)
3 (INLINE CALL foo) inline fun <bold>foo(a: Int, f: (Int) -> Int) = f(a)</bold>
3 (INLINE CALL foo) inline fun foo(a: Int, f: (Int) -> Int) = <bold>f(a)</bold>
3 (INLINE CALL foo) [LAMBDA IN] inline fun foo(a: Int, f: (Int) -> Int) = <bold>f</bold>(a)
3 (INLINE CALL foo) [LAMBDA IN] inline fun foo(a: Int, <bold>f: (Int) -> Int</bold>) = f(a)
5 [LAMBDA IN] 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 }
5 [LAMBDA CALLS] fun bar(a: Int): Int = foo(a) <bold>{ if (it > 0) it else return 0 }</bold>
3 [LAMBDA CALLS] inline fun foo(a: Int, <bold>f: (Int) -> Int</bold>) = f(a)
3 inline fun foo(a: Int, f: (Int) -> Int) = f(<bold>a</bold>)
3 inline fun foo(<bold>a: Int</bold>, f: (Int) -> Int) = f(a)
3 (INLINE CALL foo) [LAMBDA CALLS] inline fun foo(a: Int, <bold>f: (Int) -> Int</bold>) = f(a)
3 (INLINE CALL foo) inline fun foo(a: Int, f: (Int) -> Int) = f(<bold>a</bold>)
3 (INLINE CALL foo) inline fun foo(<bold>a: Int</bold>, f: (Int) -> Int) = f(a)
5 fun bar(a: Int): Int = foo(<bold>a</bold>) { if (it > 0) it else return 0 }
5 fun bar(<bold>a: Int</bold>): Int = foo(a) { if (it > 0) it else return 0 }
+7 -7
View File
@@ -1,17 +1,17 @@
5 fun <bold>bar(a: Int): Int = foo(a) { if (it > 0) it else return 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 IN] inline fun foo(a: Int, f: (Int) -> Int) = <bold>f</bold>(a)
3 [LAMBDA IN] inline fun foo(a: Int, <bold>f: (Int) -> Int</bold>) = f(a)
3 (INLINE CALL foo) inline fun <bold>foo(a: Int, f: (Int) -> Int) = f(a)</bold>
3 (INLINE CALL foo) inline fun foo(a: Int, f: (Int) -> Int) = <bold>f(a)</bold>
3 (INLINE CALL foo) [LAMBDA IN] inline fun foo(a: Int, f: (Int) -> Int) = <bold>f</bold>(a)
3 (INLINE CALL foo) [LAMBDA IN] inline fun foo(a: Int, <bold>f: (Int) -> Int</bold>) = f(a)
5 [LAMBDA IN] 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 }
5 [LAMBDA CALLS] fun bar(a: Int): Int = foo(a) <bold>{ if (it > 0) it else return 0 }</bold>
3 [LAMBDA CALLS] inline fun foo(a: Int, <bold>f: (Int) -> Int</bold>) = f(a)
3 inline fun foo(a: Int, f: (Int) -> Int) = f(<bold>a</bold>)
3 inline fun foo(<bold>a: Int</bold>, f: (Int) -> Int) = f(a)
3 (INLINE CALL foo) [LAMBDA CALLS] inline fun foo(a: Int, <bold>f: (Int) -> Int</bold>) = f(a)
3 (INLINE CALL foo) inline fun foo(a: Int, f: (Int) -> Int) = f(<bold>a</bold>)
3 (INLINE CALL foo) inline fun foo(<bold>a: Int</bold>, f: (Int) -> Int) = f(a)
5 fun bar(a: Int): Int = foo(<bold>a</bold>) { if (it > 0) it else return 0 }
5 fun bar(<bold>a: Int</bold>): Int = foo(a) { if (it > 0) it else return 0 }
5 fun bar(a: Int): Int = foo(a) { if (it > 0) it else return <bold>0</bold> }
@@ -18,8 +18,8 @@
18 with(<bold>123</bold>) {
8 fun <bold>Any</bold>.extensionFun() {
18 [LAMBDA CALLS] with(123) <bold>{</bold>
27 [LAMBDA CALLS] inline fun <T, R> with(receiver: T, <bold>block: T.() -> R</bold>): R {
28 return <bold>receiver</bold>.block()
27 inline fun <T, R> with(<bold>receiver: T</bold>, block: T.() -> R): R {
27 (INLINE CALL with) [LAMBDA CALLS] inline fun <T, R> with(receiver: T, <bold>block: T.() -> R</bold>): R {
28 (INLINE CALL with) return <bold>receiver</bold>.block()
27 (INLINE CALL with) inline fun <T, R> with(<bold>receiver: T</bold>, block: T.() -> R): R {
18 with(<bold>123</bold>) {
@@ -5,7 +5,7 @@
12 <bold>""</bold>.extensionFun()
14 <bold>1</bold>.extensionFun()
18 [LAMBDA CALLS] with(123) <bold>{</bold>
27 [LAMBDA CALLS] inline fun <T, R> with(receiver: T, <bold>block: T.() -> R</bold>): R {
28 return <bold>receiver</bold>.block()
27 inline fun <T, R> with(<bold>receiver: T</bold>, block: T.() -> R): R {
27 (INLINE CALL with) [LAMBDA CALLS] inline fun <T, R> with(receiver: T, <bold>block: T.() -> R</bold>): R {
28 (INLINE CALL with) return <bold>receiver</bold>.block()
27 (INLINE CALL with) inline fun <T, R> with(<bold>receiver: T</bold>, block: T.() -> R): R {
18 with(<bold>123</bold>) {
@@ -178,6 +178,11 @@ public class SlicerLeafGroupingTestGenerated extends AbstractSlicerLeafGroupingT
runTest("idea/testData/slicer/inflow/ifExpression.kt");
}
@TestMetadata("inlineFunctionManyCalls.kt")
public void testInlineFunctionManyCalls() throws Exception {
runTest("idea/testData/slicer/inflow/inlineFunctionManyCalls.kt");
}
@TestMetadata("lambdaImplicitParameter.kt")
public void testLambdaImplicitParameter() throws Exception {
runTest("idea/testData/slicer/inflow/lambdaImplicitParameter.kt");
@@ -178,6 +178,11 @@ public class SlicerNullnessGroupingTestGenerated extends AbstractSlicerNullnessG
runTest("idea/testData/slicer/inflow/ifExpression.kt");
}
@TestMetadata("inlineFunctionManyCalls.kt")
public void testInlineFunctionManyCalls() throws Exception {
runTest("idea/testData/slicer/inflow/inlineFunctionManyCalls.kt");
}
@TestMetadata("lambdaImplicitParameter.kt")
public void testLambdaImplicitParameter() throws Exception {
runTest("idea/testData/slicer/inflow/lambdaImplicitParameter.kt");
@@ -52,8 +52,10 @@ internal fun buildTreeRepresentation(rootNode: SliceNode): String {
node.sortedChildren.forEach { append(process(it, indent)) }
return@buildString
}
// SliceLeafValueClassNode is package-private
node.isSliceLeafValueClassNode() -> append("[${node.nodeText}]\n")
else -> {
val chunks = usage.text
append(chunks.first().render() + " ")
@@ -62,11 +64,10 @@ internal fun buildTreeRepresentation(rootNode: SliceNode): String {
append("DEREFERENCE: ")
}
if (usage is KotlinSliceUsage) {
var behaviour = usage.behaviour
while (behaviour != null) {
append(behaviour.testPresentationPrefix)
behaviour = behaviour.originalBehaviour
usage.mode.inlineCallStack.forEach {
append("(INLINE CALL ${it.function?.name}) ")
}
usage.mode.behaviourStack.reversed().joinTo(this, separator = "") { it.testPresentationPrefix }
}
chunks.slice(1 until chunks.size).joinTo(
this,
@@ -190,6 +190,11 @@ public class SlicerTreeTestGenerated extends AbstractSlicerTreeTest {
runTest("idea/testData/slicer/inflow/ifExpression.kt");
}
@TestMetadata("inlineFunctionManyCalls.kt")
public void testInlineFunctionManyCalls() throws Exception {
runTest("idea/testData/slicer/inflow/inlineFunctionManyCalls.kt");
}
@TestMetadata("lambdaImplicitParameter.kt")
public void testLambdaImplicitParameter() throws Exception {
runTest("idea/testData/slicer/inflow/lambdaImplicitParameter.kt");