Inline val: more correct and simple detection of write usages
#KT-17489 Fixed
This commit is contained in:
+4
-8
@@ -18,7 +18,6 @@ package org.jetbrains.kotlin.idea.refactoring.inline
|
|||||||
|
|
||||||
import com.intellij.lang.findUsages.DescriptiveNameUtil
|
import com.intellij.lang.findUsages.DescriptiveNameUtil
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.psi.PsiElement
|
|
||||||
import com.intellij.psi.search.GlobalSearchScope
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
import com.intellij.psi.search.searches.ReferencesSearch
|
import com.intellij.psi.search.searches.ReferencesSearch
|
||||||
import com.intellij.refactoring.BaseRefactoringProcessor
|
import com.intellij.refactoring.BaseRefactoringProcessor
|
||||||
@@ -32,10 +31,7 @@ import org.jetbrains.kotlin.idea.codeInliner.replaceUsages
|
|||||||
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
||||||
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
||||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
import org.jetbrains.kotlin.psi.KtCallableDeclaration
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
|
||||||
import org.jetbrains.kotlin.psi.KtProperty
|
|
||||||
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
|
||||||
|
|
||||||
class KotlinInlineCallableProcessor(
|
class KotlinInlineCallableProcessor(
|
||||||
project: Project,
|
project: Project,
|
||||||
@@ -44,7 +40,7 @@ class KotlinInlineCallableProcessor(
|
|||||||
private val reference: KtSimpleNameReference?,
|
private val reference: KtSimpleNameReference?,
|
||||||
private val inlineThisOnly: Boolean,
|
private val inlineThisOnly: Boolean,
|
||||||
private val deleteAfter: Boolean,
|
private val deleteAfter: Boolean,
|
||||||
private val assignments: Set<PsiElement> = emptySet()
|
private val statementToDelete: KtBinaryExpression? = null
|
||||||
) : BaseRefactoringProcessor(project) {
|
) : BaseRefactoringProcessor(project) {
|
||||||
|
|
||||||
private val kind = when (declaration) {
|
private val kind = when (declaration) {
|
||||||
@@ -71,11 +67,11 @@ class KotlinInlineCallableProcessor(
|
|||||||
declaration,
|
declaration,
|
||||||
myProject,
|
myProject,
|
||||||
commandName,
|
commandName,
|
||||||
{
|
postAction = {
|
||||||
if (deleteAfter) {
|
if (deleteAfter) {
|
||||||
if (usages.size == simpleNameUsages.size) {
|
if (usages.size == simpleNameUsages.size) {
|
||||||
declaration.delete()
|
declaration.delete()
|
||||||
assignments.forEach(PsiElement::delete)
|
statementToDelete?.delete()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
CommonRefactoringUtil.showErrorHint(
|
CommonRefactoringUtil.showErrorHint(
|
||||||
|
|||||||
@@ -17,18 +17,18 @@
|
|||||||
package org.jetbrains.kotlin.idea.refactoring.inline
|
package org.jetbrains.kotlin.idea.refactoring.inline
|
||||||
|
|
||||||
import com.intellij.openapi.util.text.StringUtil
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
import com.intellij.psi.PsiElement
|
|
||||||
import com.intellij.refactoring.JavaRefactoringSettings
|
import com.intellij.refactoring.JavaRefactoringSettings
|
||||||
import com.intellij.refactoring.inline.InlineOptionsDialog
|
import com.intellij.refactoring.inline.InlineOptionsDialog
|
||||||
import org.jetbrains.kotlin.idea.codeInliner.CallableUsageReplacementStrategy
|
import org.jetbrains.kotlin.idea.codeInliner.CallableUsageReplacementStrategy
|
||||||
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
||||||
|
import org.jetbrains.kotlin.psi.KtBinaryExpression
|
||||||
import org.jetbrains.kotlin.psi.KtProperty
|
import org.jetbrains.kotlin.psi.KtProperty
|
||||||
|
|
||||||
class KotlinInlineValDialog(
|
class KotlinInlineValDialog(
|
||||||
private val property: KtProperty,
|
private val property: KtProperty,
|
||||||
private val reference: KtSimpleNameReference?,
|
private val reference: KtSimpleNameReference?,
|
||||||
private val replacementStrategy: CallableUsageReplacementStrategy,
|
private val replacementStrategy: CallableUsageReplacementStrategy,
|
||||||
private val assignments: Set<PsiElement>
|
private val assignmentToDelete: KtBinaryExpression?
|
||||||
) : InlineOptionsDialog(property.project, true, property) {
|
) : InlineOptionsDialog(property.project, true, property) {
|
||||||
|
|
||||||
private var occurrenceCount = initOccurrencesNumber(property)
|
private var occurrenceCount = initOccurrencesNumber(property)
|
||||||
@@ -69,7 +69,7 @@ class KotlinInlineValDialog(
|
|||||||
KotlinInlineCallableProcessor(project, replacementStrategy, property, reference,
|
KotlinInlineCallableProcessor(project, replacementStrategy, property, reference,
|
||||||
inlineThisOnly = isInlineThisOnly,
|
inlineThisOnly = isInlineThisOnly,
|
||||||
deleteAfter = !isInlineThisOnly && !isKeepTheDeclaration,
|
deleteAfter = !isInlineThisOnly && !isKeepTheDeclaration,
|
||||||
assignments = assignments)
|
statementToDelete = assignmentToDelete)
|
||||||
)
|
)
|
||||||
|
|
||||||
val settings = JavaRefactoringSettings.getInstance()
|
val settings = JavaRefactoringSettings.getInstance()
|
||||||
|
|||||||
@@ -39,13 +39,18 @@ import org.jetbrains.kotlin.idea.codeInliner.CodeToInlineBuilder
|
|||||||
import org.jetbrains.kotlin.idea.core.copied
|
import org.jetbrains.kotlin.idea.core.copied
|
||||||
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
|
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
|
||||||
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
||||||
|
import org.jetbrains.kotlin.idea.references.ReferenceAccess
|
||||||
|
import org.jetbrains.kotlin.idea.references.readWriteAccess
|
||||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
|
import org.jetbrains.kotlin.psi.KtBinaryExpression
|
||||||
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
|
import org.jetbrains.kotlin.psi.KtExpression
|
||||||
|
import org.jetbrains.kotlin.psi.KtProperty
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getAssignmentByLHS
|
import org.jetbrains.kotlin.psi.psiUtil.getAssignmentByLHS
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelectorOrThis
|
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelectorOrThis
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.types.TypeUtils
|
import org.jetbrains.kotlin.types.TypeUtils
|
||||||
import org.jetbrains.kotlin.types.expressions.OperatorConventions
|
|
||||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||||
|
|
||||||
class KotlinInlineValHandler : InlineActionHandler() {
|
class KotlinInlineValHandler : InlineActionHandler() {
|
||||||
@@ -82,28 +87,24 @@ class KotlinInlineValHandler : InlineActionHandler() {
|
|||||||
return showErrorHint(project, editor, "$kind '$name' is never used")
|
return showErrorHint(project, editor, "$kind '$name' is never used")
|
||||||
}
|
}
|
||||||
|
|
||||||
val assignments = hashSetOf<PsiElement>()
|
val writeUsages = referenceExpressions.filter { it.readWriteAccess(useResolveForReadWrite = true) != ReferenceAccess.READ }
|
||||||
referenceExpressions.forEach { expression ->
|
|
||||||
val parent = expression.parent
|
|
||||||
|
|
||||||
val assignment = expression.getAssignmentByLHS()
|
|
||||||
if (assignment != null) {
|
|
||||||
assignments.add(parent)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parent is KtUnaryExpression && OperatorConventions.INCREMENT_OPERATIONS.contains(parent.operationToken)) {
|
|
||||||
assignments.add(parent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val initializerInDeclaration = declaration.initializer
|
val initializerInDeclaration = declaration.initializer
|
||||||
val initializer = if (initializerInDeclaration != null) {
|
val initializer: KtExpression
|
||||||
if (!assignments.isEmpty()) return reportAmbiguousAssignment(project, editor, name, assignments)
|
val assignment: KtBinaryExpression?
|
||||||
initializerInDeclaration
|
if (initializerInDeclaration != null) {
|
||||||
|
if (!writeUsages.isEmpty()) {
|
||||||
|
return reportAmbiguousAssignment(project, editor, name, writeUsages)
|
||||||
|
}
|
||||||
|
initializer = initializerInDeclaration
|
||||||
|
assignment = null
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
(assignments.singleOrNull() as? KtBinaryExpression)?.right
|
assignment = writeUsages.singleOrNull()
|
||||||
?: return reportAmbiguousAssignment(project, editor, name, assignments)
|
?.getAssignmentByLHS()
|
||||||
|
?.takeIf { it.operationToken == KtTokens.EQ }
|
||||||
|
initializer = assignment?.right
|
||||||
|
?: return reportAmbiguousAssignment(project, editor, name, writeUsages)
|
||||||
}
|
}
|
||||||
|
|
||||||
val referencesInOriginalFile = referenceExpressions.filter { it.containingFile == file }
|
val referencesInOriginalFile = referenceExpressions.filter { it.containingFile == file }
|
||||||
@@ -133,7 +134,7 @@ class KotlinInlineValHandler : InlineActionHandler() {
|
|||||||
val replacement = replacementBuilder.prepareCodeToInline(initializerCopy, emptyList(), ::analyzeInitializerCopy)
|
val replacement = replacementBuilder.prepareCodeToInline(initializerCopy, emptyList(), ::analyzeInitializerCopy)
|
||||||
val replacementStrategy = CallableUsageReplacementStrategy(replacement)
|
val replacementStrategy = CallableUsageReplacementStrategy(replacement)
|
||||||
|
|
||||||
val dialog = KotlinInlineValDialog(declaration, reference, replacementStrategy, assignments)
|
val dialog = KotlinInlineValDialog(declaration, reference, replacementStrategy, assignment)
|
||||||
|
|
||||||
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
dialog.show()
|
dialog.show()
|
||||||
@@ -159,7 +160,7 @@ class KotlinInlineValHandler : InlineActionHandler() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun reportAmbiguousAssignment(project: Project, editor: Editor?, name: String, assignments: Set<PsiElement>) {
|
private fun reportAmbiguousAssignment(project: Project, editor: Editor?, name: String, assignments: Collection<PsiElement>) {
|
||||||
val key = if (assignments.isEmpty()) "variable.has.no.initializer" else "variable.has.no.dominating.definition"
|
val key = if (assignments.isEmpty()) "variable.has.no.initializer" else "variable.has.no.dominating.definition"
|
||||||
val message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message(key, name))
|
val message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message(key, name))
|
||||||
showErrorHint(project, editor, message)
|
showErrorHint(project, editor, message)
|
||||||
|
|||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
fun foo(list: MutableList<Int>) {
|
||||||
|
val <caret>list1 = list
|
||||||
|
list1 += 10
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
fun foo(list: MutableList<Int>) {
|
||||||
|
list += 10
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
class Predicate(val x: Int) {
|
||||||
|
operator fun plusAssign(y: Int) {}
|
||||||
|
|
||||||
|
operator fun unaryMinus() = Predicate(-x)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test(p: Predicate) {
|
||||||
|
val <caret>x = -p
|
||||||
|
x += 42
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
class Predicate(val x: Int) {
|
||||||
|
operator fun plusAssign(y: Int) {}
|
||||||
|
|
||||||
|
operator fun unaryMinus() = Predicate(-x)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test(p: Predicate) {
|
||||||
|
-p += 42
|
||||||
|
}
|
||||||
@@ -539,6 +539,12 @@ public class InlineTestGenerated extends AbstractInlineTest {
|
|||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/inline/inlineVariableOrProperty"), Pattern.compile("^(\\w+)\\.kt$"), TargetBackend.ANY, true);
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/inline/inlineVariableOrProperty"), Pattern.compile("^(\\w+)\\.kt$"), TargetBackend.ANY, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("AppendToCollection.kt")
|
||||||
|
public void testAppendToCollection() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/inlineVariableOrProperty/AppendToCollection.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("Basic.kt")
|
@TestMetadata("Basic.kt")
|
||||||
public void testBasic() throws Exception {
|
public void testBasic() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/inlineVariableOrProperty/Basic.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/inlineVariableOrProperty/Basic.kt");
|
||||||
@@ -1000,6 +1006,12 @@ public class InlineTestGenerated extends AbstractInlineTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("KT17489.kt")
|
||||||
|
public void testKT17489() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/inlineVariableOrProperty/property/KT17489.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("keepImport.kt")
|
@TestMetadata("keepImport.kt")
|
||||||
public void testKeepImport() throws Exception {
|
public void testKeepImport() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/inlineVariableOrProperty/property/keepImport.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/inlineVariableOrProperty/property/keepImport.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user