diff --git a/idea/src/org/jetbrains/kotlin/idea/slicer/InflowSlicer.kt b/idea/src/org/jetbrains/kotlin/idea/slicer/InflowSlicer.kt index beba45363e4..d48b869c400 100644 --- a/idea/src/org/jetbrains/kotlin/idea/slicer/InflowSlicer.kt +++ b/idea/src/org/jetbrains/kotlin/idea/slicer/InflowSlicer.kt @@ -6,12 +6,10 @@ package org.jetbrains.kotlin.idea.slicer import com.intellij.psi.PsiCall -import com.intellij.psi.PsiElement import com.intellij.psi.search.LocalSearchScope import com.intellij.psi.search.SearchScope import com.intellij.psi.search.searches.ReferencesSearch import com.intellij.usageView.UsageInfo -import org.jetbrains.kotlin.asJava.namedUnwrappedElement import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.* @@ -27,15 +25,14 @@ import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinValVar import org.jetbrains.kotlin.idea.refactoring.changeSignature.toValVar import org.jetbrains.kotlin.idea.references.KtPropertyDelegationMethodsReference import org.jetbrains.kotlin.idea.references.ReferenceAccess +import org.jetbrains.kotlin.idea.references.mainReference 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.idea.search.ideaExtensions.KotlinTargetElementEvaluator import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType +import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode @@ -141,10 +138,23 @@ class InflowSlicer( is KtNamedFunction -> expression.takeIf { expression.name == null } else -> null } + val currentBehaviour = mode.currentBehaviour if (lambda != null) { - if (mode.currentBehaviour is LambdaResultInflowBehaviour) { + if (currentBehaviour is LambdaResultInflowBehaviour) { lambda.passToProcessor(mode.dropBehaviour()) } + else if (currentBehaviour is LambdaArgumentInflowBehaviour) { + val valueParameters = lambda.valueParameters + if (valueParameters.isEmpty() && lambda is KtFunctionLiteral) { + if (currentBehaviour.argumentIndex == 0) { + lambda.implicitItUsages().forEach { + it.passToProcessor(mode.dropBehaviour()) + } + } + } else { + valueParameters.getOrNull(currentBehaviour.argumentIndex)?.passToProcessor(mode.dropBehaviour()) + } + } return } @@ -195,12 +205,12 @@ class InflowSlicer( processCalls(functionLiteral, false, ArgumentSliceProducer(parameterDescriptor)) } } else { - accessedDeclaration.passDeclarationToProcessorWithOverriders(createdAt.element) + accessedDeclaration.passDeclarationToProcessorWithOverriders() } } else -> { - accessedDeclaration?.passDeclarationToProcessorWithOverriders(createdAt.element) + accessedDeclaration?.passDeclarationToProcessorWithOverriders() } } } @@ -216,9 +226,10 @@ class InflowSlicer( val referencedDescriptor = bindingContext[BindingContext.REFERENCE_TARGET, callableRefExpr.callableReference] ?: return val referencedDeclaration = (referencedDescriptor as? DeclarationDescriptorWithSource)?.originalSource?.getPsi() ?: return - if (mode.currentBehaviour is LambdaResultInflowBehaviour) { + if (currentBehaviour is LambdaResultInflowBehaviour) { referencedDeclaration.passToProcessor(mode.dropBehaviour()) } + //TODO: LambdaArgumentInflowBehaviour } else -> return @@ -231,7 +242,7 @@ class InflowSlicer( (resolvedCall.dispatchReceiver as? ExpressionReceiver)?.expression ?.passToProcessorAsValue(mode.withBehaviour(LambdaResultInflowBehaviour)) } else { - resultingDescriptor.originalSource.getPsi()?.passDeclarationToProcessorWithOverriders(createdAt.element) + resultingDescriptor.originalSource.getPsi()?.passToProcessorInCallMode(createdAt.element, withOverriders = true) } } } @@ -306,27 +317,6 @@ class InflowSlicer( } } - 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(newMode) } - - if (this is KtCallableDeclaration && isExpectDeclaration()) { - resolveToDescriptorIfAny(BodyResolveMode.FULL) - ?.actualsForExpected() - ?.forEach { - (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) @@ -339,4 +329,12 @@ class InflowSlicer( super.processCalls(callable, includeOverriders, sliceProducer) } + + private fun KtFunctionLiteral.implicitItUsages(): Collection { + return collectDescendantsOfType(fun(expression: KtSimpleNameExpression): Boolean { + if (expression.getQualifiedExpressionForSelector() != null || expression.getReferencedName() != "it") return false + val lBrace = KotlinTargetElementEvaluator.findLambdaOpenLBraceForGeneratedIt(expression.mainReference) ?: return false + return lBrace == this.lBrace.node.treeNext.psi + }) + } } diff --git a/idea/src/org/jetbrains/kotlin/idea/slicer/LambdaArgumentInflowBehaviour.kt b/idea/src/org/jetbrains/kotlin/idea/slicer/LambdaArgumentInflowBehaviour.kt new file mode 100644 index 00000000000..5a82fccd9f6 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/slicer/LambdaArgumentInflowBehaviour.kt @@ -0,0 +1,21 @@ +/* + * 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 + +data class LambdaArgumentInflowBehaviour(val argumentIndex: Int) : KotlinSliceAnalysisMode.Behaviour { + override fun processUsages(element: KtElement, parent: KotlinSliceUsage, uniqueProcessor: SliceUsageProcessor) { + InflowSlicer(element, uniqueProcessor, parent).processChildren(parent.forcedExpressionMode) + } + + override val slicePresentationPrefix: String + get() = TODO() + + override val testPresentationPrefix: String + get() = "[LAMBDA ARGUMENT IN] " +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/slicer/OutflowSlicer.kt b/idea/src/org/jetbrains/kotlin/idea/slicer/OutflowSlicer.kt index 90f7b0cfeeb..889fb75eec0 100644 --- a/idea/src/org/jetbrains/kotlin/idea/slicer/OutflowSlicer.kt +++ b/idea/src/org/jetbrains/kotlin/idea/slicer/OutflowSlicer.kt @@ -6,7 +6,6 @@ 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 @@ -16,6 +15,8 @@ import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.* import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.ReturnValueInstruction import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny @@ -23,13 +24,13 @@ 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 import org.jetbrains.kotlin.psi.psiUtil.parameterIndex import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.source.getPsi +import org.jetbrains.kotlin.util.OperatorNameConventions class OutflowSlicer( element: KtElement, @@ -202,12 +203,46 @@ class OutflowSlicer( is CallInstruction -> { if (!processIfReceiverValue(instruction, pseudoValue)) { - instruction.arguments[pseudoValue]?.originalSource?.getPsi()?.passCalledDeclarationToProcessor(instruction.element) + val parameterDescriptor = instruction.arguments[pseudoValue] ?: return@processPseudocodeUsages + val parameter = parameterDescriptor.originalSource.getPsi() + if (parameter != null) { + parameter.passToProcessorInCallMode(instruction.element) + } else { + val function = parameterDescriptor.containingDeclaration as? FunctionDescriptor + if (function != null && function.isOperator && function.name == OperatorNameConventions.INVOKE) { + val receiverPseudoValue = instruction.receiverValues.entries.singleOrNull()?.key + ?: return@processPseudocodeUsages + if (receiverPseudoValue.createdAt != null) { + when (val createdAt = receiverPseudoValue.createdAt) { + is ReadValueInstruction -> { + val accessedDescriptor = createdAt.target.accessedDescriptor ?: return@processPseudocodeUsages + val accessedDeclaration = accessedDescriptor.originalSource.getPsi() ?: return@processPseudocodeUsages + when (accessedDescriptor) { + is ValueParameterDescriptor -> { + //TODO: argument index is not always correct - first argument can be receiver + val newMode = mode.withBehaviour(LambdaArgumentInflowBehaviour(accessedDescriptor.index)) + accessedDeclaration.passToProcessor(newMode) + } + } + } + } + } + } + } } } is ReturnValueInstruction -> { - instruction.subroutine.passToProcessor() + val subroutine = instruction.subroutine + if (subroutine is KtNamedFunction) { + val (newMode, callElement) = mode.popInlineFunctionCall(subroutine) + if (newMode != null) { + callElement?.passToProcessor(newMode) + return@processPseudocodeUsages + } + } + + subroutine.passToProcessor() } is MagicInstruction -> { @@ -228,13 +263,13 @@ class OutflowSlicer( Call.CallType.DEFAULT -> { if (receiverValue == resolvedCall.extensionReceiver) { val targetDeclaration = resolvedCall.resultingDescriptor.originalSource.getPsi() - (targetDeclaration as? KtCallableDeclaration)?.receiverTypeReference?.passCalledDeclarationToProcessor(instruction.element) + (targetDeclaration as? KtCallableDeclaration)?.receiverTypeReference?.passToProcessorInCallMode(instruction.element) } } Call.CallType.INVOKE -> { if (receiverValue == resolvedCall.dispatchReceiver && mode.currentBehaviour is LambdaCallsBehaviour) { - instruction.element.passCalledDeclarationToProcessor(instruction.element, mode) + instruction.element.passToProcessor() } } @@ -278,20 +313,4 @@ 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) - } } diff --git a/idea/src/org/jetbrains/kotlin/idea/slicer/Slicer.kt b/idea/src/org/jetbrains/kotlin/idea/slicer/Slicer.kt index aca267e8a72..127ef32786e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/slicer/Slicer.kt +++ b/idea/src/org/jetbrains/kotlin/idea/slicer/Slicer.kt @@ -11,6 +11,7 @@ import com.intellij.psi.search.SearchScope import com.intellij.slicer.JavaSliceUsage import com.intellij.usageView.UsageInfo import com.intellij.util.containers.addIfNotNull +import org.jetbrains.kotlin.asJava.namedUnwrappedElement import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode import org.jetbrains.kotlin.cfg.pseudocode.containingDeclarationForPseudocode import org.jetbrains.kotlin.cfg.pseudocode.getContainingPseudocode @@ -25,7 +26,12 @@ import org.jetbrains.kotlin.idea.findUsages.KotlinPropertyFindUsagesOptions import org.jetbrains.kotlin.idea.findUsages.handlers.SliceUsageProcessor import org.jetbrains.kotlin.idea.findUsages.processAllExactUsages import org.jetbrains.kotlin.idea.findUsages.processAllUsages +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.expectedDescriptor +import org.jetbrains.kotlin.idea.util.hasInlineModifier +import org.jetbrains.kotlin.idea.util.isExpectDeclaration import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode @@ -65,6 +71,52 @@ abstract class Slicer( processor.process(KotlinSliceUsage(this, parentUsage, mode, forcedExpressionMode = true)) } + protected fun PsiElement.passToProcessorInCallMode(callElement: KtElement, withOverriders: Boolean = false) { + val newMode = when (this) { + is KtNamedFunction -> this.callMode(callElement) + + is KtParameter -> ownerFunction.callMode(callElement) + + is KtTypeReference -> { + val declaration = parent + require(declaration is KtCallableDeclaration) + require(this == declaration.receiverTypeReference) + declaration.callMode(callElement) + } + + else -> mode + } + + if (withOverriders) { + passDeclarationToProcessorWithOverriders(newMode) + } else { + passToProcessor(newMode) + } + } + + protected fun PsiElement.passDeclarationToProcessorWithOverriders(mode: KotlinSliceAnalysisMode = this@Slicer.mode) { + passToProcessor(mode) + + HierarchySearchRequest(this, analysisScope) + .searchOverriders() + .forEach { it.namedUnwrappedElement?.passToProcessor(mode) } + + if (this is KtCallableDeclaration && isExpectDeclaration()) { + resolveToDescriptorIfAny(BodyResolveMode.FULL) + ?.actualsForExpected() + ?.forEach { + (it as? DeclarationDescriptorWithSource)?.originalSource?.getPsi()?.passToProcessor(mode) + } + } + } + + protected fun KtDeclaration?.callMode(callElement: KtElement): KotlinSliceAnalysisMode { + return if (this is KtNamedFunction && hasInlineModifier()) + mode.withInlineFunctionCall(callElement, this) + else + mode + } + protected open fun processCalls( callable: KtCallableDeclaration, includeOverriders: Boolean, diff --git a/idea/testData/slicer/outflow/letResult.kt b/idea/testData/slicer/outflow/letResult.kt new file mode 100644 index 00000000000..360d6045a19 --- /dev/null +++ b/idea/testData/slicer/outflow/letResult.kt @@ -0,0 +1,18 @@ +// FLOW: OUT + +fun foo(p: String) { + val v1 = p.let { value -> bar(value) } + + val v2 = p.let { it } + + val v3 = p.let { + val it = "a" + it + } +} + +fun bar(s: String) = s + +inline fun T.let(block: (T) -> R): R { + return block(this) +} diff --git a/idea/testData/slicer/outflow/letResult.results.txt b/idea/testData/slicer/outflow/letResult.results.txt new file mode 100644 index 00000000000..37cc8f36c12 --- /dev/null +++ b/idea/testData/slicer/outflow/letResult.results.txt @@ -0,0 +1,35 @@ +3 fun foo(p: String) { +4 val v1 = p.let { value -> bar(value) } +16 (INLINE CALL let) inline fun T.let(block: (T) -> R): R { +17 (INLINE CALL let) return block(this) +16 (INLINE CALL let) [LAMBDA ARGUMENT IN] inline fun T.let(block: (T) -> R): R { +4 [LAMBDA ARGUMENT IN] val v1 = p.let { value -> bar(value) } +4 val v1 = p.let { value -> bar(value) } +4 val v1 = p.let { value -> bar(value) } +14 fun bar(s: String) = s +14 fun bar(s: String) = s +14 fun bar(s: String) = s +4 val v1 = p.let { value -> bar(value) } +4 val v1 = p.let { value -> bar(value) } +4 [LAMBDA CALLS] val v1 = p.let { value -> bar(value) } +16 (INLINE CALL let) [LAMBDA CALLS] inline fun T.let(block: (T) -> R): R { +17 (INLINE CALL let) return block(this) +4 val v1 = p.let { value -> bar(value) } +4 val v1 = p.let { value -> bar(value) } +6 val v2 = p.let { it } +16 (INLINE CALL let) inline fun T.let(block: (T) -> R): R { +17 (INLINE CALL let) return block(this) +16 (INLINE CALL let) [LAMBDA ARGUMENT IN] inline fun T.let(block: (T) -> R): R { +6 [LAMBDA ARGUMENT IN] val v2 = p.let { it } +6 val v2 = p.let { it } +6 val v2 = p.let { it } +6 [LAMBDA CALLS] val v2 = p.let { it } +16 (INLINE CALL let) [LAMBDA CALLS] inline fun T.let(block: (T) -> R): R { +17 (INLINE CALL let) return block(this) +6 val v2 = p.let { it } +6 val v2 = p.let { it } +8 val v3 = p.let { +16 (INLINE CALL let) inline fun T.let(block: (T) -> R): R { +17 (INLINE CALL let) return block(this) +16 (INLINE CALL let) [LAMBDA ARGUMENT IN] inline fun T.let(block: (T) -> R): R { +8 [LAMBDA ARGUMENT IN] val v3 = p.let { diff --git a/idea/testData/slicer/outflow/nonInlineLetResult.kt b/idea/testData/slicer/outflow/nonInlineLetResult.kt new file mode 100644 index 00000000000..df5d0a98d52 --- /dev/null +++ b/idea/testData/slicer/outflow/nonInlineLetResult.kt @@ -0,0 +1,18 @@ +// FLOW: OUT + +fun foo(p: String) { + val v1 = p.let { value -> bar(value) } + + val v2 = p.let { it } + + val v3 = p.let { + val it = "a" + it + } +} + +fun bar(s: String) = s + +fun T.let(block: (T) -> R): R { + return block(this) +} diff --git a/idea/testData/slicer/outflow/nonInlineLetResult.results.txt b/idea/testData/slicer/outflow/nonInlineLetResult.results.txt new file mode 100644 index 00000000000..a6edbba90f6 --- /dev/null +++ b/idea/testData/slicer/outflow/nonInlineLetResult.results.txt @@ -0,0 +1,33 @@ +3 fun foo(p: String) { +4 val v1 = p.let { value -> bar(value) } +16 fun T.let(block: (T) -> R): R { +17 return block(this) +16 [LAMBDA ARGUMENT IN] fun T.let(block: (T) -> R): R { +4 [LAMBDA ARGUMENT IN] val v1 = p.let { value -> bar(value) } +4 val v1 = p.let { value -> bar(value) } +4 val v1 = p.let { value -> bar(value) } +14 fun bar(s: String) = s +14 fun bar(s: String) = s +14 fun bar(s: String) = s +4 val v1 = p.let { value -> bar(value) } +4 val v1 = p.let { value -> bar(value) } +4 [LAMBDA CALLS] val v1 = p.let { value -> bar(value) } +16 [LAMBDA CALLS] fun T.let(block: (T) -> R): R { +17 return block(this) +16 fun T.let(block: (T) -> R): R { +4 val v1 = p.let { value -> bar(value) } +4 val v1 = p.let { value -> bar(value) } +6 val v2 = p.let { it } +6 val v2 = p.let { it } +8 val v3 = p.let { +8 val v3 = p.let { +6 [LAMBDA ARGUMENT IN] val v2 = p.let { it } +6 val v2 = p.let { it } +6 val v2 = p.let { it } +6 [LAMBDA CALLS] val v2 = p.let { it } +16 [LAMBDA CALLS] DUPLICATE: fun T.let(block: (T) -> R): R { +8 [LAMBDA ARGUMENT IN] val v3 = p.let { +6 val v2 = p.let { it } +16 DUPLICATE: fun T.let(block: (T) -> R): R { +8 val v3 = p.let { +16 DUPLICATE: fun T.let(block: (T) -> R): R { diff --git a/idea/tests/org/jetbrains/kotlin/idea/slicer/SlicerTreeTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/slicer/SlicerTreeTestGenerated.java index 55b083f1cd3..7571a9d8394 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/slicer/SlicerTreeTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/slicer/SlicerTreeTestGenerated.java @@ -623,6 +623,11 @@ public class SlicerTreeTestGenerated extends AbstractSlicerTreeTest { runTest("idea/testData/slicer/outflow/lambdaResultWithDirectCallViaAssignment.kt"); } + @TestMetadata("letResult.kt") + public void testLetResult() throws Exception { + runTest("idea/testData/slicer/outflow/letResult.kt"); + } + @TestMetadata("localVariableUsages.kt") public void testLocalVariableUsages() throws Exception { runTest("idea/testData/slicer/outflow/localVariableUsages.kt"); @@ -633,6 +638,11 @@ public class SlicerTreeTestGenerated extends AbstractSlicerTreeTest { runTest("idea/testData/slicer/outflow/memberPropertyUsages.kt"); } + @TestMetadata("nonInlineLetResult.kt") + public void testNonInlineLetResult() throws Exception { + runTest("idea/testData/slicer/outflow/nonInlineLetResult.kt"); + } + @TestMetadata("nonLocalReturn.kt") public void testNonLocalReturn() throws Exception { runTest("idea/testData/slicer/outflow/nonLocalReturn.kt");