diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineCallableProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineCallableProcessor.kt index fe1f9ece66e..1183cf401c8 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineCallableProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineCallableProcessor.kt @@ -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 = 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( diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineValDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineValDialog.kt index 7f5d72cf9b4..6bf7bd19c47 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineValDialog.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineValDialog.kt @@ -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 + 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() diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineValHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineValHandler.kt index 644adbd558a..420ce8ed825 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineValHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineValHandler.kt @@ -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() - 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) { + private fun reportAmbiguousAssignment(project: Project, editor: Editor?, name: String, assignments: Collection) { 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) diff --git a/idea/testData/refactoring/inline/inlineVariableOrProperty/AppendToCollection.kt b/idea/testData/refactoring/inline/inlineVariableOrProperty/AppendToCollection.kt new file mode 100644 index 00000000000..47871b8a2b9 --- /dev/null +++ b/idea/testData/refactoring/inline/inlineVariableOrProperty/AppendToCollection.kt @@ -0,0 +1,4 @@ +fun foo(list: MutableList) { + val list1 = list + list1 += 10 +} \ No newline at end of file diff --git a/idea/testData/refactoring/inline/inlineVariableOrProperty/AppendToCollection.kt.after b/idea/testData/refactoring/inline/inlineVariableOrProperty/AppendToCollection.kt.after new file mode 100644 index 00000000000..4314906934d --- /dev/null +++ b/idea/testData/refactoring/inline/inlineVariableOrProperty/AppendToCollection.kt.after @@ -0,0 +1,3 @@ +fun foo(list: MutableList) { + list += 10 +} \ No newline at end of file diff --git a/idea/testData/refactoring/inline/inlineVariableOrProperty/property/KT17489.kt b/idea/testData/refactoring/inline/inlineVariableOrProperty/property/KT17489.kt new file mode 100644 index 00000000000..cc5b0a231c7 --- /dev/null +++ b/idea/testData/refactoring/inline/inlineVariableOrProperty/property/KT17489.kt @@ -0,0 +1,10 @@ +class Predicate(val x: Int) { + operator fun plusAssign(y: Int) {} + + operator fun unaryMinus() = Predicate(-x) +} + +fun test(p: Predicate) { + val x = -p + x += 42 +} \ No newline at end of file diff --git a/idea/testData/refactoring/inline/inlineVariableOrProperty/property/KT17489.kt.after b/idea/testData/refactoring/inline/inlineVariableOrProperty/property/KT17489.kt.after new file mode 100644 index 00000000000..dd890cb2455 --- /dev/null +++ b/idea/testData/refactoring/inline/inlineVariableOrProperty/property/KT17489.kt.after @@ -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 +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/inline/InlineTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/inline/InlineTestGenerated.java index bc1b13237dd..0a591690d48 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/inline/InlineTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/inline/InlineTestGenerated.java @@ -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");