Data flow to/from here: supported expected/actual declarations and extension receivers

This commit is contained in:
Valentin Kipyatkov
2020-04-09 22:01:22 +03:00
parent bfa3fb0589
commit 90188ef44f
78 changed files with 1704 additions and 801 deletions
@@ -124,6 +124,7 @@ import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationHighlightingT
import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationNavigationTest
import org.jetbrains.kotlin.idea.script.AbstractScriptDefinitionsOrderTest
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerLeafGroupingTest
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerMultiplatformTest
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerNullnessGroupingTest
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerTreeTest
import org.jetbrains.kotlin.idea.structureView.AbstractKotlinFileStructureTest
@@ -872,7 +873,7 @@ fun main(args: Array<String>) {
}
testClass<AbstractSlicerTreeTest> {
model("slicer", singleClass = true)
model("slicer", excludeDirs = listOf("mpp"))
}
testClass<AbstractSlicerLeafGroupingTest> {
@@ -882,6 +883,10 @@ fun main(args: Array<String>) {
testClass<AbstractSlicerNullnessGroupingTest> {
model("slicer/inflow", singleClass = true)
}
testClass<AbstractSlicerMultiplatformTest> {
model("slicer/mpp", recursive = false, extension = null)
}
}
testGroup("idea/idea-fir/tests", "idea/testData") {
@@ -126,6 +126,7 @@ import org.jetbrains.kotlin.idea.script.AbstractScriptDefinitionsOrderTest
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerLeafGroupingTest
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerNullnessGroupingTest
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerTreeTest
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerMultiplatformTest
import org.jetbrains.kotlin.idea.structureView.AbstractKotlinFileStructureTest
import org.jetbrains.kotlin.idea.stubs.AbstractMultiFileHighlightingTest
import org.jetbrains.kotlin.idea.stubs.AbstractResolveByStubTest
@@ -821,7 +822,7 @@ fun main(args: Array<String>) {
}
testClass<AbstractSlicerTreeTest> {
model("slicer", singleClass = true)
model("slicer", excludeDirs = listOf("mpp"))
}
testClass<AbstractSlicerLeafGroupingTest> {
@@ -831,6 +832,10 @@ fun main(args: Array<String>) {
testClass<AbstractSlicerNullnessGroupingTest> {
model("slicer/inflow", singleClass = true)
}
testClass<AbstractSlicerMultiplatformTest> {
model("slicer/mpp", recursive = false, extension = null)
}
}
testGroup("idea/scripting-support/test", "idea/scripting-support/testData") {
@@ -126,6 +126,7 @@ import org.jetbrains.kotlin.idea.script.AbstractScriptDefinitionsOrderTest
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerLeafGroupingTest
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerNullnessGroupingTest
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerTreeTest
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerMultiplatformTest
import org.jetbrains.kotlin.idea.structureView.AbstractKotlinFileStructureTest
import org.jetbrains.kotlin.idea.stubs.AbstractMultiFileHighlightingTest
import org.jetbrains.kotlin.idea.stubs.AbstractResolveByStubTest
@@ -821,7 +822,7 @@ fun main(args: Array<String>) {
}
testClass<AbstractSlicerTreeTest> {
model("slicer", singleClass = true)
model("slicer", excludeDirs = listOf("mpp"))
}
testClass<AbstractSlicerLeafGroupingTest> {
@@ -831,6 +832,10 @@ fun main(args: Array<String>) {
testClass<AbstractSlicerNullnessGroupingTest> {
model("slicer/inflow", singleClass = true)
}
testClass<AbstractSlicerMultiplatformTest> {
model("slicer/mpp", recursive = false, extension = null)
}
}
testGroup("idea/scripting-support/test", "idea/scripting-support/testData") {
@@ -126,6 +126,7 @@ import org.jetbrains.kotlin.idea.script.AbstractScriptDefinitionsOrderTest
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerLeafGroupingTest
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerNullnessGroupingTest
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerTreeTest
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerMultiplatformTest
import org.jetbrains.kotlin.idea.structureView.AbstractKotlinFileStructureTest
import org.jetbrains.kotlin.idea.stubs.AbstractMultiFileHighlightingTest
import org.jetbrains.kotlin.idea.stubs.AbstractResolveByStubTest
@@ -821,7 +822,7 @@ fun main(args: Array<String>) {
}
testClass<AbstractSlicerTreeTest> {
model("slicer", singleClass = true)
model("slicer", excludeDirs = listOf("mpp"))
}
testClass<AbstractSlicerLeafGroupingTest> {
@@ -831,6 +832,10 @@ fun main(args: Array<String>) {
testClass<AbstractSlicerNullnessGroupingTest> {
model("slicer/inflow", singleClass = true)
}
testClass<AbstractSlicerMultiplatformTest> {
model("slicer/mpp", recursive = false, extension = null)
}
}
testGroup("idea/scripting-support/test", "idea/scripting-support/testData") {
@@ -18,7 +18,9 @@ import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.*
import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.ReturnValueInstruction
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.traverse
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptorWithAccessors
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.*
@@ -30,6 +32,8 @@ import org.jetbrains.kotlin.idea.references.ReferenceAccess
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.isExpectDeclaration
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
@@ -45,23 +49,34 @@ import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.util.OperatorNameConventions
class InflowSlicer(
element: KtExpression,
element: KtElement,
processor: SliceUsageProcessor,
parentUsage: KotlinSliceUsage
) : Slicer(element, processor, parentUsage) {
override fun processChildren() {
if (parentUsage.forcedExpressionMode) {
processExpression(element)
(element as? KtExpression)?.let { processExpression(it) }
return
}
when (element) {
is KtProperty -> processProperty(element)
// for parameter, we include overriders only when the feature is invoked on parameter itself
// include overriders only when invoked on the parameter declaration
is KtParameter -> processParameter(parameter = element, includeOverriders = parentUsage.parent == null)
is KtDeclarationWithBody -> element.processBody()
else -> processExpression(element)
is KtTypeReference -> {
val parent = element.parent
require(parent is KtCallableDeclaration)
require(element == parent.receiverTypeReference)
// include overriders only when invoked on receiver type in the declaration
processExtensionReceiver(parent, includeOverriders = parentUsage.parent == null)
}
is KtExpression -> processExpression(element)
}
}
@@ -135,8 +150,9 @@ class InflowSlicer(
}
fun processCall(usageInfo: UsageInfo) {
val refElement = usageInfo.element ?: return
extractArgumentExpression(refElement)?.passToProcessorAsValue()
usageInfo.element?.let {
extractArgumentExpression(it)?.passToProcessorAsValue()
}
}
processCalls(function, analysisScope, includeOverriders, ::processCall)
@@ -147,6 +163,31 @@ class InflowSlicer(
}
}
private fun processExtensionReceiver(declaration: KtCallableDeclaration, includeOverriders: Boolean) {
fun extractReceiverExpression(refElement: PsiElement): PsiElement? {
return when (refElement) {
is KtExpression -> {
val resolvedCall = refElement.resolveToCall() ?: return null
//TODO: implicit receiver
val expressionReceiver = resolvedCall.extensionReceiver as? ExpressionReceiver ?: return null
return expressionReceiver.expression
}
else -> {
(refElement.parent as? PsiCall)?.argumentList?.expressions?.getOrNull(0)
}
}
}
fun processCall(usageInfo: UsageInfo) {
usageInfo.element?.let {
extractReceiverExpression(it)?.passToProcessorAsValue()
}
}
processCalls(declaration, analysisScope, includeOverriders, ::processCall)
}
private fun processExpression(expression: KtExpression) {
val lambda = when (expression) {
is KtLambdaExpression -> expression.functionLiteral
@@ -171,18 +212,30 @@ class InflowSlicer(
}
return
}
val accessedDescriptor = createdAt.target.accessedDescriptor ?: return
val accessedDeclaration = accessedDescriptor.originalSource.getPsi() ?: return
if (accessedDescriptor is SyntheticFieldDescriptor) {
val property = accessedDeclaration as? KtProperty ?: return
if (accessedDescriptor.propertyDescriptor.setter?.isDefault != false) {
property.processPropertyAssignments()
} else {
property.setter?.processBackingFieldAssignments()
val accessedDeclaration = accessedDescriptor.originalSource.getPsi()
when (accessedDescriptor) {
is SyntheticFieldDescriptor -> {
val property = accessedDeclaration as? KtProperty ?: return
if (accessedDescriptor.propertyDescriptor.setter?.isDefault != false) {
property.processPropertyAssignments()
} else {
property.setter?.processBackingFieldAssignments()
}
}
is ReceiverParameterDescriptor -> {
val callable = accessedDescriptor.containingDeclaration as? CallableDescriptor ?: return
val callableDeclaration = callable.originalSource.getPsi() as? KtCallableDeclaration ?: return
//TODO: what about non-extensions?
callableDeclaration.receiverTypeReference?.passToProcessor()
}
else -> {
accessedDeclaration?.passDeclarationToProcessorWithOverriders()
}
return
}
accessedDeclaration.passDeclarationToProcessorWithOverriders()
}
is MergeInstruction -> createdAt.passInputsToProcessor()
@@ -287,8 +340,17 @@ class InflowSlicer(
private fun PsiElement.passDeclarationToProcessorWithOverriders() {
passToProcessor()
HierarchySearchRequest(this, analysisScope)
.searchOverriders()
.forEach { it.namedUnwrappedElement?.passToProcessor() }
if (this is KtCallableDeclaration && isExpectDeclaration()) {
resolveToDescriptorIfAny(BodyResolveMode.FULL)
?.actualsForExpected()
?.forEach {
(it as? DeclarationDescriptorWithSource)?.originalSource?.getPsi()?.passToProcessor()
}
}
}
}
@@ -20,6 +20,7 @@ import com.intellij.codeInsight.Nullability
import com.intellij.ide.util.treeView.AbstractTreeStructure
import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.psi.PsiElement
import com.intellij.psi.util.parentOfType
import com.intellij.slicer.*
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.CallableDescriptor
@@ -30,6 +31,7 @@ import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.isPlainWithEscapes
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.psi2ir.deparenthesize
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.isError
@@ -73,26 +75,34 @@ class KotlinSliceProvider : SliceLanguageSupportProvider, SliceUsageTransformer
return listOf(KotlinSliceUsage(usage.element, usage.parent, 0, false))
}
override fun getExpressionAtCaret(atCaret: PsiElement, dataFlowToThis: Boolean): KtExpression? {
val element =
atCaret.parentsWithSelf
.firstOrNull {
it is KtProperty ||
it is KtParameter ||
it is KtDeclarationWithBody ||
(it is KtClass && !it.hasExplicitPrimaryConstructor()) ||
(it is KtExpression && it !is KtDeclaration)
}
?.let { KtPsiUtil.safeDeparenthesize(it as KtExpression) } ?: return null
override fun getExpressionAtCaret(atCaret: PsiElement, dataFlowToThis: Boolean): KtElement? {
val element = atCaret.parentsWithSelf
.filterIsInstance<KtElement>()
.firstOrNull(::isSliceElement)
?.deparenthesize() ?: return null
if (dataFlowToThis) {
if (element is KtConstantExpression) return null
if (element is KtStringTemplateExpression && element.isPlainWithEscapes()) return null
if (element is KtClassLiteralExpression) return null
if (element is KtCallableReferenceExpression) return null
}
return element
}
private fun isSliceElement(element: KtElement): Boolean {
return when {
element is KtProperty -> true
element is KtParameter -> true
element is KtDeclarationWithBody -> true
element is KtClass && !element.hasExplicitPrimaryConstructor() -> true
element is KtExpression && element !is KtDeclaration && element.parentOfType<KtTypeReference>() == null -> true
element is KtTypeReference && element == (element.parent as? KtCallableDeclaration)?.receiverTypeReference -> true
else -> false
}
}
override fun getElementForDescription(element: PsiElement): PsiElement {
return (element as? KtSimpleNameExpression)?.mainReference?.resolve() ?: element
}
@@ -21,6 +21,7 @@ import com.intellij.slicer.SliceAnalysisParams
import com.intellij.slicer.SliceUsage
import com.intellij.usageView.UsageInfo
import org.jetbrains.kotlin.idea.findUsages.handlers.SliceUsageProcessor
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtExpression
open class KotlinSliceUsage : SliceUsage {
@@ -58,10 +59,10 @@ open class KotlinSliceUsage : SliceUsage {
override fun canBeLeaf() = element != null && lambdaLevel == 0
public override fun processUsagesFlownDownTo(element: PsiElement, uniqueProcessor: SliceUsageProcessor) {
InflowSlicer(element as? KtExpression ?: return, uniqueProcessor, this).processChildren()
InflowSlicer(element as? KtElement ?: return, uniqueProcessor, this).processChildren()
}
public override fun processUsagesFlownFromThe(element: PsiElement, uniqueProcessor: SliceUsageProcessor) {
OutflowSlicer(element as? KtExpression ?: return, uniqueProcessor, this).processChildren()
OutflowSlicer(element as? KtElement ?: return, uniqueProcessor, this).processChildren()
}
}
@@ -12,33 +12,77 @@ import com.intellij.psi.impl.light.LightMemberReference
import com.intellij.usageView.UsageInfo
import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction
import org.jetbrains.kotlin.cfg.pseudocode.instructions.KtElementInstruction
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.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
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.isExpectDeclaration
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.source.getPsi
class OutflowSlicer(
element: KtExpression,
element: KtElement,
processor: SliceUsageProcessor,
parentUsage: KotlinSliceUsage
) : Slicer(element, processor, parentUsage) {
override fun processChildren() {
if (parentUsage.forcedExpressionMode) return processExpression(element)
if (parentUsage.forcedExpressionMode) {
(element as? KtExpression)?.let { processExpression(it) }
return
}
when (element) {
is KtProperty -> processVariable(element)
is KtParameter -> processVariable(element)
is KtFunction -> processFunction(element)
is KtPropertyAccessor -> if (element.isGetter) processVariable(element.property)
else -> processExpression(element)
is KtPropertyAccessor -> {
if (element.isGetter) {
processVariable(element.property)
}
}
is KtTypeReference -> {
val declaration = element.parent
require(declaration is KtCallableDeclaration)
require(element == declaration.receiverTypeReference)
if (declaration.isExpectDeclaration()) {
declaration.resolveToDescriptorIfAny(BodyResolveMode.FULL)
?.actualsForExpected()
?.forEach {
val actualDeclaration = (it as? DeclarationDescriptorWithSource)?.originalSource?.getPsi()
(actualDeclaration as? KtCallableDeclaration)?.receiverTypeReference?.passToProcessor()
}
}
when (declaration) {
is KtFunction -> {
processExtensionReceiver(declaration, declaration)
}
is KtProperty -> {
//TODO: process only one of them or both depending on the usage type
declaration.getter?.let { processExtensionReceiver(declaration, it) }
declaration.setter?.let { processExtensionReceiver(declaration, it) }
}
}
}
is KtExpression -> processExpression(element)
}
}
@@ -65,28 +109,37 @@ class OutflowSlicer(
if (variable is KtParameter) {
if (!canProcessParameter(variable)) return //TODO
//TODO: expect/actual
val callable = variable.ownerFunction as? KtCallableDeclaration
val parameterIndex = variable.parameterIndex()
callable?.forEachOverridingElement(scope = analysisScope) { _, overridingMember ->
when (overridingMember) {
is KtCallableDeclaration -> {
val parameters = overridingMember.valueParameters
check(parameters.size == callable.valueParameters.size)
parameters[parameterIndex].passToProcessor()
}
is PsiMethod -> {
val parameters = overridingMember.parameterList.parameters
val shift = if (callable.receiverTypeReference != null) 1 else 0
check(parameters.size == callable.valueParameters.size + shift)
parameters[parameterIndex + shift].passToProcessor()
}
else -> {} // not supported
if (callable != null) {
if (callable.isExpectDeclaration()) {
variable.resolveToDescriptorIfAny(BodyResolveMode.FULL)
?.actualsForExpected()
?.forEach {
(it as? DeclarationDescriptorWithSource)?.originalSource?.getPsi()?.passToProcessor()
}
}
val parameterIndex = variable.parameterIndex()
callable.forEachOverridingElement(scope = analysisScope) { _, overridingMember ->
when (overridingMember) {
is KtCallableDeclaration -> {
val parameters = overridingMember.valueParameters
check(parameters.size == callable.valueParameters.size)
parameters[parameterIndex].passToProcessor()
}
is PsiMethod -> {
val parameters = overridingMember.parameterList.parameters
val shift = if (callable.receiverTypeReference != null) 1 else 0
check(parameters.size == callable.valueParameters.size + shift)
parameters[parameterIndex + shift].passToProcessor()
}
else -> {} // not supported
}
true
}
true
}
}
@@ -113,31 +166,81 @@ class OutflowSlicer(
is KtNamedFunction -> function
else -> null
} ?: return
funExpression as PsiElement
funExpression.passToProcessor(parentUsage.lambdaLevel + 1, true)
}
private fun processExtensionReceiver(declaration: KtCallableDeclaration, declarationWithBody: KtDeclarationWithBody) {
//TODO: overriders
//TODO: implicit receivers
val resolutionFacade = declaration.getResolutionFacade()
val callableDescriptor = declaration.resolveToDescriptorIfAny(resolutionFacade) as? CallableDescriptor ?: return
val extensionReceiver = callableDescriptor.extensionReceiverParameter ?: return
declarationWithBody.bodyExpression?.forEachDescendantOfType<KtThisExpression> { thisExpression ->
val receiverDescriptor = thisExpression.resolveToCall(resolutionFacade)?.resultingDescriptor
if (receiverDescriptor == extensionReceiver) {
thisExpression.passToProcessor()
}
}
}
private fun processExpression(expression: KtExpression) {
expression.processPseudocodeUsages { pseudoValue, instr ->
when (instr) {
is WriteValueInstruction -> instr.target.accessedDescriptor?.originalSource?.getPsi()?.passToProcessor()
is CallInstruction -> {
if (parentUsage.lambdaLevel > 0 && instr.receiverValues[pseudoValue] != null) {
instr.element.passToProcessor(parentUsage.lambdaLevel - 1)
} else {
instr.arguments[pseudoValue]?.originalSource?.getPsi()?.passToProcessor()
expression.processPseudocodeUsages { pseudoValue, instruction ->
when (instruction) {
is WriteValueInstruction -> {
if (!processIfReceiverValue(instruction, pseudoValue)) {
instruction.target.accessedDescriptor?.originalSource?.getPsi()?.passToProcessor()
}
}
is ReturnValueInstruction -> instr.subroutine.passToProcessor()
is MagicInstruction -> when (instr.kind) {
MagicKind.NOT_NULL_ASSERTION, MagicKind.CAST -> instr.outputValue.element?.passToProcessor()
else -> {
is ReadValueInstruction -> {
processIfReceiverValue(instruction, pseudoValue)
}
is CallInstruction -> {
if (!processIfReceiverValue(instruction, pseudoValue)) {
instruction.arguments[pseudoValue]?.originalSource?.getPsi()?.passToProcessor()
}
}
is ReturnValueInstruction -> {
instruction.subroutine.passToProcessor()
}
is MagicInstruction -> {
when (instruction.kind) {
MagicKind.NOT_NULL_ASSERTION, MagicKind.CAST -> instruction.outputValue.element?.passToProcessor()
else -> {
}
}
}
}
}
}
private fun processIfReceiverValue(instruction: KtElementInstruction, pseudoValue: PseudoValue): Boolean {
val receiverValue = (instruction as? InstructionWithReceivers)?.receiverValues?.get(pseudoValue) ?: return false
val resolvedCall = instruction.element.resolveToCall() ?: return true
when (resolvedCall.call.callType) {
Call.CallType.DEFAULT -> {
if (receiverValue == resolvedCall.extensionReceiver) {
val targetDeclaration = resolvedCall.resultingDescriptor.originalSource.getPsi()
(targetDeclaration as? KtCallableDeclaration)?.receiverTypeReference?.passToProcessor()
}
}
Call.CallType.INVOKE -> {
if (parentUsage.lambdaLevel > 0 && receiverValue == resolvedCall.dispatchReceiver) {
instruction.element.passToProcessor(parentUsage.lambdaLevel - 1)
}
}
else -> {
//TODO
}
}
return true
}
private fun PsiElement.getCallElementForExactCallee(): PsiElement? {
if (this is KtArrayAccessExpression) return this
@@ -10,6 +10,7 @@ import com.intellij.psi.PsiMethod
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.cfg.pseudocode.Pseudocode
import org.jetbrains.kotlin.cfg.pseudocode.containingDeclarationForPseudocode
import org.jetbrains.kotlin.cfg.pseudocode.getContainingPseudocode
@@ -17,21 +18,22 @@ import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
import org.jetbrains.kotlin.descriptors.SourceElement
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.core.getDeepestSuperDeclarations
import org.jetbrains.kotlin.idea.findUsages.KotlinFunctionFindUsagesOptions
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.util.expectedDescriptor
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.contains
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.source.getPsi
import java.util.*
abstract class Slicer(
protected val element: KtExpression,
protected val element: KtElement,
protected val processor: SliceUsageProcessor,
protected val parentUsage: KotlinSliceUsage
) {
@@ -62,22 +64,43 @@ abstract class Slicer(
}
protected fun processCalls(
function: KtFunction,
callable: KtCallableDeclaration,
scope: SearchScope,
includeOverriders: Boolean,
usageProcessor: (UsageInfo) -> Unit
) {
val options = KotlinFunctionFindUsagesOptions(project).apply {
isSearchForTextOccurrences = false
isSkipImportStatements = true
searchScope = scope
val options = when (callable) {
is KtFunction -> {
KotlinFunctionFindUsagesOptions(project).apply {
isSearchForTextOccurrences = false
isSkipImportStatements = true
searchScope = scope
}
}
is KtProperty -> {
KotlinPropertyFindUsagesOptions(project).apply {
isSearchForTextOccurrences = false
isSkipImportStatements = true
searchScope = scope
}
}
else -> return
}
val descriptor = function.unsafeResolveToDescriptor() as? CallableMemberDescriptor ?: return
val superDescriptors = if (includeOverriders)
val descriptor = callable.resolveToDescriptorIfAny(BodyResolveMode.FULL) as? CallableMemberDescriptor ?: return
val superDescriptors = if (includeOverriders) {
descriptor.getDeepestSuperDeclarations()
else
listOf(descriptor) + DescriptorUtils.getAllOverriddenDeclarations(descriptor)
} else {
mutableListOf<CallableMemberDescriptor>().apply {
add(descriptor)
addAll(DescriptorUtils.getAllOverriddenDeclarations(descriptor))
if (descriptor.isActual) {
addIfNotNull(descriptor.expectedDescriptor() as CallableMemberDescriptor?)
}
}
}
for (superDescriptor in superDescriptors) {
val declaration = superDescriptor.originalSource.getPsi() ?: continue
@@ -123,11 +146,17 @@ abstract class Slicer(
}
val allDeclarations = mutableListOf(declaration)
val descriptor = declaration.unsafeResolveToDescriptor()
val descriptor = declaration.resolveToDescriptorIfAny()
if (descriptor is CallableMemberDescriptor) {
DescriptorUtils.getAllOverriddenDeclarations(descriptor).mapNotNullTo(allDeclarations) {
val additionalDescriptors = if (descriptor.isActual) {
listOfNotNull(descriptor.expectedDescriptor() as? CallableMemberDescriptor)
} else {
DescriptorUtils.getAllOverriddenDeclarations(descriptor)
}
additionalDescriptors.mapNotNullTo(allDeclarations) {
it.originalSource.getPsi() as? KtCallableDeclaration
}
}
allDeclarations.forEach { it.processAllExactUsages(options, usageProcessor) }
@@ -0,0 +1,5 @@
class JavaClass {
void f() {
KotlinUtil.extensionFun("x");
}
}
+13
View File
@@ -0,0 +1,13 @@
// FLOW: IN
@file: JvmName("KotlinUtil")
import kotlin.jvm.JvmName
fun <caret>Any.extensionFun() {
}
fun foo() {
"".extensionFun()
1.extensionFun()
}
@@ -0,0 +1,12 @@
11 <bold>""</bold>.extensionFun()
7 fun <bold>Any</bold>.extensionFun() {
11 <bold>""</bold>.extensionFun()
3 KotlinUtil.extensionFun(<bold>"x"</bold>);
7 fun <bold>Any</bold>.extensionFun() {
3 KotlinUtil.extensionFun(<bold>"x"</bold>);
12 <bold>1</bold>.extensionFun()
7 fun <bold>Any</bold>.extensionFun() {
12 <bold>1</bold>.extensionFun()
@@ -0,0 +1,13 @@
[NotNull Values]
11 <bold>""</bold>.extensionFun()
7 fun <bold>Any</bold>.extensionFun() {
11 <bold>""</bold>.extensionFun()
12 <bold>1</bold>.extensionFun()
7 fun <bold>Any</bold>.extensionFun() {
12 <bold>1</bold>.extensionFun()
[Other Values]
3 KotlinUtil.extensionFun(<bold>"x"</bold>);
7 fun <bold>Any</bold>.extensionFun() {
3 KotlinUtil.extensionFun(<bold>"x"</bold>);
@@ -0,0 +1,4 @@
7 fun <bold>Any</bold>.extensionFun() {
3 KotlinUtil.extensionFun(<bold>"x"</bold>);
11 <bold>""</bold>.extensionFun()
12 <bold>1</bold>.extensionFun()
+10
View File
@@ -0,0 +1,10 @@
// FLOW: IN
var <caret>Any.property: Int
get() = 1
set(value) { }
fun foo() {
println("a".property)
"b".property = 2
}
@@ -0,0 +1,7 @@
9 <bold>"b"</bold>.property = 2
3 var <bold>Any</bold>.property: Int
9 <bold>"b"</bold>.property = 2
8 println(<bold>"a"</bold>.property)
3 var <bold>Any</bold>.property: Int
8 println(<bold>"a"</bold>.property)
@@ -0,0 +1,7 @@
[NotNull Values]
8 println(<bold>"a"</bold>.property)
3 var <bold>Any</bold>.property: Int
8 println(<bold>"a"</bold>.property)
9 <bold>"b"</bold>.property = 2
3 var <bold>Any</bold>.property: Int
9 <bold>"b"</bold>.property = 2
@@ -0,0 +1,3 @@
3 var <bold>Any</bold>.property: Int
8 println(<bold>"a"</bold>.property)
9 <bold>"b"</bold>.property = 2
+20
View File
@@ -0,0 +1,20 @@
// FLOW: IN
open class Base {
open fun Int.extensionFun() {
println(<caret>this)
}
fun baseF() {
1.extensionFun()
}
}
class Derived : Base() {
override fun Int.extensionFun() {
}
fun derivedF() {
2.extensionFun()
}
}
@@ -0,0 +1,4 @@
9 <bold>1</bold>.extensionFun()
5 println(<bold>this</bold>)
4 open fun <bold>Int</bold>.extensionFun() {
9 <bold>1</bold>.extensionFun()
@@ -0,0 +1,3 @@
[NotNull Values]
5 println(<bold>this</bold>)
5 println(<bold>this</bold>)
@@ -0,0 +1,3 @@
5 println(<bold>this</bold>)
4 open fun <bold>Int</bold>.extensionFun() {
9 <bold>1</bold>.extensionFun()
+20
View File
@@ -0,0 +1,20 @@
// FLOW: IN
open class Base {
open fun <caret>Int.extensionFun() {
println(this)
}
fun baseF() {
1.extensionFun()
}
}
class Derived : Base() {
override fun Int.extensionFun() {
}
fun derivedF() {
2.extensionFun()
}
}
@@ -0,0 +1,7 @@
9 <bold>1</bold>.extensionFun()
4 open fun <bold>Int</bold>.extensionFun() {
9 <bold>1</bold>.extensionFun()
18 <bold>2</bold>.extensionFun()
4 open fun <bold>Int</bold>.extensionFun() {
18 <bold>2</bold>.extensionFun()
@@ -0,0 +1,7 @@
[NotNull Values]
9 <bold>1</bold>.extensionFun()
4 open fun <bold>Int</bold>.extensionFun() {
9 <bold>1</bold>.extensionFun()
18 <bold>2</bold>.extensionFun()
4 open fun <bold>Int</bold>.extensionFun() {
18 <bold>2</bold>.extensionFun()
@@ -0,0 +1,3 @@
4 open fun <bold>Int</bold>.extensionFun() {
9 <bold>1</bold>.extensionFun()
18 <bold>2</bold>.extensionFun()
@@ -0,0 +1,9 @@
// FLOW: IN
fun Any.extensionFun(): Any {
return this
}
fun foo() {
val <caret>x = "".extensionFun()
}
@@ -0,0 +1,8 @@
8 val x = <bold>""</bold>.extensionFun()
8 val <bold>x = "".extensionFun()</bold>
8 val x = <bold>"".extensionFun()</bold>
3 fun Any.<bold>extensionFun(): Any {</bold>
4 return <bold>this</bold>
3 fun <bold>Any</bold>.extensionFun(): Any {
8 val x = <bold>""</bold>.extensionFun()
@@ -0,0 +1,3 @@
[NotNull Values]
8 val <bold>x = "".extensionFun()</bold>
8 val <bold>x = "".extensionFun()</bold>
@@ -0,0 +1,6 @@
8 val <bold>x = "".extensionFun()</bold>
8 val x = <bold>"".extensionFun()</bold>
3 fun Any.<bold>extensionFun(): Any {</bold>
4 return <bold>this</bold>
3 fun <bold>Any</bold>.extensionFun(): Any {
8 val x = <bold>""</bold>.extensionFun()
@@ -0,0 +1,8 @@
// FLOW: IN
val Any.extensionProp: Any
get() = this
fun foo() {
val <caret>x = "".extensionProp
}
@@ -0,0 +1,8 @@
7 val x = <bold>""</bold>.extensionProp
7 val <bold>x = "".extensionProp</bold>
7 val x = <bold>"".extensionProp</bold>
3 val Any.<bold>extensionProp: Any</bold>
4 get() = <bold>this</bold>
3 val <bold>Any</bold>.extensionProp: Any
7 val x = <bold>""</bold>.extensionProp
@@ -0,0 +1,3 @@
[NotNull Values]
7 val <bold>x = "".extensionProp</bold>
7 val <bold>x = "".extensionProp</bold>
@@ -0,0 +1,6 @@
7 val <bold>x = "".extensionProp</bold>
7 val x = <bold>"".extensionProp</bold>
3 val Any.<bold>extensionProp: Any</bold>
4 get() = <bold>this</bold>
3 val <bold>Any</bold>.extensionProp: Any
7 val x = <bold>""</bold>.extensionProp
@@ -0,0 +1,9 @@
package sample
expect class ExpectClass() {
fun foo(p: Any)
}
fun f(c: ExpectClass) {
c.foo(1)
}
@@ -0,0 +1,9 @@
// FLOW: IN
package sample
actual class ExpectClass {
actual fun foo(p: Any) {
println(<caret>p)
}
}
@@ -0,0 +1,3 @@
7 println(<bold>p</bold>)
6 actual fun foo(<bold>p: Any</bold>) {
8 c.foo(<bold>1</bold>)
@@ -0,0 +1,5 @@
expect fun foo(p: Any)
fun bar() {
foo(1)
}
@@ -0,0 +1,5 @@
// FLOW: IN
actual fun foo(p: Any) {
println(<caret>p)
}
@@ -0,0 +1,3 @@
4 println(<bold>p</bold>)
3 actual fun foo(<bold>p: Any</bold>) {
4 foo(<bold>1</bold>)
@@ -0,0 +1,11 @@
// FLOW: OUT
package sample
expect class ExpectClass() {
fun foo(p: Any)
}
fun f(c: ExpectClass) {
c.foo(<caret>1)
}
@@ -0,0 +1,7 @@
package sample
actual class ExpectClass {
actual fun foo(p: Any) {
println(p)
}
}
@@ -0,0 +1,4 @@
10 c.foo(<bold>1</bold>)
6 fun foo(<bold>p: Any</bold>)
4 actual fun foo(<bold>p: Any</bold>) {
5 println(<bold>p</bold>)
@@ -0,0 +1,8 @@
// FLOW: OUT
expect fun Any.foo(): Any
fun bar() {
val result = <caret>1.foo()
println(result)
}
@@ -0,0 +1,3 @@
actual fun Any.foo(): Any {
return this
}
@@ -0,0 +1,8 @@
6 val result = <bold>1</bold>.foo()
3 expect fun <bold>Any</bold>.foo(): Any
1 actual fun <bold>Any</bold>.foo(): Any {
2 return <bold>this</bold>
1 actual fun Any.<bold>foo(): Any {</bold>
6 val result = 1.<bold>foo()</bold>
6 val <bold>result = 1.foo()</bold>
7 println(<bold>result</bold>)
@@ -0,0 +1,7 @@
// FLOW: OUT
expect fun foo(p: Any)
fun bar() {
foo(<caret>1)
}
@@ -0,0 +1,3 @@
actual fun foo(p: Any) {
println(p)
}
@@ -0,0 +1,4 @@
6 foo(<bold>1</bold>)
3 expect fun foo(<bold>p: Any</bold>)
1 actual fun foo(<bold>p: Any</bold>) {
2 println(<bold>p</bold>)
@@ -0,0 +1,8 @@
// FLOW: IN
expect fun foo(p: Any): Any
fun bar() {
val result = foo(1)
println(<caret>result)
}
@@ -0,0 +1,3 @@
actual fun foo(p: Any): Any {
return p
}
@@ -0,0 +1,8 @@
7 println(<bold>result</bold>)
6 val <bold>result = foo(1)</bold>
6 val result = <bold>foo(1)</bold>
1 actual fun <bold>foo(p: Any): Any {</bold>
2 return <bold>p</bold>
1 actual fun foo(<bold>p: Any</bold>): Any {
6 val result = foo(<bold>1</bold>)
3 expect fun <bold>foo(p: Any): Any</bold>
@@ -0,0 +1,8 @@
// FLOW: OUT
expect fun foo(p: Any): Any
fun bar() {
val result = foo(<caret>1)
println(result)
}
@@ -0,0 +1,3 @@
actual fun foo(p: Any): Any {
return p
}
@@ -0,0 +1,8 @@
6 val result = foo(<bold>1</bold>)
3 expect fun foo(<bold>p: Any</bold>): Any
1 actual fun foo(<bold>p: Any</bold>): Any {
2 return <bold>p</bold>
1 actual fun <bold>foo(p: Any): Any {</bold>
6 val result = <bold>foo(1)</bold>
6 val <bold>result = foo(1)</bold>
7 println(<bold>result</bold>)
@@ -0,0 +1,8 @@
// FLOW: IN
expect val Int.property: Any
fun bar() {
val result = 1.property
println(<caret>result)
}
@@ -0,0 +1,2 @@
actual val Int.property: Any
get() = this
@@ -0,0 +1,8 @@
7 println(<bold>result</bold>)
6 val <bold>result = 1.property</bold>
6 val result = <bold>1.property</bold>
1 actual val Int.<bold>property: Any</bold>
2 get() = <bold>this</bold>
1 actual val <bold>Int</bold>.property: Any
6 val result = <bold>1</bold>.property
3 expect val Int.<bold>property: Any</bold>
@@ -0,0 +1,8 @@
// FLOW: OUT
expect val Int.property: Any
fun bar() {
val result = <caret>1.property
println(result)
}
@@ -0,0 +1,2 @@
actual val Int.property: Any
get() = this
@@ -0,0 +1,8 @@
6 val result = <bold>1</bold>.property
3 expect val <bold>Int</bold>.property: Any
1 actual val <bold>Int</bold>.property: Any
2 get() = <bold>this</bold>
2 <bold>get() = this</bold>
6 val result = 1.<bold>property</bold>
6 val <bold>result = 1.property</bold>
7 println(<bold>result</bold>)
@@ -5,5 +5,18 @@
22 DEREFERENCE: <bold>x</bold> += 1
23 DEREFERENCE: <bold>x</bold> *= 1
25 -<bold>x</bold>
12 operator fun <bold>A</bold>.unaryMinus() = this
12 operator fun A.unaryMinus() = <bold>this</bold>
12 operator fun A.<bold>unaryMinus() = this</bold>
25 <bold>-x</bold>
26 <bold>x</bold> - 1
11 operator fun <bold>A</bold>.minus(n: Int) = this
11 operator fun A.minus(n: Int) = <bold>this</bold>
11 operator fun A.<bold>minus(n: Int) = this</bold>
26 <bold>x - 1</bold>
28 <bold>x -= 1</bold>
17 DUPLICATE: var <bold>x = A()</bold>
29 <bold>x</bold> /= 1
14 operator fun <bold>A</bold>.divAssign(n: Int) = this
14 operator fun A.divAssign(n: Int) = <bold>this</bold>
14 operator fun A.<bold>divAssign(n: Int) = this</bold>
@@ -2,10 +2,14 @@
15 DEREFERENCE: <bold>x</bold>.foo()
16 DEREFERENCE: <bold>x</bold>.bar
17 <bold>x</bold>.fooExt()
9 fun <bold>A</bold>.fooExt() = 1
18 <bold>x</bold>.barExt
10 val <bold>A</bold>.barExt: Int get() = 2
20 val y: A? = <bold>x</bold>
20 val <bold>y: A? = x</bold>
22 DEREFERENCE: <bold>y</bold>?.foo()
23 DEREFERENCE: <bold>y</bold>?.bar
24 <bold>y</bold>?.fooExt()
9 DUPLICATE: fun <bold>A</bold>.fooExt() = 1
25 <bold>y</bold>?.barExt
10 DUPLICATE: val <bold>A</bold>.barExt: Int get() = 2
@@ -0,0 +1,7 @@
// FLOW: OUT
fun String.extensionFun(): Any = this
fun foo() {
val x = <caret>"".extensionFun()
}
@@ -0,0 +1,6 @@
6 val x = <bold>""</bold>.extensionFun()
3 fun <bold>String</bold>.extensionFun(): Any = this
3 fun String.extensionFun(): Any = <bold>this</bold>
3 fun String.<bold>extensionFun(): Any = this</bold>
6 val x = "".<bold>extensionFun()</bold>
6 val <bold>x = "".extensionFun()</bold>
@@ -0,0 +1,8 @@
// FLOW: OUT
val String.extensionProp: Any
get() = this
fun foo() {
val x = <caret>"".extensionProp
}
@@ -0,0 +1,6 @@
7 val x = <bold>""</bold>.extensionProp
3 val <bold>String</bold>.extensionProp: Any
4 get() = <bold>this</bold>
4 <bold>get() = this</bold>
7 val x = "".<bold>extensionProp</bold>
7 val <bold>x = "".extensionProp</bold>
+19
View File
@@ -0,0 +1,19 @@
// FLOW: OUT
class C {
fun String.extensionFun(): Any {
with("A") {
println(this.length)
println(this@extensionFun.length)
}
return this@C
}
fun foo() {
val x = <caret>"".extensionFun()
}
}
inline fun <T, R> with(receiver: T, block: T.() -> R): R {
return receiver.block()
}
+3
View File
@@ -0,0 +1,3 @@
13 val x = <bold>""</bold>.extensionFun()
4 fun <bold>String</bold>.extensionFun(): Any {
7 println(<bold>this@extensionFun</bold>.length)
@@ -11,7 +11,7 @@ import java.io.File
abstract class AbstractSlicerLeafGroupingTest : AbstractSlicerTest() {
override fun doTest(path: String, sliceProvider: KotlinSliceProvider, rootNode: SliceRootNode) {
val treeStructure = SliceTreeStructure(rootNode)
val treeStructure = TestSliceTreeStructure(rootNode)
val analyzer = sliceProvider.leafAnalyzer
val possibleElementsByNode = analyzer.createMap()
val leafExpressions = analyzer.calcLeafExpressions(rootNode, treeStructure, possibleElementsByNode)
@@ -0,0 +1,35 @@
/*
* 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 com.intellij.psi.PsiDocumentManager
import org.jetbrains.annotations.NotNull
import org.jetbrains.kotlin.idea.multiplatform.setupMppProjectFromDirStructure
import org.jetbrains.kotlin.idea.stubs.AbstractMultiModuleTest
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.idea.test.extractMarkerOffset
import org.jetbrains.kotlin.idea.test.findFileWithCaret
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
abstract class AbstractSlicerMultiplatformTest : AbstractMultiModuleTest() {
override fun getTestDataPath() =
File(PluginTestCaseBase.getTestDataPathBase(), "/slicer/mpp").path + File.separator
protected fun doTest(filePath: String) {
val testRoot = File(filePath)
setupMppProjectFromDirStructure(testRoot)
val file = project.findFileWithCaret() as KtFile
val document = PsiDocumentManager.getInstance(myProject).getDocument(file)!!
val offset = document.extractMarkerOffset(project, "<caret>")
testSliceFromOffset(file, offset) { _, rootNode ->
KotlinTestUtils.assertEqualsToFile(testRoot.resolve("results.txt"), buildTreeRepresentation(rootNode))
}
}
}
@@ -12,7 +12,7 @@ import java.io.File
abstract class AbstractSlicerNullnessGroupingTest : AbstractSlicerTest() {
override fun doTest(path: String, sliceProvider: KotlinSliceProvider, rootNode: SliceRootNode) {
val treeStructure = SliceTreeStructure(rootNode)
val treeStructure = TestSliceTreeStructure(rootNode)
val analyzer = sliceProvider.nullnessAnalyzer
val nullnessByNode = SliceNullnessAnalyzerBase.createMap()
val nullness = analyzer.calcNullableLeaves(rootNode, treeStructure, nullnessByNode)
@@ -5,96 +5,15 @@
package org.jetbrains.kotlin.idea.slicer
import com.intellij.analysis.AnalysisScope
import com.intellij.ide.projectView.TreeStructureProvider
import com.intellij.ide.util.treeView.AbstractTreeStructureBase
import com.intellij.openapi.util.io.FileUtil
import com.intellij.slicer.DuplicateMap
import com.intellij.slicer.SliceAnalysisParams
import com.intellij.slicer.SliceNode
import com.intellij.slicer.SliceRootNode
import com.intellij.usages.TextChunk
import com.intellij.util.PathUtil
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.awt.Font
import java.io.File
abstract class AbstractSlicerTest : KotlinLightCodeInsightFixtureTestCase() {
protected class SliceTreeStructure(private val rootNode: SliceNode) : AbstractTreeStructureBase(rootNode.project) {
override fun getProviders(): List<TreeStructureProvider>? = emptyList()
override fun getRootElement() = rootNode
override fun commit() {
}
override fun hasSomethingToCommit() = false
}
companion object {
private val SliceNode.sortedChildren: List<SliceNode>
get() = children.sortedBy { it.value.element?.startOffset ?: -1 }
@JvmStatic
protected fun buildTreeRepresentation(rootNode: SliceNode): String {
fun TextChunk.render(): String {
var text = text
if (attributes.fontType == Font.BOLD) {
text = "<bold>$text</bold>"
}
return text
}
fun SliceNode.isSliceLeafValueClassNode() = javaClass.name == "com.intellij.slicer.SliceLeafValueClassNode"
fun process(node: SliceNode, indent: Int): String {
val usage = node.element!!.value
node.calculateDupNode()
val isDuplicated = !node.isSliceLeafValueClassNode() && node.duplicate != null
return buildString {
when {
node is SliceRootNode && usage.element is KtFile -> {
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() + " ")
append("\t".repeat(indent))
if (usage is KotlinSliceDereferenceUsage) {
append("DEREFERENCE: ")
}
if (usage is KotlinSliceUsage) {
append("[LAMBDA] ".repeat(usage.lambdaLevel))
}
chunks.slice(1 until chunks.size).joinTo(
this,
separator = "",
prefix = if (isDuplicated) "DUPLICATE: " else "",
postfix = "\n"
) { it.render() }
}
}
if (!isDuplicated) {
node.sortedChildren.forEach { append(process(it, indent + 1)) }
}
}.replace(Regex("</bold><bold>"), "")
}
return process(rootNode, 0)
}
}
protected abstract fun doTest(path: String, sliceProvider: KotlinSliceProvider, rootNode: SliceRootNode)
protected fun doTest(path: String) {
@@ -104,30 +23,15 @@ abstract class AbstractSlicerTest : KotlinLightCodeInsightFixtureTestCase() {
val namePrefix = FileUtil.getNameWithoutExtension(mainFile)
val extraFiles = rootDir.listFiles { _, name ->
name.startsWith("$namePrefix.") && PathUtil.getFileExtension(name).let { it == "kt" || it == "java" }
}
}!!
myFixture.testDataPath = "${KotlinTestUtils.getHomeDirectory()}/${rootDir.path}"
val fileText = FileUtil.loadFile(mainFile, true)
val flowKind = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// FLOW: ")
val withDereferences = InTextDirectivesUtils.isDirectiveDefined(fileText, "// WITH_DEREFERENCES")
val analysisParams = SliceAnalysisParams().apply {
dataFlowToThis = when (flowKind) {
"IN" -> true
"OUT" -> false
else -> throw AssertionError("Invalid flow kind: $flowKind")
}
showInstanceDereferences = withDereferences
scope = AnalysisScope(project)
}
extraFiles.forEach { myFixture.configureByFile(it.name) }
val file = myFixture.configureByFile(mainFile.name) as KtFile
val elementAtCaret = file.findElementAt(editor.caretModel.offset)!!
val sliceProvider = KotlinSliceProvider()
val expression = sliceProvider.getExpressionAtCaret(elementAtCaret, analysisParams.dataFlowToThis)!!
val rootUsage = sliceProvider.createRootUsage(expression, analysisParams)
val rootNode = SliceRootNode(project, DuplicateMap(), rootUsage)
doTest(path, sliceProvider, rootNode)
testSliceFromOffset(file, editor.caretModel.offset) { sliceProvider, rootNode ->
doTest(path, sliceProvider, rootNode)
}
}
}
@@ -228,6 +228,16 @@ public class SlicerLeafGroupingTestGenerated extends AbstractSlicerLeafGroupingT
runTest("idea/testData/slicer/inflow/nullsAndNotNulls.kt");
}
@TestMetadata("onFunctionReceiverType.kt")
public void testOnFunctionReceiverType() throws Exception {
runTest("idea/testData/slicer/inflow/onFunctionReceiverType.kt");
}
@TestMetadata("onPropertyReceiverType.kt")
public void testOnPropertyReceiverType() throws Exception {
runTest("idea/testData/slicer/inflow/onPropertyReceiverType.kt");
}
@TestMetadata("openFun.kt")
public void testOpenFun() throws Exception {
runTest("idea/testData/slicer/inflow/openFun.kt");
@@ -238,6 +248,16 @@ public class SlicerLeafGroupingTestGenerated extends AbstractSlicerLeafGroupingT
runTest("idea/testData/slicer/inflow/openFunInvokeOnParameter.kt");
}
@TestMetadata("overrideExtension1.kt")
public void testOverrideExtension1() throws Exception {
runTest("idea/testData/slicer/inflow/overrideExtension1.kt");
}
@TestMetadata("overrideExtension2.kt")
public void testOverrideExtension2() throws Exception {
runTest("idea/testData/slicer/inflow/overrideExtension2.kt");
}
@TestMetadata("overrideFun.kt")
public void testOverrideFun() throws Exception {
runTest("idea/testData/slicer/inflow/overrideFun.kt");
@@ -323,6 +343,16 @@ public class SlicerLeafGroupingTestGenerated extends AbstractSlicerLeafGroupingT
runTest("idea/testData/slicer/inflow/settersViaJavaDelegate.kt");
}
@TestMetadata("thisInExtensionFunction.kt")
public void testThisInExtensionFunction() throws Exception {
runTest("idea/testData/slicer/inflow/thisInExtensionFunction.kt");
}
@TestMetadata("thisInExtensionProperty.kt")
public void testThisInExtensionProperty() throws Exception {
runTest("idea/testData/slicer/inflow/thisInExtensionProperty.kt");
}
@TestMetadata("topLevelVal.kt")
public void testTopLevelVal() throws Exception {
runTest("idea/testData/slicer/inflow/topLevelVal.kt");
@@ -0,0 +1,75 @@
/*
* 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 com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/testData/slicer/mpp")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class SlicerMultiplatformTestGenerated extends AbstractSlicerMultiplatformTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("actualClassFunctionParameter")
public void testActualClassFunctionParameter() throws Exception {
runTest("idea/testData/slicer/mpp/actualClassFunctionParameter/");
}
@TestMetadata("actualFunctionParameter")
public void testActualFunctionParameter() throws Exception {
runTest("idea/testData/slicer/mpp/actualFunctionParameter/");
}
public void testAllFilesPresentInMpp() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/slicer/mpp"), Pattern.compile("^([^\\.]+)$"), null, false);
}
@TestMetadata("expectClassFunctionParameter")
public void testExpectClassFunctionParameter() throws Exception {
runTest("idea/testData/slicer/mpp/expectClassFunctionParameter/");
}
@TestMetadata("expectExtensionFunctionResultOut")
public void testExpectExtensionFunctionResultOut() throws Exception {
runTest("idea/testData/slicer/mpp/expectExtensionFunctionResultOut/");
}
@TestMetadata("expectFunctionParameter")
public void testExpectFunctionParameter() throws Exception {
runTest("idea/testData/slicer/mpp/expectFunctionParameter/");
}
@TestMetadata("expectFunctionResultIn")
public void testExpectFunctionResultIn() throws Exception {
runTest("idea/testData/slicer/mpp/expectFunctionResultIn/");
}
@TestMetadata("expectFunctionResultOut")
public void testExpectFunctionResultOut() throws Exception {
runTest("idea/testData/slicer/mpp/expectFunctionResultOut/");
}
@TestMetadata("expectPropertyResultIn")
public void testExpectPropertyResultIn() throws Exception {
runTest("idea/testData/slicer/mpp/expectPropertyResultIn/");
}
@TestMetadata("expectPropertyResultOut")
public void testExpectPropertyResultOut() throws Exception {
runTest("idea/testData/slicer/mpp/expectPropertyResultOut/");
}
}
@@ -228,6 +228,16 @@ public class SlicerNullnessGroupingTestGenerated extends AbstractSlicerNullnessG
runTest("idea/testData/slicer/inflow/nullsAndNotNulls.kt");
}
@TestMetadata("onFunctionReceiverType.kt")
public void testOnFunctionReceiverType() throws Exception {
runTest("idea/testData/slicer/inflow/onFunctionReceiverType.kt");
}
@TestMetadata("onPropertyReceiverType.kt")
public void testOnPropertyReceiverType() throws Exception {
runTest("idea/testData/slicer/inflow/onPropertyReceiverType.kt");
}
@TestMetadata("openFun.kt")
public void testOpenFun() throws Exception {
runTest("idea/testData/slicer/inflow/openFun.kt");
@@ -238,6 +248,16 @@ public class SlicerNullnessGroupingTestGenerated extends AbstractSlicerNullnessG
runTest("idea/testData/slicer/inflow/openFunInvokeOnParameter.kt");
}
@TestMetadata("overrideExtension1.kt")
public void testOverrideExtension1() throws Exception {
runTest("idea/testData/slicer/inflow/overrideExtension1.kt");
}
@TestMetadata("overrideExtension2.kt")
public void testOverrideExtension2() throws Exception {
runTest("idea/testData/slicer/inflow/overrideExtension2.kt");
}
@TestMetadata("overrideFun.kt")
public void testOverrideFun() throws Exception {
runTest("idea/testData/slicer/inflow/overrideFun.kt");
@@ -323,6 +343,16 @@ public class SlicerNullnessGroupingTestGenerated extends AbstractSlicerNullnessG
runTest("idea/testData/slicer/inflow/settersViaJavaDelegate.kt");
}
@TestMetadata("thisInExtensionFunction.kt")
public void testThisInExtensionFunction() throws Exception {
runTest("idea/testData/slicer/inflow/thisInExtensionFunction.kt");
}
@TestMetadata("thisInExtensionProperty.kt")
public void testThisInExtensionProperty() throws Exception {
runTest("idea/testData/slicer/inflow/thisInExtensionProperty.kt");
}
@TestMetadata("topLevelVal.kt")
public void testTopLevelVal() throws Exception {
runTest("idea/testData/slicer/inflow/topLevelVal.kt");
@@ -0,0 +1,112 @@
/*
* 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 com.intellij.analysis.AnalysisScope
import com.intellij.ide.projectView.TreeStructureProvider
import com.intellij.ide.util.treeView.AbstractTreeStructureBase
import com.intellij.slicer.DuplicateMap
import com.intellij.slicer.SliceAnalysisParams
import com.intellij.slicer.SliceNode
import com.intellij.slicer.SliceRootNode
import com.intellij.usages.TextChunk
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import java.awt.Font
internal class TestSliceTreeStructure(private val rootNode: SliceNode) : AbstractTreeStructureBase(rootNode.project) {
override fun getProviders() = emptyList<TreeStructureProvider>()
override fun getRootElement() = rootNode
override fun commit() {
}
override fun hasSomethingToCommit() = false
}
internal fun buildTreeRepresentation(rootNode: SliceNode): String {
fun TextChunk.render(): String {
var text = text
if (attributes.fontType == Font.BOLD) {
text = "<bold>$text</bold>"
}
return text
}
fun SliceNode.isSliceLeafValueClassNode() = javaClass.name == "com.intellij.slicer.SliceLeafValueClassNode"
fun process(node: SliceNode, indent: Int): String {
val usage = node.element!!.value
node.calculateDupNode()
val isDuplicated = !node.isSliceLeafValueClassNode() && node.duplicate != null
return buildString {
when {
node is SliceRootNode && usage.element is KtFile -> {
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() + " ")
append("\t".repeat(indent))
if (usage is KotlinSliceDereferenceUsage) {
append("DEREFERENCE: ")
}
if (usage is KotlinSliceUsage) {
append("[LAMBDA] ".repeat(usage.lambdaLevel))
}
chunks.slice(1 until chunks.size).joinTo(
this,
separator = "",
prefix = if (isDuplicated) "DUPLICATE: " else "",
postfix = "\n"
) { it.render() }
}
}
if (!isDuplicated) {
node.sortedChildren.forEach { append(process(it, indent + 1)) }
}
}.replace(Regex("</bold><bold>"), "")
}
return process(rootNode, 0)
}
private val SliceNode.sortedChildren: List<SliceNode>
get() = children.sortedBy { it.value.element?.startOffset ?: -1 }
internal fun testSliceFromOffset(
file: KtFile,
offset: Int,
doTest: (sliceProvider: KotlinSliceProvider, rootNode: SliceRootNode) -> Unit
) {
val fileText = file.text
val flowKind = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// FLOW: ")
val withDereferences = InTextDirectivesUtils.isDirectiveDefined(fileText, "// WITH_DEREFERENCES")
val analysisParams = SliceAnalysisParams().apply {
dataFlowToThis = when (flowKind) {
"IN" -> true
"OUT" -> false
else -> throw AssertionError("Invalid flow kind: $flowKind")
}
showInstanceDereferences = withDereferences
scope = AnalysisScope(file.project)
}
val elementAtCaret = file.findElementAt(offset)!!
val sliceProvider = KotlinSliceProvider()
val expression = sliceProvider.getExpressionAtCaret(elementAtCaret, analysisParams.dataFlowToThis)!!
val rootUsage = sliceProvider.createRootUsage(expression, analysisParams)
val rootNode = SliceRootNode(file.project, DuplicateMap(), rootUsage)
doTest(sliceProvider, rootNode)
}
File diff suppressed because it is too large Load Diff