Inline val: more correct and simple detection of write usages

#KT-17489 Fixed
This commit is contained in:
Valentin Kipyatkov
2017-05-22 19:21:44 +03:00
parent 55979aca74
commit 940ce27925
8 changed files with 69 additions and 34 deletions
@@ -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)
@@ -0,0 +1,4 @@
fun foo(list: MutableList<Int>) {
val <caret>list1 = list
list1 += 10
}
@@ -0,0 +1,3 @@
fun foo(list: MutableList<Int>) {
list += 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
}
@@ -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");