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)