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.openapi.project.Project
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
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.stubindex.KotlinSourceFilterScope
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.KtCallableDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
|
||||
class KotlinInlineCallableProcessor(
|
||||
project: Project,
|
||||
@@ -44,7 +40,7 @@ class KotlinInlineCallableProcessor(
|
||||
private val reference: KtSimpleNameReference?,
|
||||
private val inlineThisOnly: Boolean,
|
||||
private val deleteAfter: Boolean,
|
||||
private val assignments: Set<PsiElement> = emptySet()
|
||||
private val statementToDelete: KtBinaryExpression? = null
|
||||
) : BaseRefactoringProcessor(project) {
|
||||
|
||||
private val kind = when (declaration) {
|
||||
@@ -71,11 +67,11 @@ class KotlinInlineCallableProcessor(
|
||||
declaration,
|
||||
myProject,
|
||||
commandName,
|
||||
{
|
||||
postAction = {
|
||||
if (deleteAfter) {
|
||||
if (usages.size == simpleNameUsages.size) {
|
||||
declaration.delete()
|
||||
assignments.forEach(PsiElement::delete)
|
||||
statementToDelete?.delete()
|
||||
}
|
||||
else {
|
||||
CommonRefactoringUtil.showErrorHint(
|
||||
|
||||
@@ -17,18 +17,18 @@
|
||||
package org.jetbrains.kotlin.idea.refactoring.inline
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.refactoring.JavaRefactoringSettings
|
||||
import com.intellij.refactoring.inline.InlineOptionsDialog
|
||||
import org.jetbrains.kotlin.idea.codeInliner.CallableUsageReplacementStrategy
|
||||
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
||||
import org.jetbrains.kotlin.psi.KtBinaryExpression
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
|
||||
class KotlinInlineValDialog(
|
||||
private val property: KtProperty,
|
||||
private val reference: KtSimpleNameReference?,
|
||||
private val replacementStrategy: CallableUsageReplacementStrategy,
|
||||
private val assignments: Set<PsiElement>
|
||||
private val assignmentToDelete: KtBinaryExpression?
|
||||
) : InlineOptionsDialog(property.project, true, property) {
|
||||
|
||||
private var occurrenceCount = initOccurrencesNumber(property)
|
||||
@@ -69,7 +69,7 @@ class KotlinInlineValDialog(
|
||||
KotlinInlineCallableProcessor(project, replacementStrategy, property, reference,
|
||||
inlineThisOnly = isInlineThisOnly,
|
||||
deleteAfter = !isInlineThisOnly && !isKeepTheDeclaration,
|
||||
assignments = assignments)
|
||||
statementToDelete = assignmentToDelete)
|
||||
)
|
||||
|
||||
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.refactoring.checkConflictsInteractively
|
||||
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.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.getQualifiedExpressionForSelectorOrThis
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import org.jetbrains.kotlin.types.expressions.OperatorConventions
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
|
||||
class KotlinInlineValHandler : InlineActionHandler() {
|
||||
@@ -82,28 +87,24 @@ class KotlinInlineValHandler : InlineActionHandler() {
|
||||
return showErrorHint(project, editor, "$kind '$name' is never used")
|
||||
}
|
||||
|
||||
val assignments = hashSetOf<PsiElement>()
|
||||
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 writeUsages = referenceExpressions.filter { it.readWriteAccess(useResolveForReadWrite = true) != ReferenceAccess.READ }
|
||||
|
||||
val initializerInDeclaration = declaration.initializer
|
||||
val initializer = if (initializerInDeclaration != null) {
|
||||
if (!assignments.isEmpty()) return reportAmbiguousAssignment(project, editor, name, assignments)
|
||||
initializerInDeclaration
|
||||
val initializer: KtExpression
|
||||
val assignment: KtBinaryExpression?
|
||||
if (initializerInDeclaration != null) {
|
||||
if (!writeUsages.isEmpty()) {
|
||||
return reportAmbiguousAssignment(project, editor, name, writeUsages)
|
||||
}
|
||||
initializer = initializerInDeclaration
|
||||
assignment = null
|
||||
}
|
||||
else {
|
||||
(assignments.singleOrNull() as? KtBinaryExpression)?.right
|
||||
?: return reportAmbiguousAssignment(project, editor, name, assignments)
|
||||
assignment = writeUsages.singleOrNull()
|
||||
?.getAssignmentByLHS()
|
||||
?.takeIf { it.operationToken == KtTokens.EQ }
|
||||
initializer = assignment?.right
|
||||
?: return reportAmbiguousAssignment(project, editor, name, writeUsages)
|
||||
}
|
||||
|
||||
val referencesInOriginalFile = referenceExpressions.filter { it.containingFile == file }
|
||||
@@ -133,7 +134,7 @@ class KotlinInlineValHandler : InlineActionHandler() {
|
||||
val replacement = replacementBuilder.prepareCodeToInline(initializerCopy, emptyList(), ::analyzeInitializerCopy)
|
||||
val replacementStrategy = CallableUsageReplacementStrategy(replacement)
|
||||
|
||||
val dialog = KotlinInlineValDialog(declaration, reference, replacementStrategy, assignments)
|
||||
val dialog = KotlinInlineValDialog(declaration, reference, replacementStrategy, assignment)
|
||||
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||
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 message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message(key, name))
|
||||
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);
|
||||
}
|
||||
|
||||
@TestMetadata("AppendToCollection.kt")
|
||||
public void testAppendToCollection() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/inlineVariableOrProperty/AppendToCollection.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("Basic.kt")
|
||||
public void testBasic() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/inlineVariableOrProperty/Basic.kt");
|
||||
@@ -1000,6 +1006,12 @@ public class InlineTestGenerated extends AbstractInlineTest {
|
||||
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")
|
||||
public void testKeepImport() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/inlineVariableOrProperty/property/keepImport.kt");
|
||||
|
||||
Reference in New Issue
Block a user