Refactoring to split big file

This commit is contained in:
Valentin Kipyatkov
2020-02-10 08:35:46 +02:00
parent ff516cac73
commit 612fd6d1bf
3 changed files with 478 additions and 439 deletions
@@ -0,0 +1,274 @@
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.slicer.SliceUsage
import com.intellij.util.Processor
import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction
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.DeclarationDescriptorWithSource
import org.jetbrains.kotlin.descriptors.VariableDescriptorWithAccessors
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.*
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.readWriteAccessWithFullExpression
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.util.OperatorNameConventions
class InflowSlicer(
element: KtExpression,
processor: Processor<SliceUsage>,
parentUsage: KotlinSliceUsage
) : Slicer(element, processor, parentUsage) {
private fun PsiElement.processHierarchyDownwardAndPass() {
processHierarchyDownward(parentUsage.scope.toSearchScope()) { passToProcessor() }
}
private fun PsiElement.passToProcessorAsValue(lambdaLevel: Int = parentUsage.lambdaLevel) = passToProcessor(lambdaLevel, true)
private fun KtDeclaration.processAssignments(accessSearchScope: SearchScope) {
processVariableAccesses(accessSearchScope,
AccessKind.WRITE_WITH_OPTIONAL_READ
) body@{
val refElement = it.element ?: return@body
val refParent = refElement.parent
val rhsValue = when {
refElement is KtExpression -> {
val (accessKind, accessExpression) = refElement.readWriteAccessWithFullExpression(true)
if (accessKind == ReferenceAccess.WRITE && accessExpression is KtBinaryExpression && accessExpression.operationToken == KtTokens.EQ) {
accessExpression.right
} else {
accessExpression
}
}
refParent is PsiCall -> refParent.argumentList?.expressions?.getOrNull(0)
else -> null
}
rhsValue?.passToProcessorAsValue()
}
}
private fun KtPropertyAccessor.processBackingFieldAssignments() {
forEachDescendantOfType<KtBinaryExpression> body@{
if (it.operationToken != KtTokens.EQ) return@body
val lhs = it.left?.let { expression -> KtPsiUtil.safeDeparenthesize(expression) } ?: return@body
val rhs = it.right ?: return@body
if (!lhs.isBackingFieldReference()) return@body
rhs.passToProcessor()
}
}
private fun KtProperty.processPropertyAssignments() {
val analysisScope = parentUsage.scope.toSearchScope()
val accessSearchScope = if (isVar) analysisScope
else {
val containerScope = getStrictParentOfType<KtDeclaration>()?.let {
LocalSearchScope(
it
)
} ?: return
analysisScope.intersectWith(containerScope)
}
processAssignments(accessSearchScope)
}
private fun KtProperty.processProperty() {
val bindingContext by lazy { analyzeWithContent() }
if (hasDelegateExpression()) {
val getter = (unsafeResolveToDescriptor() as VariableDescriptorWithAccessors).getter
val delegateGetterResolvedCall = getter?.let { bindingContext[BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, it] }
delegateGetterResolvedCall?.resultingDescriptor?.originalSource?.getPsi()?.passToProcessor()
return
}
initializer?.passToProcessor()
getter?.processFunction()
val isDefaultGetter = getter?.bodyExpression == null
val isDefaultSetter = setter?.bodyExpression == null
if (isDefaultGetter) {
if (isDefaultSetter) {
processPropertyAssignments()
} else {
setter!!.processBackingFieldAssignments()
}
}
}
private fun KtParameter.processParameter(includeOverriders: Boolean) {
if (!canProcess()) return
val function = ownerFunction ?: return
if (function is KtPropertyAccessor && function.isSetter) {
function.property.processPropertyAssignments()
return
}
if (function is KtNamedFunction
&& function.name == OperatorNameConventions.SET_VALUE.asString()
&& function.hasModifier(KtTokens.OPERATOR_KEYWORD)
) {
ReferencesSearch.search(function, parentUsage.scope.toSearchScope())
.filterIsInstance<KtPropertyDelegationMethodsReference>()
.forEach { (it.element.parent as? KtProperty)?.processPropertyAssignments() }
}
val parameterDescriptor = resolveToParameterDescriptorIfAny(BodyResolveMode.FULL) ?: return
(function as? KtFunction)?.processCalls(parentUsage.scope.toSearchScope(), includeOverriders) body@{
val refElement = it.element ?: return@body
val refParent = refElement.parent
val argumentExpression = when {
refElement is KtExpression -> {
val callElement = refElement.getParentOfTypeAndBranch<KtCallElement> { calleeExpression } ?: return@body
val resolvedCall = callElement.resolveToCall() ?: return@body
val callParameterDescriptor = resolvedCall.resultingDescriptor.valueParameters[parameterDescriptor.index]
val resolvedArgument = resolvedCall.valueArguments[callParameterDescriptor] ?: return@body
when (resolvedArgument) {
is DefaultValueArgument -> defaultValue
is ExpressionValueArgument -> resolvedArgument.valueArgument?.getArgumentExpression()
else -> null
}
}
refParent is PsiCall -> refParent.argumentList?.expressions?.getOrNull(this@processParameter.parameterIndex())
else -> null
}
argumentExpression?.passToProcessorAsValue()
}
if (valOrVarKeyword.toValVar() == KotlinValVar.Var) {
processAssignments(parentUsage.scope.toSearchScope())
}
}
private fun KtDeclarationWithBody.processFunction() {
val bodyExpression = bodyExpression ?: return
val pseudocode = pseudocodeCache[bodyExpression] ?: return
pseudocode.traverse(TraversalOrder.FORWARD) { instr ->
if (instr is ReturnValueInstruction && instr.subroutine == this) {
(instr.returnExpressionIfAny?.returnedExpression ?: instr.element as? KtExpression)?.passToProcessorAsValue()
}
}
}
private fun Instruction.passInputsToProcessor() {
inputValues.forEach {
if (it.createdAt != null) {
it.element?.passToProcessorAsValue()
}
}
}
private fun KtExpression.isBackingFieldReference(): Boolean {
return this is KtSimpleNameExpression &&
getReferencedName() == SyntheticFieldDescriptor.NAME.asString() &&
resolveToCall()?.resultingDescriptor is SyntheticFieldDescriptor
}
private fun KtExpression.processExpression() {
val lambda = when (this) {
is KtLambdaExpression -> functionLiteral
is KtNamedFunction -> if (name == null) this else null
else -> null
}
if (lambda != null) {
if (parentUsage.lambdaLevel > 0) {
lambda.passToProcessor(parentUsage.lambdaLevel - 1)
}
return
}
val pseudocode = pseudocodeCache[this] ?: return
val expressionValue = pseudocode.getElementValue(this) ?: return
when (val createdAt = expressionValue.createdAt) {
is ReadValueInstruction -> {
if (createdAt.target == AccessTarget.BlackBox) {
val originalElement = expressionValue.element as? KtExpression ?: return
if (originalElement != this) {
originalElement.processExpression()
}
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()
}
return
}
accessedDeclaration.processHierarchyDownwardAndPass()
}
is MergeInstruction -> createdAt.passInputsToProcessor()
is MagicInstruction -> when (createdAt.kind) {
MagicKind.NOT_NULL_ASSERTION, MagicKind.CAST -> createdAt.passInputsToProcessor()
MagicKind.BOUND_CALLABLE_REFERENCE, MagicKind.UNBOUND_CALLABLE_REFERENCE -> {
val callableRefExpr = expressionValue.element as? KtCallableReferenceExpression
?: return
val referencedDescriptor = analyze()[BindingContext.REFERENCE_TARGET, callableRefExpr.callableReference] ?: return
val referencedDeclaration = (referencedDescriptor as? DeclarationDescriptorWithSource)?.originalSource?.getPsi() ?: return
referencedDeclaration.passToProcessor(parentUsage.lambdaLevel - 1)
}
else -> return
}
is CallInstruction -> {
val resolvedCall = createdAt.resolvedCall
val resultingDescriptor = resolvedCall.resultingDescriptor
if (resultingDescriptor is FunctionInvokeDescriptor) {
(resolvedCall.dispatchReceiver as? ExpressionReceiver)?.expression?.passToProcessorAsValue(parentUsage.lambdaLevel + 1)
} else {
resultingDescriptor.originalSource.getPsi()?.processHierarchyDownwardAndPass()
}
}
}
}
override fun processChildren() {
if (parentUsage.forcedExpressionMode) return element.processExpression()
when (element) {
is KtProperty -> element.processProperty()
// for parameter, we include overriders only when the feature is invoked on parameter itself
is KtParameter -> element.processParameter(includeOverriders = parentUsage.parent == null)
is KtDeclarationWithBody -> element.processFunction()
else -> element.processExpression()
}
}
}
@@ -0,0 +1,163 @@
package org.jetbrains.kotlin.idea.slicer
import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector
import com.intellij.psi.PsiElement
import com.intellij.psi.impl.light.LightMemberReference
import com.intellij.slicer.SliceUsage
import com.intellij.util.Processor
import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.*
import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.ReturnValueInstruction
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReadWriteAccessDetector
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.resolve.source.getPsi
class OutflowSlicer(
element: KtExpression,
processor: Processor<SliceUsage>,
parentUsage: KotlinSliceUsage
) : Slicer(element, processor, parentUsage) {
private fun KtDeclaration.processVariable() {
processHierarchyUpward(parentUsage.scope) {
if (this is KtParameter && !canProcess()) return@processHierarchyUpward
val withDereferences = parentUsage.params.showInstanceDereferences
val accessKind = if (withDereferences) AccessKind.READ_OR_WRITE else AccessKind.READ_ONLY
(this as? KtDeclaration)?.processVariableAccesses(parentUsage.scope.toSearchScope(), accessKind) body@{
val refElement = it.element
if (refElement !is KtExpression) {
refElement?.passToProcessor()
return@body
}
val refExpression = KtPsiUtil.safeDeparenthesize(refElement)
if (withDereferences) {
refExpression.processDereferences()
}
if (!withDereferences || KotlinReadWriteAccessDetector.INSTANCE.getExpressionAccess(refExpression) == ReadWriteAccessDetector.Access.Read) {
refExpression.passToProcessor()
}
}
}
}
private fun PsiElement.getCallElementForExactCallee(): PsiElement? {
if (this is KtArrayAccessExpression) return this
val operationRefExpr = getNonStrictParentOfType<KtOperationReferenceExpression>()
if (operationRefExpr != null) return operationRefExpr.parent as? KtOperationExpression
val parentCall = getParentOfTypeAndBranch<KtCallElement> { calleeExpression } ?: return null
val callee = parentCall.calleeExpression?.let { KtPsiUtil.safeDeparenthesize(it) }
if (callee == this || callee is KtConstructorCalleeExpression && callee.isAncestor(this, true)) return parentCall
return null
}
private fun PsiElement.getCallableReferenceForExactCallee(): KtCallableReferenceExpression? {
val callableRef = getParentOfTypeAndBranch<KtCallableReferenceExpression> { callableReference } ?: return null
val callee = KtPsiUtil.safeDeparenthesize(callableRef.callableReference)
return if (callee == this) callableRef else null
}
private fun KtFunction.processFunction() {
if (this is KtConstructor<*> || this is KtNamedFunction && name != null) {
processCalls(parentUsage.scope.toSearchScope(), includeOverriders = false) {
when (val refElement = it.element) {
null -> (it.reference as? LightMemberReference)?.element?.passToProcessor()
is KtExpression -> {
refElement.getCallElementForExactCallee()?.passToProcessor()
refElement.getCallableReferenceForExactCallee()?.passToProcessor(parentUsage.lambdaLevel + 1)
}
else -> refElement.passToProcessor()
}
}
return
}
val funExpression = when (this) {
is KtFunctionLiteral -> parent as? KtLambdaExpression
is KtNamedFunction -> this
else -> null
} ?: return
(funExpression as PsiElement).passToProcessor(parentUsage.lambdaLevel + 1, true)
}
private fun processDereferenceIsNeeded(
expression: KtExpression,
pseudoValue: PseudoValue,
instr: InstructionWithReceivers
) {
if (!parentUsage.params.showInstanceDereferences) return
val receiver = instr.receiverValues[pseudoValue]
val resolvedCall = when (instr) {
is CallInstruction -> instr.resolvedCall
is ReadValueInstruction -> (instr.target as? AccessTarget.Call)?.resolvedCall
else -> null
} ?: return
if (receiver != null && resolvedCall.dispatchReceiver == receiver) {
processor.process(
KotlinSliceDereferenceUsage(
expression,
parentUsage,
parentUsage.lambdaLevel
)
)
}
}
private fun KtExpression.processPseudocodeUsages(processor: (PseudoValue, Instruction) -> Unit) {
val pseudocode = pseudocodeCache[this] ?: return
val pseudoValue = pseudocode.getElementValue(this) ?: return
pseudocode.getUsages(pseudoValue).forEach { processor(pseudoValue, it) }
}
private fun KtExpression.processDereferences() {
processPseudocodeUsages { pseudoValue, instr ->
when (instr) {
is ReadValueInstruction -> processDereferenceIsNeeded(this, pseudoValue, instr)
is CallInstruction -> processDereferenceIsNeeded(this, pseudoValue, instr)
}
}
}
private fun KtExpression.processExpression() {
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()
}
}
is ReturnValueInstruction -> instr.subroutine.passToProcessor()
is MagicInstruction -> when (instr.kind) {
MagicKind.NOT_NULL_ASSERTION, MagicKind.CAST -> instr.outputValue.element?.passToProcessor()
else -> {
}
}
}
}
}
override fun processChildren() {
if (parentUsage.forcedExpressionMode) return element.processExpression()
when (element) {
is KtProperty, is KtParameter -> (element as KtDeclaration).processVariable()
is KtFunction -> element.processFunction()
is KtPropertyAccessor -> if (element.isGetter) {
element.property.processVariable()
}
else -> element.processExpression()
}
}
}
@@ -6,103 +6,34 @@
package org.jetbrains.kotlin.idea.slicer package org.jetbrains.kotlin.idea.slicer
import com.intellij.analysis.AnalysisScope import com.intellij.analysis.AnalysisScope
import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector.Access
import com.intellij.psi.PsiCall
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod import com.intellij.psi.PsiMethod
import com.intellij.psi.impl.light.LightMemberReference
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.SearchScope import com.intellij.psi.search.SearchScope
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.slicer.JavaSliceUsage import com.intellij.slicer.JavaSliceUsage
import com.intellij.usageView.UsageInfo import com.intellij.usageView.UsageInfo
import org.jetbrains.kotlin.asJava.namedUnwrappedElement import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor
import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue
import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode
import org.jetbrains.kotlin.cfg.pseudocode.containingDeclarationForPseudocode import org.jetbrains.kotlin.cfg.pseudocode.containingDeclarationForPseudocode
import org.jetbrains.kotlin.cfg.pseudocode.getContainingPseudocode import org.jetbrains.kotlin.cfg.pseudocode.getContainingPseudocode
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction
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.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
import org.jetbrains.kotlin.descriptors.SourceElement import org.jetbrains.kotlin.descriptors.SourceElement
import org.jetbrains.kotlin.descriptors.VariableDescriptorWithAccessors import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.*
import org.jetbrains.kotlin.idea.core.getDeepestSuperDeclarations import org.jetbrains.kotlin.idea.core.getDeepestSuperDeclarations
import org.jetbrains.kotlin.idea.findUsages.KotlinFunctionFindUsagesOptions import org.jetbrains.kotlin.idea.findUsages.KotlinFunctionFindUsagesOptions
import org.jetbrains.kotlin.idea.findUsages.KotlinPropertyFindUsagesOptions import org.jetbrains.kotlin.idea.findUsages.KotlinPropertyFindUsagesOptions
import org.jetbrains.kotlin.idea.findUsages.handlers.SliceUsageProcessor import org.jetbrains.kotlin.idea.findUsages.handlers.SliceUsageProcessor
import org.jetbrains.kotlin.idea.findUsages.processAllExactUsages import org.jetbrains.kotlin.idea.findUsages.processAllExactUsages
import org.jetbrains.kotlin.idea.findUsages.processAllUsages import org.jetbrains.kotlin.idea.findUsages.processAllUsages
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.readWriteAccessWithFullExpression
import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest
import org.jetbrains.kotlin.idea.search.declarationsSearch.searchOverriders import org.jetbrains.kotlin.idea.search.declarationsSearch.searchOverriders
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReadWriteAccessDetector
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.psi.psiUtil.contains
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.resolve.source.getPsi import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.util.OperatorNameConventions
import java.util.* import java.util.*
private fun PsiElement.processHierarchyDownward(scope: SearchScope, processor: PsiElement.() -> Unit) {
processor()
HierarchySearchRequest(this, scope).searchOverriders().forEach {
it.namedUnwrappedElement?.processor()
}
}
private fun KtDeclaration.processHierarchyUpward(scope: AnalysisScope, processor: PsiElement.() -> Unit) {
processor()
val descriptor = unsafeResolveToDescriptor() as? CallableMemberDescriptor ?: return
DescriptorUtils
.getAllOverriddenDescriptors(descriptor)
.asSequence()
.mapNotNull { it.originalSource.getPsi() }
.filter { scope.contains(it) }
.toList()
.forEach(processor)
}
private enum class AccessKind {
READ_ONLY, WRITE_ONLY, WRITE_WITH_OPTIONAL_READ, READ_OR_WRITE
}
private fun KtDeclaration.processVariableAccesses(
scope: SearchScope,
kind: AccessKind,
processor: (UsageInfo) -> Unit
) {
processAllExactUsages(
KotlinPropertyFindUsagesOptions(project).apply {
isReadAccess = kind == AccessKind.READ_ONLY || kind == AccessKind.READ_OR_WRITE
isWriteAccess = kind == AccessKind.WRITE_ONLY || kind == AccessKind.WRITE_WITH_OPTIONAL_READ || kind == AccessKind.READ_OR_WRITE
isReadWriteAccess = kind == AccessKind.WRITE_WITH_OPTIONAL_READ || kind == AccessKind.READ_OR_WRITE
isSearchForTextOccurrences = false
isSkipImportStatements = true
searchScope = scope.intersectWith(useScope)
},
processor
)
}
private fun KtParameter.canProcess() = !isVarArg
abstract class Slicer( abstract class Slicer(
protected val element: KtExpression, protected val element: KtExpression,
protected val processor: SliceUsageProcessor, protected val processor: SliceUsageProcessor,
@@ -168,385 +99,56 @@ abstract class Slicer(
} }
} }
} }
}
class InflowSlicer( protected fun PsiElement.processHierarchyDownward(scope: SearchScope, processor: PsiElement.() -> Unit) {
element: KtExpression, processor()
processor: SliceUsageProcessor, HierarchySearchRequest(this, scope).searchOverriders().forEach {
parentUsage: KotlinSliceUsage it.namedUnwrappedElement?.processor()
) : Slicer(element, processor, parentUsage) {
private fun PsiElement.processHierarchyDownwardAndPass() {
processHierarchyDownward(parentUsage.scope.toSearchScope()) { passToProcessor() }
}
private fun PsiElement.passToProcessorAsValue(lambdaLevel: Int = parentUsage.lambdaLevel) = passToProcessor(lambdaLevel, true)
private fun KtDeclaration.processAssignments(accessSearchScope: SearchScope) {
processVariableAccesses(accessSearchScope, AccessKind.WRITE_WITH_OPTIONAL_READ) body@{
val refElement = it.element ?: return@body
val refParent = refElement.parent
val rhsValue = when {
refElement is KtExpression -> {
val (accessKind, accessExpression) = refElement.readWriteAccessWithFullExpression(true)
if (accessKind == ReferenceAccess.WRITE && accessExpression is KtBinaryExpression && accessExpression.operationToken == KtTokens.EQ) {
accessExpression.right
} else {
accessExpression
}
}
refParent is PsiCall -> refParent.argumentList?.expressions?.getOrNull(0)
else -> null
}
rhsValue?.passToProcessorAsValue()
} }
} }
private fun KtPropertyAccessor.processBackingFieldAssignments() { protected fun KtDeclaration.processHierarchyUpward(scope: AnalysisScope, processor: PsiElement.() -> Unit) {
forEachDescendantOfType<KtBinaryExpression> body@{ processor()
if (it.operationToken != KtTokens.EQ) return@body val descriptor = unsafeResolveToDescriptor() as? CallableMemberDescriptor ?: return
val lhs = it.left?.let { expression -> KtPsiUtil.safeDeparenthesize(expression) } ?: return@body DescriptorUtils
val rhs = it.right ?: return@body .getAllOverriddenDescriptors(descriptor)
if (!lhs.isBackingFieldReference()) return@body .asSequence()
rhs.passToProcessor() .mapNotNull { it.originalSource.getPsi() }
} .filter { scope.contains(it) }
.toList()
.forEach(processor)
} }
private fun KtProperty.processPropertyAssignments() { protected enum class AccessKind {
val analysisScope = parentUsage.scope.toSearchScope() READ_ONLY, WRITE_ONLY, WRITE_WITH_OPTIONAL_READ, READ_OR_WRITE
val accessSearchScope = if (isVar) analysisScope
else {
val containerScope = getStrictParentOfType<KtDeclaration>()?.let { LocalSearchScope(it) } ?: return
analysisScope.intersectWith(containerScope)
}
processAssignments(accessSearchScope)
} }
private fun KtProperty.processProperty() { protected fun KtDeclaration.processVariableAccesses(
val bindingContext by lazy { analyzeWithContent() } scope: SearchScope,
kind: AccessKind,
if (hasDelegateExpression()) { processor: (UsageInfo) -> Unit
val getter = (unsafeResolveToDescriptor() as VariableDescriptorWithAccessors).getter
val delegateGetterResolvedCall = getter?.let { bindingContext[BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, it] }
delegateGetterResolvedCall?.resultingDescriptor?.originalSource?.getPsi()?.passToProcessor()
return
}
initializer?.passToProcessor()
getter?.processFunction()
val isDefaultGetter = getter?.bodyExpression == null
val isDefaultSetter = setter?.bodyExpression == null
if (isDefaultGetter) {
if (isDefaultSetter) {
processPropertyAssignments()
} else {
setter!!.processBackingFieldAssignments()
}
}
}
private fun KtParameter.processParameter(includeOverriders: Boolean) {
if (!canProcess()) return
val function = ownerFunction ?: return
if (function is KtPropertyAccessor && function.isSetter) {
function.property.processPropertyAssignments()
return
}
if (function is KtNamedFunction
&& function.name == OperatorNameConventions.SET_VALUE.asString()
&& function.hasModifier(KtTokens.OPERATOR_KEYWORD)
) {
ReferencesSearch
.search(function, parentUsage.scope.toSearchScope())
.filterIsInstance<KtPropertyDelegationMethodsReference>()
.forEach { (it.element.parent as? KtProperty)?.processPropertyAssignments() }
}
val parameterDescriptor = resolveToParameterDescriptorIfAny(BodyResolveMode.FULL) ?: return
(function as? KtFunction)?.processCalls(parentUsage.scope.toSearchScope(), includeOverriders) body@{
val refElement = it.element ?: return@body
val refParent = refElement.parent
val argumentExpression = when {
refElement is KtExpression -> {
val callElement = refElement.getParentOfTypeAndBranch<KtCallElement> { calleeExpression } ?: return@body
val resolvedCall = callElement.resolveToCall() ?: return@body
val callParameterDescriptor = resolvedCall.resultingDescriptor.valueParameters[parameterDescriptor.index]
val resolvedArgument = resolvedCall.valueArguments[callParameterDescriptor] ?: return@body
when (resolvedArgument) {
is DefaultValueArgument -> defaultValue
is ExpressionValueArgument -> resolvedArgument.valueArgument?.getArgumentExpression()
else -> null
}
}
refParent is PsiCall -> refParent.argumentList?.expressions?.getOrNull(this@processParameter.parameterIndex())
else -> null
}
argumentExpression?.passToProcessorAsValue()
}
if (valOrVarKeyword.toValVar() == KotlinValVar.Var) {
processAssignments(parentUsage.scope.toSearchScope())
}
}
private fun KtDeclarationWithBody.processFunction() {
val bodyExpression = bodyExpression ?: return
val pseudocode = pseudocodeCache[bodyExpression] ?: return
pseudocode.traverse(TraversalOrder.FORWARD) { instr ->
if (instr is ReturnValueInstruction && instr.subroutine == this) {
(instr.returnExpressionIfAny?.returnedExpression ?: instr.element as? KtExpression)?.passToProcessorAsValue()
}
}
}
private fun Instruction.passInputsToProcessor() {
inputValues.forEach {
if (it.createdAt != null) {
it.element?.passToProcessorAsValue()
}
}
}
private fun KtExpression.isBackingFieldReference(): Boolean {
return this is KtSimpleNameExpression &&
getReferencedName() == SyntheticFieldDescriptor.NAME.asString() &&
resolveToCall()?.resultingDescriptor is SyntheticFieldDescriptor
}
private fun KtExpression.processExpression() {
val lambda = when (this) {
is KtLambdaExpression -> functionLiteral
is KtNamedFunction -> if (name == null) this else null
else -> null
}
if (lambda != null) {
if (parentUsage.lambdaLevel > 0) {
lambda.passToProcessor(parentUsage.lambdaLevel - 1)
}
return
}
val pseudocode = pseudocodeCache[this] ?: return
val expressionValue = pseudocode.getElementValue(this) ?: return
when (val createdAt = expressionValue.createdAt) {
is ReadValueInstruction -> {
if (createdAt.target == AccessTarget.BlackBox) {
val originalElement = expressionValue.element as? KtExpression ?: return
if (originalElement != this) {
originalElement.processExpression()
}
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()
}
return
}
accessedDeclaration.processHierarchyDownwardAndPass()
}
is MergeInstruction -> createdAt.passInputsToProcessor()
is MagicInstruction -> when (createdAt.kind) {
MagicKind.NOT_NULL_ASSERTION, MagicKind.CAST -> createdAt.passInputsToProcessor()
MagicKind.BOUND_CALLABLE_REFERENCE, MagicKind.UNBOUND_CALLABLE_REFERENCE -> {
val callableRefExpr = expressionValue.element as? KtCallableReferenceExpression ?: return
val referencedDescriptor = analyze()[BindingContext.REFERENCE_TARGET, callableRefExpr.callableReference] ?: return
val referencedDeclaration =
(referencedDescriptor as? DeclarationDescriptorWithSource)?.originalSource?.getPsi() ?: return
referencedDeclaration.passToProcessor(parentUsage.lambdaLevel - 1)
}
else -> return
}
is CallInstruction -> {
val resolvedCall = createdAt.resolvedCall
val resultingDescriptor = resolvedCall.resultingDescriptor
if (resultingDescriptor is FunctionInvokeDescriptor) {
(resolvedCall.dispatchReceiver as? ExpressionReceiver)?.expression?.passToProcessorAsValue(parentUsage.lambdaLevel + 1)
} else {
resultingDescriptor.originalSource.getPsi()?.processHierarchyDownwardAndPass()
}
}
}
}
override fun processChildren() {
if (parentUsage.forcedExpressionMode) return element.processExpression()
when (element) {
is KtProperty -> element.processProperty()
// for parameter, we include overriders only when the feature is invoked on parameter itself
is KtParameter -> element.processParameter(includeOverriders = parentUsage.parent == null)
is KtDeclarationWithBody -> element.processFunction()
else -> element.processExpression()
}
}
}
class OutflowSlicer(
element: KtExpression,
processor: SliceUsageProcessor,
parentUsage: KotlinSliceUsage
) : Slicer(element, processor, parentUsage) {
private fun KtDeclaration.processVariable() {
processHierarchyUpward(parentUsage.scope) {
if (this is KtParameter && !canProcess()) return@processHierarchyUpward
val withDereferences = parentUsage.params.showInstanceDereferences
val accessKind = if (withDereferences) AccessKind.READ_OR_WRITE else AccessKind.READ_ONLY
(this as? KtDeclaration)?.processVariableAccesses(parentUsage.scope.toSearchScope(), accessKind) body@{
val refElement = it.element
if (refElement !is KtExpression) {
refElement?.passToProcessor()
return@body
}
val refExpression = KtPsiUtil.safeDeparenthesize(refElement)
if (withDereferences) {
refExpression.processDereferences()
}
if (!withDereferences || KotlinReadWriteAccessDetector.INSTANCE.getExpressionAccess(refExpression) == Access.Read) {
refExpression.passToProcessor()
}
}
}
}
private fun PsiElement.getCallElementForExactCallee(): PsiElement? {
if (this is KtArrayAccessExpression) return this
val operationRefExpr = getNonStrictParentOfType<KtOperationReferenceExpression>()
if (operationRefExpr != null) return operationRefExpr.parent as? KtOperationExpression
val parentCall = getParentOfTypeAndBranch<KtCallElement> { calleeExpression } ?: return null
val callee = parentCall.calleeExpression?.let { KtPsiUtil.safeDeparenthesize(it) }
if (callee == this || callee is KtConstructorCalleeExpression && callee.isAncestor(this, true)) return parentCall
return null
}
private fun PsiElement.getCallableReferenceForExactCallee(): KtCallableReferenceExpression? {
val callableRef = getParentOfTypeAndBranch<KtCallableReferenceExpression> { callableReference } ?: return null
val callee = KtPsiUtil.safeDeparenthesize(callableRef.callableReference)
return if (callee == this) callableRef else null
}
private fun KtFunction.processFunction() {
if (this is KtConstructor<*> || this is KtNamedFunction && name != null) {
processCalls(parentUsage.scope.toSearchScope(), includeOverriders = false) {
when (val refElement = it.element) {
null -> (it.reference as? LightMemberReference)?.element?.passToProcessor()
is KtExpression -> {
refElement.getCallElementForExactCallee()?.passToProcessor()
refElement.getCallableReferenceForExactCallee()?.passToProcessor(parentUsage.lambdaLevel + 1)
}
else -> refElement.passToProcessor()
}
}
return
}
val funExpression = when (this) {
is KtFunctionLiteral -> parent as? KtLambdaExpression
is KtNamedFunction -> this
else -> null
} ?: return
(funExpression as PsiElement).passToProcessor(parentUsage.lambdaLevel + 1, true)
}
private fun processDereferenceIsNeeded(
expression: KtExpression,
pseudoValue: PseudoValue,
instr: InstructionWithReceivers
) { ) {
if (!parentUsage.params.showInstanceDereferences) return processAllExactUsages(
KotlinPropertyFindUsagesOptions(project).apply {
val receiver = instr.receiverValues[pseudoValue] isReadAccess = kind == AccessKind.READ_ONLY || kind == AccessKind.READ_OR_WRITE
val resolvedCall = when (instr) { isWriteAccess = kind == AccessKind.WRITE_ONLY || kind == AccessKind.WRITE_WITH_OPTIONAL_READ || kind == AccessKind.READ_OR_WRITE
is CallInstruction -> instr.resolvedCall isReadWriteAccess = kind == AccessKind.WRITE_WITH_OPTIONAL_READ || kind == AccessKind.READ_OR_WRITE
is ReadValueInstruction -> (instr.target as? AccessTarget.Call)?.resolvedCall isSearchForTextOccurrences = false
else -> null isSkipImportStatements = true
} ?: return searchScope = scope.intersectWith(useScope)
},
if (receiver != null && resolvedCall.dispatchReceiver == receiver) { processor
processor.process(KotlinSliceDereferenceUsage(expression, parentUsage, parentUsage.lambdaLevel)) )
}
} }
private fun KtExpression.processPseudocodeUsages(processor: (PseudoValue, Instruction) -> Unit) { protected fun KtParameter.canProcess() = !isVarArg
val pseudocode = pseudocodeCache[this] ?: return
val pseudoValue = pseudocode.getElementValue(this) ?: return
pseudocode.getUsages(pseudoValue).forEach { processor(pseudoValue, it) }
}
private fun KtExpression.processDereferences() { protected val DeclarationDescriptorWithSource.originalSource: SourceElement
processPseudocodeUsages { pseudoValue, instr -> get() {
when (instr) { var descriptor = this
is ReadValueInstruction -> processDereferenceIsNeeded(this, pseudoValue, instr) while (descriptor.original != descriptor) {
is CallInstruction -> processDereferenceIsNeeded(this, pseudoValue, instr) descriptor = descriptor.original
} }
return descriptor.source
} }
}
private fun KtExpression.processExpression() {
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()
}
}
is ReturnValueInstruction -> instr.subroutine.passToProcessor()
is MagicInstruction -> when (instr.kind) {
MagicKind.NOT_NULL_ASSERTION, MagicKind.CAST -> instr.outputValue.element?.passToProcessor()
else -> {
}
}
}
}
}
override fun processChildren() {
if (parentUsage.forcedExpressionMode) return element.processExpression()
when (element) {
is KtProperty, is KtParameter -> (element as KtDeclaration).processVariable()
is KtFunction -> element.processFunction()
is KtPropertyAccessor -> if (element.isGetter) {
element.property.processVariable()
}
else -> element.processExpression()
}
}
} }
private val DeclarationDescriptorWithSource.originalSource: SourceElement
get() {
var descriptor = this
while (descriptor.original != descriptor) {
descriptor = descriptor.original
}
return descriptor.source
}