Rename: Fix processing of references to synthetic Java properties

#KT-17742 Fixed
This commit is contained in:
Alexey Sedunov
2018-03-05 13:54:14 +03:00
parent 862dc76ed9
commit 8978c4b9e8
19 changed files with 256 additions and 65 deletions
@@ -1684,7 +1684,7 @@ public class KotlinTypeMapper {
@Nullable
public static String getModuleNameSuffix(@NotNull String name) {
int indexOfDollar = name.indexOf('$');
return indexOfDollar >=0 ? name.substring(indexOfDollar + 1) : null;
return indexOfDollar >= 0 ? name.substring(indexOfDollar + 1) : null;
}
@Nullable
@@ -22,14 +22,23 @@ import com.intellij.psi.PsiMethod
import com.intellij.util.SmartList
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator
import org.jetbrains.kotlin.idea.core.copied
import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.lexer.KtSingleValueToken
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor
import org.jetbrains.kotlin.types.expressions.OperatorConventions
import org.jetbrains.kotlin.utils.addIfNotNull
sealed class SyntheticPropertyAccessorReference(expression: KtNameReferenceExpression, private val getter: Boolean) : KtSimpleReference<KtNameReferenceExpression>(expression) {
sealed class SyntheticPropertyAccessorReference(expression: KtNameReferenceExpression, private val getter: Boolean) :
KtSimpleReference<KtNameReferenceExpression>(expression) {
override fun getTargetDescriptors(context: BindingContext): Collection<DeclarationDescriptor> {
val descriptors = super.getTargetDescriptors(context)
if (descriptors.none { it is SyntheticJavaPropertyDescriptor }) return emptyList()
@@ -39,8 +48,7 @@ sealed class SyntheticPropertyAccessorReference(expression: KtNameReferenceExpre
if (descriptor is SyntheticJavaPropertyDescriptor) {
if (getter) {
result.add(descriptor.getMethod)
}
else {
} else {
result.addIfNotNull(descriptor.setMethod)
}
}
@@ -50,6 +58,7 @@ sealed class SyntheticPropertyAccessorReference(expression: KtNameReferenceExpre
override fun isReferenceTo(element: PsiElement?): Boolean {
if (element !is PsiMethod || !isAccessorName(element.name)) return false
if (!getter && expression.readWriteAccess(true) == ReferenceAccess.READ) return false
return super.isReferenceTo(element)
}
@@ -70,19 +79,115 @@ sealed class SyntheticPropertyAccessorReference(expression: KtNameReferenceExpre
return expression
}
private fun KtExpression.createCall(
psiFactory: KtPsiFactory,
newName: String? = null,
argument: KtExpression? = null
): KtExpression {
return if (this is KtQualifiedExpression) {
copied().also {
val selector = it.getQualifiedElementSelector() as? KtExpression
selector?.replace(selector.createCall(psiFactory, newName, argument))
}
} else {
psiFactory.buildExpression {
if (newName != null) {
appendFixedText(newName)
} else {
appendExpression(this@createCall)
}
appendFixedText("(")
if (argument != null) {
appendExpression(argument)
}
appendFixedText(")")
}
}
}
override fun handleElementRename(newElementName: String?): PsiElement? {
if (!Name.isValidIdentifier(newElementName!!)) return expression
val newNameAsName = Name.identifier(newElementName)
val newName = if (getter) {
SyntheticJavaPropertyDescriptor.propertyNameByGetMethodName(newNameAsName)
}
else {
} else {
//TODO: it's not correct
//TODO: setIsY -> setIsIsY bug
SyntheticJavaPropertyDescriptor.propertyNameBySetMethodName(newNameAsName, withIsPrefix = expression.getReferencedNameAsName().asString().startsWith("is"))
SyntheticJavaPropertyDescriptor.propertyNameBySetMethodName(
newNameAsName,
withIsPrefix = expression.getReferencedNameAsName().asString().startsWith(
"is"
)
)
}
// get/set becomes ordinary method
if (newName == null) {
val psiFactory = KtPsiFactory(expression)
val newGetterName = if (getter) newElementName else JvmAbi.getterName(expression.getReferencedName())
if (expression.readWriteAccess(false) == ReferenceAccess.READ) {
return expression.replaced(expression.createCall(psiFactory, newGetterName))
}
val newSetterName = if (getter) JvmAbi.setterName(expression.getReferencedName()) else newElementName
val fullExpression = expression.getQualifiedExpressionForSelectorOrThis()
fullExpression.getAssignmentByLHS()?.let { assignment ->
val rhs = assignment.right ?: return expression
val operationToken = assignment.operationToken as? KtSingleValueToken
val counterpartOp = OperatorConventions.ASSIGNMENT_OPERATION_COUNTERPARTS[operationToken]
val setterArgument = if (counterpartOp != null) {
val getterCall = if (getter) fullExpression.createCall(psiFactory, newGetterName) else fullExpression
psiFactory.createExpressionByPattern("$0 ${counterpartOp.value} $1", getterCall, rhs)
} else {
rhs
}
val newSetterCall = fullExpression.createCall(psiFactory, newSetterName, setterArgument)
return assignment.replaced(newSetterCall).getQualifiedElementSelector()
}
fullExpression.getStrictParentOfType<KtUnaryExpression>()?.let { unaryExpr ->
val operationToken = unaryExpr.operationToken as? KtSingleValueToken ?: return expression
if (operationToken !in OperatorConventions.INCREMENT_OPERATIONS) return expression
val operationName = OperatorConventions.getNameForOperationSymbol(operationToken)
val originalValue = if (getter) fullExpression.createCall(psiFactory, newGetterName) else fullExpression
val incDecValue = psiFactory.createExpressionByPattern("$0.$operationName()", originalValue)
val parent = unaryExpr.parent
val context = parent.parentsWithSelf.firstOrNull { it is KtBlockExpression || it is KtDeclarationContainer }
if (context == parent || context == null) {
val newSetterCall = fullExpression.createCall(psiFactory, newSetterName, incDecValue)
return unaryExpr.replaced(newSetterCall).getQualifiedElementSelector()
} else {
val anchor = parent.parentsWithSelf.firstOrNull { it.parent == context }
val validator = NewDeclarationNameValidator(
context,
anchor,
NewDeclarationNameValidator.Target.VARIABLES
)
val varName = KotlinNameSuggester.suggestNamesByExpressionAndType(
unaryExpr,
null,
unaryExpr.analyze(),
validator,
"p"
).first()
val isPrefix = unaryExpr is KtPrefixExpression
val varInitializer = if (isPrefix) incDecValue else originalValue
val newVar = psiFactory.createDeclarationByPattern<KtProperty>("val $varName = $0", varInitializer)
val setterArgument = psiFactory.createExpression(if (isPrefix) varName else "$varName.$operationName()")
val newSetterCall = fullExpression.createCall(psiFactory, newSetterName, setterArgument)
val newLine = psiFactory.createNewLine()
context.addBefore(newVar, anchor)
context.addBefore(newLine, anchor)
context.addBefore(newSetterCall, anchor)
return unaryExpr.replaced(psiFactory.createExpression(varName))
}
}
return expression
}
if (newName == null) return expression //TODO: handle the case when get/set becomes ordinary method
return renameByPropertyName(newName.identifier)
}
+3
View File
@@ -512,6 +512,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
<psi.treeChangePreprocessor implementation="org.jetbrains.kotlin.idea.caches.KotlinPackageStatementPsiTreeChangePreprocessor"/>
<renamePsiElementProcessor id="KotlinAwareJavaGetter"
implementation="org.jetbrains.kotlin.idea.refactoring.rename.KotlinAwareJavaGetterRenameProcessor"
order="first"/>
<renamePsiElementProcessor id="KotlinClass"
implementation="org.jetbrains.kotlin.idea.refactoring.rename.RenameKotlinClassifierProcessor"
order="first"/>
@@ -0,0 +1,39 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.refactoring.rename
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiReference
import com.intellij.psi.PsiType
import com.intellij.refactoring.rename.RenameJavaMethodProcessor
import org.jetbrains.kotlin.asJava.elements.KtLightMethod
import org.jetbrains.kotlin.idea.references.SyntheticPropertyAccessorReference
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor
import org.jetbrains.kotlin.utils.ifEmpty
class KotlinAwareJavaGetterRenameProcessor : RenameJavaMethodProcessor() {
override fun canProcessElement(element: PsiElement) = element is PsiMethod && element !is KtLightMethod && JvmAbi.isGetterName(element.name)
override fun findReferences(element: PsiElement): MutableCollection<PsiReference> {
val getterReferences = super.findReferences(element)
val getter = element as? PsiMethod ?: return getterReferences
val propertyName = SyntheticJavaPropertyDescriptor.propertyNameByGetMethodName(Name.identifier(getter.name)) ?: return getterReferences
val setterName = JvmAbi.setterName(propertyName.asString())
val containingClass = getter.containingClass ?: return getterReferences
val setterReferences = containingClass
.findMethodsByName(setterName, true)
.filter { it.parameters.size == 1 && it.returnType == PsiType.VOID }
.flatMap { super.findReferences(it).filterIsInstance<SyntheticPropertyAccessorReference.Setter>() }
.ifEmpty { return getterReferences }
return ArrayList<PsiReference>(getterReferences.size + setterReferences.size).apply {
addAll(getterReferences)
setterReferences.mapTo(this) { SyntheticPropertyAccessorReference.Getter(it.expression) }
}
}
}
@@ -35,8 +35,7 @@ import org.jetbrains.kotlin.asJava.elements.KtLightElement
import org.jetbrains.kotlin.asJava.elements.KtLightMethod
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper.InternalNameMapper.demangleInternalName
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper.InternalNameMapper.getModuleNameSuffix
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper.InternalNameMapper.*
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.refactoring.Pass
@@ -183,19 +182,20 @@ class RenameKotlinFunctionProcessor : RenameKotlinPsiProcessor() {
override fun prepareRenaming(element: PsiElement, newName: String, allRenames: MutableMap<PsiElement, String>, scope: SearchScope) {
super.prepareRenaming(element, newName, allRenames, scope)
val originalName = (element.unwrapped as? KtNamedFunction)?.name ?: ""
if (element is KtLightMethod && getJvmName(element) == null) {
(element.kotlinOrigin as? KtNamedFunction)?.let { allRenames[it] = newName }
}
if (element is FunctionWithSupersWrapper) {
allRenames.remove(element)
}
val originalName = (element.unwrapped as? KtNamedFunction)?.name ?: return
for (declaration in ((element as? FunctionWithSupersWrapper)?.supers ?: listOf(element))) {
val psiMethod = wrapPsiMethod(declaration) ?: continue
allRenames[declaration] = newName
val baseName = psiMethod.name
val newBaseName = if (demangleInternalName(baseName) == originalName) "$newName$${getModuleNameSuffix(baseName)}" else newName
val newBaseName = if (demangleInternalName(baseName) == originalName) {
mangleInternalName(newName, getModuleNameSuffix(baseName)!!)
} else newName
if (psiMethod.containingClass != null) {
psiMethod.forEachOverridingMethod(scope) { it ->
val overrider = (it as? PsiMirrorElement)?.prototype as? PsiMethod ?: it
@@ -227,7 +227,7 @@ class RenameKotlinFunctionProcessor : RenameKotlinPsiProcessor() {
ambiguousImportUsages += usage
}
else {
if (!renameUsageIfPossible(usage, element, newName)) {
if (!renameMangledUsageIfPossible(usage, element, newName)) {
simpleUsages += usage
}
}
@@ -46,19 +46,7 @@ import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded
import org.jetbrains.kotlin.resolve.ImportPath
import java.util.ArrayList
import kotlin.collections.Collection
import kotlin.collections.HashSet
import kotlin.collections.List
import kotlin.collections.MutableMap
import kotlin.collections.flatMapTo
import kotlin.collections.forEach
import kotlin.collections.isEmpty
import kotlin.collections.mapNotNullTo
import kotlin.collections.none
import kotlin.collections.plusAssign
import kotlin.collections.set
import kotlin.collections.toMutableList
import kotlin.collections.toTypedArray
import kotlin.collections.*
abstract class RenameKotlinPsiProcessor : RenamePsiElementProcessor() {
class MangledJavaRefUsageInfo(
@@ -151,15 +139,16 @@ abstract class RenameKotlinPsiProcessor : RenamePsiElementProcessor() {
&& ref.multiResolve(false).mapNotNullTo(HashSet()) { it.element?.unwrapped }.size > 1
}
protected fun renameUsageIfPossible(usage: UsageInfo, element: PsiElement, newName: String): Boolean {
var chosenName: String? = null
if (usage is MangledJavaRefUsageInfo) {
chosenName = KotlinTypeMapper.InternalNameMapper.mangleInternalName(newName, usage.manglingSuffix)
protected fun renameMangledUsageIfPossible(usage: UsageInfo, element: PsiElement, newName: String): Boolean {
val chosenName = if (usage is MangledJavaRefUsageInfo) {
KotlinTypeMapper.InternalNameMapper.mangleInternalName(newName, usage.manglingSuffix)
} else {
val reference = usage.reference
if (reference is KtReference) {
chosenName = (if (element is KtLightMethod && element.isMangled) KotlinTypeMapper.InternalNameMapper.demangleInternalName(newName) else null) ?: newName
}
if (element is KtLightMethod && element.isMangled) {
KotlinTypeMapper.InternalNameMapper.demangleInternalName(newName)
} else null
} else null
}
if (chosenName == null) return false
usage.reference?.handleElementRename(chosenName)
@@ -174,7 +163,7 @@ abstract class RenameKotlinPsiProcessor : RenamePsiElementProcessor() {
) {
val simpleUsages = ArrayList<UsageInfo>(usages.size)
for (usage in usages) {
if (renameUsageIfPossible(usage, element, newName)) continue
if (renameMangledUsageIfPossible(usage, element, newName)) continue
simpleUsages += usage
}
@@ -186,7 +175,7 @@ abstract class RenameKotlinPsiProcessor : RenamePsiElementProcessor() {
element.ambiguousImportUsages?.forEach {
val ref = it.reference as? PsiPolyVariantReference ?: return@forEach
if (ref.multiResolve(false).isEmpty()) {
if (!renameUsageIfPossible(it, element, newName)) {
if (!renameMangledUsageIfPossible(it, element, newName)) {
ref.handleElementRename(newName)
}
}
@@ -24,7 +24,6 @@ import com.intellij.psi.search.UsageSearchContext
import com.intellij.psi.search.searches.MethodReferencesSearch
import com.intellij.psi.util.MethodSignatureUtil
import com.intellij.psi.util.TypeConversionUtil
import org.jetbrains.kotlin.asJava.elements.KtLightMethod
import org.jetbrains.kotlin.asJava.toLightMethods
import org.jetbrains.kotlin.compatibility.ExecutorProcessor
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
@@ -32,10 +31,8 @@ import org.jetbrains.kotlin.idea.references.SyntheticPropertyAccessorReference
import org.jetbrains.kotlin.idea.references.readWriteAccess
import org.jetbrains.kotlin.idea.search.restrictToKotlinSources
import org.jetbrains.kotlin.idea.util.runReadActionInSmartMode
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.load.java.getPropertyNamesCandidatesByAccessorName
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper.InternalNameMapper.demangleInternalName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction
@@ -50,30 +47,10 @@ class KotlinOverridingMethodReferenceSearcher : MethodUsagesSearcher() {
return
}
if (method is KtLightMethod) {
method.kotlinOrigin?.let { ktElement ->
val (mayBeMangled, name) = p.project.runReadActionInSmartMode {
ktElement.hasModifier(KtTokens.PRIVATE_KEYWORD) || ktElement.hasModifier(KtTokens.INTERNAL_KEYWORD)
} to method.name
if (mayBeMangled && name != null) {
val demangledName = demangleInternalName(name)
if (demangledName != null) {
val wrappedMethod = object : KtLightMethod by method {
override fun getName(): String = demangledName
}
processQuery(
MethodReferencesSearch.SearchParameters(wrappedMethod, p.scopeDeterminedByUser, p.isStrictSignatureSearch, p.optimizer),
consumer
)
}
}
}
}
val searchScope = p.project.runReadActionInSmartMode {
p.effectiveSearchScope
.intersectWith(method.useScope)
.restrictToKotlinSources()
.intersectWith(method.useScope)
.restrictToKotlinSources()
}
if (searchScope === GlobalSearchScope.EMPTY_SCOPE) return
@@ -133,8 +110,8 @@ class KotlinOverridingMethodReferenceSearcher : MethodUsagesSearcher() {
}
fun countNonFinalLightMethods() = refElement
.toLightMethods()
.filterNot { it.hasModifierProperty(PsiModifier.FINAL) }
.toLightMethods()
.filterNot { it.hasModifierProperty(PsiModifier.FINAL) }
val lightMethods = when (refElement) {
is KtProperty, is KtParameter -> {
@@ -0,0 +1,4 @@
public class SyntheticProperty {
public int foo() { return 1; }
public void setSyntheticA(int x) { }
}
@@ -0,0 +1,12 @@
fun synthesize(p: SyntheticProperty) {
val v1 = p.foo()
p.setSyntheticA(1)
p.setSyntheticA(p.foo() + 2)
p.setSyntheticA(p.foo().inc())
val syntheticA = p.foo()
p.setSyntheticA(syntheticA.inc())
val x = syntheticA
val i = p.foo().inc()
p.setSyntheticA(i)
val y = i
}
@@ -0,0 +1,4 @@
public class SyntheticProperty {
public int /*rename*/getSyntheticA() { return 1; }
public void setSyntheticA(int x) { }
}
@@ -0,0 +1,8 @@
fun synthesize(p: SyntheticProperty) {
val v1 = p.syntheticA
p.syntheticA = 1
p.syntheticA += 2
p.syntheticA++
val x = p.syntheticA++
val y = ++p.syntheticA
}
@@ -0,0 +1,6 @@
{
"type": "AUTO_DETECT",
"mainFile": "SyntheticProperty.java",
"newName": "foo",
"withRuntime": "true"
}
@@ -0,0 +1,4 @@
public class SyntheticProperty {
public int getSyntheticA() { return 1; }
public void foo(int x) { }
}
@@ -0,0 +1,12 @@
fun synthesize(p: SyntheticProperty) {
val v1 = p.syntheticA
p.foo(1)
p.foo(p.syntheticA + 2)
p.foo(p.syntheticA.inc())
val syntheticA = p.syntheticA
p.foo(syntheticA.inc())
val x = syntheticA
val i = p.syntheticA.inc()
p.foo(i)
val y = i
}
@@ -0,0 +1,4 @@
public class SyntheticProperty {
public int getSyntheticA() { return 1; }
public void /*rename*/setSyntheticA(int x) { }
}
@@ -0,0 +1,8 @@
fun synthesize(p: SyntheticProperty) {
val v1 = p.syntheticA
p.syntheticA = 1
p.syntheticA += 2
p.syntheticA++
val x = p.syntheticA++
val y = ++p.syntheticA
}
@@ -0,0 +1,6 @@
{
"type": "AUTO_DETECT",
"mainFile": "SyntheticProperty.java",
"newName": "foo",
"withRuntime": "true"
}
@@ -1,6 +1,6 @@
import testing.JavaClass
fun usages(javaClass: JavaClass) {
javaClass.something = javaClass.somethingNew + 1
javaClass.somethingNew = javaClass.somethingNew + 1
javaClass.somethingNew++
}
@@ -234,6 +234,16 @@ public class RenameTestGenerated extends AbstractRenameTest {
runTest("idea/testData/refactoring/rename/javaEnumValueOf/javaEnumValueOf.test");
}
@TestMetadata("javaGetterToOrdinaryMethod/javaGetterToOrdinaryMethod.test")
public void testJavaGetterToOrdinaryMethod_JavaGetterToOrdinaryMethod() throws Exception {
runTest("idea/testData/refactoring/rename/javaGetterToOrdinaryMethod/javaGetterToOrdinaryMethod.test");
}
@TestMetadata("javaSetterToOrdinaryMethod/javaSetterToOrdinaryMethod.test")
public void testJavaSetterToOrdinaryMethod_JavaSetterToOrdinaryMethod() throws Exception {
runTest("idea/testData/refactoring/rename/javaSetterToOrdinaryMethod/javaSetterToOrdinaryMethod.test");
}
@TestMetadata("labeledAnonymousFunByLabel/labeledLambdaByLabel.test")
public void testLabeledAnonymousFunByLabel_LabeledLambdaByLabel() throws Exception {
runTest("idea/testData/refactoring/rename/labeledAnonymousFunByLabel/labeledLambdaByLabel.test");