Data Flow to Here: process data-class copy-method calls

This commit is contained in:
Valentin Kipyatkov
2020-04-21 11:18:32 +03:00
parent 7f8b3b6a79
commit 7c81d93db0
10 changed files with 98 additions and 2 deletions
@@ -21,7 +21,9 @@ import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.* import org.jetbrains.kotlin.idea.caches.resolve.*
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.processAllUsages
import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinValVar import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinValVar
import org.jetbrains.kotlin.idea.refactoring.changeSignature.toValVar import org.jetbrains.kotlin.idea.refactoring.changeSignature.toValVar
import org.jetbrains.kotlin.idea.references.KtPropertyDelegationMethodsReference import org.jetbrains.kotlin.idea.references.KtPropertyDelegationMethodsReference
@@ -128,8 +130,29 @@ class InflowSlicer(
processCalls(function, includeOverriders, ArgumentSliceProducer(parameterDescriptor)) processCalls(function, includeOverriders, ArgumentSliceProducer(parameterDescriptor))
} }
if (parameter.valOrVarKeyword.toValVar() == KotlinValVar.Var) { val valVar = parameter.valOrVarKeyword.toValVar()
processAssignments(parameter, analysisScope) if (valVar != KotlinValVar.None) {
val classOrObject = (parameter.ownerFunction as? KtPrimaryConstructor)?.getContainingClassOrObject()
if (classOrObject != null && classOrObject.hasModifier(KtTokens.DATA_KEYWORD)) {
// Search usages of constructor parameter in form of named argument of call to "copy" function.
// We will miss calls of "copy" with positional parameters but it's unlikely someone write such code.
// Also, we will find named arguments of constructor calls but we need them anyway (already found above).
val options = KotlinPropertyFindUsagesOptions(project).apply {
searchScope = analysisScope
}
//TODO: optimizations to search only in files where "copy" word is present and also not resolve anything except named arguments
parameter.processAllUsages(options) { usageInfo ->
(((usageInfo.element as? KtNameReferenceExpression)
?.parent as? KtValueArgumentName)
?.parent as? KtValueArgument)
?.getArgumentExpression()
?.passToProcessorAsValue()
}
}
if (valVar == KotlinValVar.Var) {
processAssignments(parameter, analysisScope)
}
} }
} }
+15
View File
@@ -0,0 +1,15 @@
// FLOW: IN
data class DataClass(val value1: Int, val value2: Int)
fun foo(dataClass: DataClass) {
val <caret>v = dataClass.value1
}
fun bar() {
val dataClass = DataClass(1, 2)
foo(dataClass)
foo(dataClass.copy(value1 = 10))
foo(dataClass.copy(value2 = 11))
foo(DataClass(value1 = 1, value2 = 2))
}
@@ -0,0 +1,12 @@
10 val dataClass = DataClass(<bold>1</bold>, 2) (in bar())
6 <bold>val v</bold> = dataClass.value1 (in foo(DataClass))
6 val v = <bold>dataClass.value1</bold> (in foo(DataClass))
3 data class DataClass(<bold>val value1: Int</bold>, val value2: Int) (in DataClass)
10 val dataClass = DataClass(<bold>1</bold>, 2) (in bar())
12 foo(dataClass.copy(value1 = <bold>10</bold>)) (in bar())
6 <bold>val v</bold> = dataClass.value1 (in foo(DataClass))
6 val v = <bold>dataClass.value1</bold> (in foo(DataClass))
3 data class DataClass(<bold>val value1: Int</bold>, val value2: Int) (in DataClass)
12 foo(dataClass.copy(value1 = <bold>10</bold>)) (in bar())
@@ -0,0 +1,3 @@
[NotNull Values]
6 <bold>val v</bold> = dataClass.value1 (in foo(DataClass))
6 <bold>val v</bold> = dataClass.value1 (in foo(DataClass))
+6
View File
@@ -0,0 +1,6 @@
6 <bold>val v</bold> = dataClass.value1 (in foo(DataClass))
6 val v = <bold>dataClass.value1</bold> (in foo(DataClass))
3 data class DataClass(<bold>val value1: Int</bold>, val value2: Int) (in DataClass)
10 val dataClass = DataClass(<bold>1</bold>, 2) (in bar())
12 foo(dataClass.copy(value1 = <bold>10</bold>)) (in bar())
14 foo(DataClass(value1 = <bold>1</bold>, value2 = 2)) (in bar())
+11
View File
@@ -0,0 +1,11 @@
// FLOW: OUT
data class DataClass(val value1: Int, val value2: Int)
fun foo(dataClass: DataClass) {
val v = dataClass.value1
}
fun bar(<caret>value: Int, dataClass: DataClass) {
foo(dataClass.copy(value1 = value))
}
@@ -0,0 +1,6 @@
9 fun bar(<bold>value: Int</bold>, dataClass: DataClass) { (in bar(Int, DataClass))
10 foo(dataClass.copy(value1 = <bold>value</bold>)) (in bar(Int, DataClass))
3 data class DataClass(<bold>val value1: Int</bold>, val value2: Int) (in DataClass)
6 val v = dataClass.<bold>value1</bold> (in foo(DataClass))
6 <bold>val v</bold> = dataClass.value1 (in foo(DataClass))
10 foo(dataClass.copy(<bold>value1</bold> = value)) (in bar(Int, DataClass))
@@ -58,6 +58,11 @@ public class SlicerLeafGroupingTestGenerated extends AbstractSlicerLeafGroupingT
runTest("idea/testData/slicer/inflow/compositeAssignments.kt"); runTest("idea/testData/slicer/inflow/compositeAssignments.kt");
} }
@TestMetadata("dataClassCopy.kt")
public void testDataClassCopy() throws Exception {
runTest("idea/testData/slicer/inflow/dataClassCopy.kt");
}
@TestMetadata("defaultGetterFieldInSetter.kt") @TestMetadata("defaultGetterFieldInSetter.kt")
public void testDefaultGetterFieldInSetter() throws Exception { public void testDefaultGetterFieldInSetter() throws Exception {
runTest("idea/testData/slicer/inflow/defaultGetterFieldInSetter.kt"); runTest("idea/testData/slicer/inflow/defaultGetterFieldInSetter.kt");
@@ -58,6 +58,11 @@ public class SlicerNullnessGroupingTestGenerated extends AbstractSlicerNullnessG
runTest("idea/testData/slicer/inflow/compositeAssignments.kt"); runTest("idea/testData/slicer/inflow/compositeAssignments.kt");
} }
@TestMetadata("dataClassCopy.kt")
public void testDataClassCopy() throws Exception {
runTest("idea/testData/slicer/inflow/dataClassCopy.kt");
}
@TestMetadata("defaultGetterFieldInSetter.kt") @TestMetadata("defaultGetterFieldInSetter.kt")
public void testDefaultGetterFieldInSetter() throws Exception { public void testDefaultGetterFieldInSetter() throws Exception {
runTest("idea/testData/slicer/inflow/defaultGetterFieldInSetter.kt"); runTest("idea/testData/slicer/inflow/defaultGetterFieldInSetter.kt");
@@ -70,6 +70,11 @@ public class SlicerTreeTestGenerated extends AbstractSlicerTreeTest {
runTest("idea/testData/slicer/inflow/compositeAssignments.kt"); runTest("idea/testData/slicer/inflow/compositeAssignments.kt");
} }
@TestMetadata("dataClassCopy.kt")
public void testDataClassCopy() throws Exception {
runTest("idea/testData/slicer/inflow/dataClassCopy.kt");
}
@TestMetadata("defaultGetterFieldInSetter.kt") @TestMetadata("defaultGetterFieldInSetter.kt")
public void testDefaultGetterFieldInSetter() throws Exception { public void testDefaultGetterFieldInSetter() throws Exception {
runTest("idea/testData/slicer/inflow/defaultGetterFieldInSetter.kt"); runTest("idea/testData/slicer/inflow/defaultGetterFieldInSetter.kt");
@@ -483,6 +488,11 @@ public class SlicerTreeTestGenerated extends AbstractSlicerTreeTest {
runTest("idea/testData/slicer/outflow/cast.kt"); runTest("idea/testData/slicer/outflow/cast.kt");
} }
@TestMetadata("dataClassCopy.kt")
public void testDataClassCopy() throws Exception {
runTest("idea/testData/slicer/outflow/dataClassCopy.kt");
}
@TestMetadata("defaultExplicitPrimaryConstructorCalls.kt") @TestMetadata("defaultExplicitPrimaryConstructorCalls.kt")
public void testDefaultExplicitPrimaryConstructorCalls() throws Exception { public void testDefaultExplicitPrimaryConstructorCalls() throws Exception {
runTest("idea/testData/slicer/outflow/defaultExplicitPrimaryConstructorCalls.kt"); runTest("idea/testData/slicer/outflow/defaultExplicitPrimaryConstructorCalls.kt");