Rename: Implement conflict analysis for parameters. Qualify property references to avoid shadowing by parameters
#KT-10687 Fixed (cherry picked from commit 7d6466d)
This commit is contained in:
@@ -237,6 +237,7 @@
|
|||||||
- [`KT-9444`](https://youtrack.jetbrains.com/issue/KT-9444) Rename dialog: Allow typing any identifier without backquotes
|
- [`KT-9444`](https://youtrack.jetbrains.com/issue/KT-9444) Rename dialog: Allow typing any identifier without backquotes
|
||||||
- [`KT-9446`](https://youtrack.jetbrains.com/issue/KT-9446) Warn about calls with default arguments if function to be renamed inherits default values from some base function which is excluded from rename
|
- [`KT-9446`](https://youtrack.jetbrains.com/issue/KT-9446) Warn about calls with default arguments if function to be renamed inherits default values from some base function which is excluded from rename
|
||||||
- [`KT-10713`](https://youtrack.jetbrains.com/issue/KT-10713) Skip read-only declarations when renaming parameters
|
- [`KT-10713`](https://youtrack.jetbrains.com/issue/KT-10713) Skip read-only declarations when renaming parameters
|
||||||
|
- [`KT-10687`](https://youtrack.jetbrains.com/issue/KT-10687) Qualify property references to avoid shadowing by parameters
|
||||||
- [`KT-12543`](https://youtrack.jetbrains.com/issue/KT-12543) Qualify property references with 'this' to avoid renaming conflicts
|
- [`KT-12543`](https://youtrack.jetbrains.com/issue/KT-12543) Qualify property references with 'this' to avoid renaming conflicts
|
||||||
|
|
||||||
#### Java to Kotlin converter
|
#### Java to Kotlin converter
|
||||||
|
|||||||
+29
@@ -20,18 +20,41 @@ import com.intellij.psi.PsiElement
|
|||||||
import com.intellij.psi.PsiMethod
|
import com.intellij.psi.PsiMethod
|
||||||
import com.intellij.psi.PsiNamedElement
|
import com.intellij.psi.PsiNamedElement
|
||||||
import com.intellij.psi.search.SearchScope
|
import com.intellij.psi.search.SearchScope
|
||||||
|
import com.intellij.refactoring.listeners.RefactoringElementListener
|
||||||
|
import com.intellij.usageView.UsageInfo
|
||||||
|
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
|
||||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||||
import org.jetbrains.kotlin.idea.refactoring.canRefactor
|
import org.jetbrains.kotlin.idea.refactoring.canRefactor
|
||||||
import org.jetbrains.kotlin.idea.refactoring.getAffectedCallables
|
import org.jetbrains.kotlin.idea.refactoring.getAffectedCallables
|
||||||
import org.jetbrains.kotlin.psi.KtCallableDeclaration
|
import org.jetbrains.kotlin.psi.KtCallableDeclaration
|
||||||
|
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
||||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||||
import org.jetbrains.kotlin.psi.KtParameter
|
import org.jetbrains.kotlin.psi.KtParameter
|
||||||
import org.jetbrains.kotlin.resolve.OverrideResolver
|
import org.jetbrains.kotlin.resolve.OverrideResolver
|
||||||
|
import org.jetbrains.kotlin.utils.SmartList
|
||||||
|
|
||||||
class RenameKotlinParameterProcessor : RenameKotlinPsiProcessor() {
|
class RenameKotlinParameterProcessor : RenameKotlinPsiProcessor() {
|
||||||
override fun canProcessElement(element: PsiElement) = element is KtParameter && element.ownerFunction is KtNamedFunction
|
override fun canProcessElement(element: PsiElement) = element is KtParameter && element.ownerFunction is KtNamedFunction
|
||||||
|
|
||||||
|
override fun findCollisions(
|
||||||
|
element: PsiElement,
|
||||||
|
newName: String?,
|
||||||
|
allRenames: MutableMap<out PsiElement, String>,
|
||||||
|
result: MutableList<UsageInfo>
|
||||||
|
) {
|
||||||
|
if (newName == null) return
|
||||||
|
val declaration = element.namedUnwrappedElement as? KtNamedDeclaration ?: return
|
||||||
|
val descriptor = declaration.resolveToDescriptor() as VariableDescriptor
|
||||||
|
|
||||||
|
val collisions = SmartList<UsageInfo>()
|
||||||
|
checkRedeclarations(descriptor, newName, collisions)
|
||||||
|
checkOriginalUsagesRetargeting(declaration, newName, result, collisions)
|
||||||
|
checkNewNameUsagesRetargeting(declaration, newName, collisions)
|
||||||
|
result += collisions
|
||||||
|
}
|
||||||
|
|
||||||
override fun prepareRenaming(element: PsiElement, newName: String?, allRenames: MutableMap<PsiElement, String>, scope: SearchScope) {
|
override fun prepareRenaming(element: PsiElement, newName: String?, allRenames: MutableMap<PsiElement, String>, scope: SearchScope) {
|
||||||
super.prepareRenaming(element, newName, allRenames, scope)
|
super.prepareRenaming(element, newName, allRenames, scope)
|
||||||
|
|
||||||
@@ -51,4 +74,10 @@ class RenameKotlinParameterProcessor : RenameKotlinPsiProcessor() {
|
|||||||
allRenames[parameter] = newName
|
allRenames[parameter] = newName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun renameElement(element: PsiElement, newName: String?, usages: Array<out UsageInfo>, listener: RefactoringElementListener?) {
|
||||||
|
super.renameElement(element, newName, usages, listener)
|
||||||
|
|
||||||
|
usages.forEach { (it as? KtResolvableCollisionUsageInfo)?.apply() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator
|
|||||||
import org.jetbrains.kotlin.idea.core.copied
|
import org.jetbrains.kotlin.idea.core.copied
|
||||||
import org.jetbrains.kotlin.idea.imports.importableFqName
|
import org.jetbrains.kotlin.idea.imports.importableFqName
|
||||||
import org.jetbrains.kotlin.idea.refactoring.explicateAsText
|
import org.jetbrains.kotlin.idea.refactoring.explicateAsText
|
||||||
|
import org.jetbrains.kotlin.idea.references.mainReference
|
||||||
import org.jetbrains.kotlin.idea.refactoring.getThisLabelName
|
import org.jetbrains.kotlin.idea.refactoring.getThisLabelName
|
||||||
import org.jetbrains.kotlin.idea.references.mainReference
|
import org.jetbrains.kotlin.idea.references.mainReference
|
||||||
import org.jetbrains.kotlin.idea.search.and
|
import org.jetbrains.kotlin.idea.search.and
|
||||||
@@ -59,6 +60,7 @@ import org.jetbrains.kotlin.resolve.source.getPsi
|
|||||||
import org.jetbrains.kotlin.types.ErrorUtils
|
import org.jetbrains.kotlin.types.ErrorUtils
|
||||||
import org.jetbrains.kotlin.utils.SmartList
|
import org.jetbrains.kotlin.utils.SmartList
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptyList
|
import org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptyList
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
internal fun ResolvedCall<*>.noReceivers() = dispatchReceiver == null && extensionReceiver == null
|
internal fun ResolvedCall<*>.noReceivers() = dispatchReceiver == null && extensionReceiver == null
|
||||||
|
|
||||||
@@ -80,24 +82,31 @@ internal fun checkRedeclarations(
|
|||||||
newName: String,
|
newName: String,
|
||||||
result: MutableList<UsageInfo>
|
result: MutableList<UsageInfo>
|
||||||
) {
|
) {
|
||||||
val containingDescriptor = descriptor.containingDeclaration
|
fun getSiblingWithNewName(): DeclarationDescriptor? {
|
||||||
val containingScope = when (containingDescriptor) {
|
if (descriptor is ValueParameterDescriptor) {
|
||||||
is ClassDescriptor -> containingDescriptor.unsubstitutedMemberScope
|
return descriptor.containingDeclaration.valueParameters.firstOrNull { it.name.asString() == newName }
|
||||||
is PackageFragmentDescriptor -> containingDescriptor.getMemberScope()
|
}
|
||||||
else -> return
|
|
||||||
}
|
val containingDescriptor = descriptor.containingDeclaration
|
||||||
val descriptorKindFilter = when (descriptor) {
|
val containingScope = when (containingDescriptor) {
|
||||||
is ClassDescriptor -> DescriptorKindFilter.CLASSIFIERS
|
is ClassDescriptor -> containingDescriptor.unsubstitutedMemberScope
|
||||||
is PropertyDescriptor -> DescriptorKindFilter.VARIABLES
|
is PackageFragmentDescriptor -> containingDescriptor.getMemberScope()
|
||||||
else -> return
|
else -> return null
|
||||||
}
|
}
|
||||||
containingScope.getDescriptorsFiltered(descriptorKindFilter) { it.asString() == newName }.firstOrNull()?.let { candidateDescriptor ->
|
val descriptorKindFilter = when (descriptor) {
|
||||||
val candidate = (candidateDescriptor as? DeclarationDescriptorWithSource)?.source?.getPsi() as? KtNamedDeclaration ?: return
|
is ClassDescriptor -> DescriptorKindFilter.CLASSIFIERS
|
||||||
val what = candidate.renderDescription().capitalize()
|
is PropertyDescriptor -> DescriptorKindFilter.VARIABLES
|
||||||
val where = candidate.representativeContainer()?.renderDescription() ?: return
|
else -> return null
|
||||||
val message = "$what is already declared in $where"
|
}
|
||||||
result += BasicUnresolvableCollisionUsageInfo(candidate, candidate, message)
|
return containingScope.getDescriptorsFiltered(descriptorKindFilter) { it.asString() == newName }.firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val candidateDescriptor = getSiblingWithNewName() ?: return
|
||||||
|
val candidate = (candidateDescriptor as? DeclarationDescriptorWithSource)?.source?.getPsi() as? KtNamedDeclaration ?: return
|
||||||
|
val what = candidate.renderDescription().capitalize()
|
||||||
|
val where = candidate.representativeContainer()?.renderDescription() ?: return
|
||||||
|
val message = "$what is already declared in $where"
|
||||||
|
result += BasicUnresolvableCollisionUsageInfo(candidate, candidate, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun LexicalScope.getRelevantDescriptors(
|
private fun LexicalScope.getRelevantDescriptors(
|
||||||
@@ -261,6 +270,32 @@ internal fun checkNewNameUsagesRetargeting(
|
|||||||
) {
|
) {
|
||||||
val currentName = declaration.name ?: return
|
val currentName = declaration.name ?: return
|
||||||
val descriptor = declaration.resolveToDescriptor()
|
val descriptor = declaration.resolveToDescriptor()
|
||||||
|
|
||||||
|
if (declaration is KtParameter && !declaration.hasValOrVar()) {
|
||||||
|
val ownerFunction = declaration.ownerFunction
|
||||||
|
val searchScope = (if (ownerFunction is KtPrimaryConstructor) ownerFunction.containingClassOrObject else ownerFunction) ?: return
|
||||||
|
|
||||||
|
val usagesByCandidate = LinkedHashMap<PsiElement, MutableList<UsageInfo>>()
|
||||||
|
|
||||||
|
searchScope.accept(
|
||||||
|
object: KtTreeVisitorVoid() {
|
||||||
|
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
|
||||||
|
if (expression.getReferencedName() != newName) return
|
||||||
|
val ref = expression.mainReference
|
||||||
|
val candidate = ref.resolve() as? PsiNamedElement ?: return
|
||||||
|
usagesByCandidate.getOrPut(candidate) { SmartList() }.add(MoveRenameUsageInfo(ref, candidate))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
for ((candidate, usages) in usagesByCandidate) {
|
||||||
|
checkUsagesRetargeting(candidate, declaration, currentName, false, listOf(descriptor), usages, newUsages)
|
||||||
|
usages.filterIsInstanceTo<KtResolvableCollisionUsageInfo, MutableList<UsageInfo>>(newUsages)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for (candidateDescriptor in declaration.getResolutionScope().getRelevantDescriptors(declaration, newName)) {
|
for (candidateDescriptor in declaration.getResolutionScope().getRelevantDescriptors(declaration, newName)) {
|
||||||
val candidate = DescriptorToSourceUtilsIde.getAnyDeclaration(declaration.project, candidateDescriptor) as? PsiNamedElement ?: continue
|
val candidate = DescriptorToSourceUtilsIde.getAnyDeclaration(declaration.project, candidateDescriptor) as? PsiNamedElement ?: continue
|
||||||
val usages = ReferencesSearch
|
val usages = ReferencesSearch
|
||||||
|
|||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
class Foo {
|
||||||
|
var foo: Int = 0
|
||||||
|
var bar: Int = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun makeFoo(foo: Int, _bar: Int) = Foo().apply {
|
||||||
|
this.foo = foo
|
||||||
|
bar = _bar
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
class Foo {
|
||||||
|
var foo: Int = 0
|
||||||
|
var bar: Int = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun makeFoo(/*rename*/_foo: Int, _bar: Int) = Foo().apply {
|
||||||
|
foo = _foo
|
||||||
|
bar = _bar
|
||||||
|
}
|
||||||
Vendored
+6
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"type": "MARKED_ELEMENT",
|
||||||
|
"mainFile": "test/test.kt",
|
||||||
|
"newName": "foo",
|
||||||
|
"withRuntime": "true"
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
fun foo(/*rename*/a: Int, b: String) {
|
||||||
|
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"type": "MARKED_ELEMENT",
|
||||||
|
"mainFile": "test/test.kt",
|
||||||
|
"newName": "b",
|
||||||
|
"withRuntime": "true",
|
||||||
|
"hint": "Parameter 'b' is already declared in function 'foo'"
|
||||||
|
}
|
||||||
@@ -119,6 +119,12 @@ public class RenameTestGenerated extends AbstractRenameTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("clashParameterWithProperty/clashParameterWithProperty.test")
|
||||||
|
public void testClashParameterWithProperty_ClashParameterWithProperty() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/clashParameterWithProperty/clashParameterWithProperty.test");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("clashWithInnerClass/clashWithInnerClass.test")
|
@TestMetadata("clashWithInnerClass/clashWithInnerClass.test")
|
||||||
public void testClashWithInnerClass_ClashWithInnerClass() throws Exception {
|
public void testClashWithInnerClass_ClashWithInnerClass() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/clashWithInnerClass/clashWithInnerClass.test");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/clashWithInnerClass/clashWithInnerClass.test");
|
||||||
@@ -185,6 +191,12 @@ public class RenameTestGenerated extends AbstractRenameTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("parameterRedeclaration/parameterRedeclaration.test")
|
||||||
|
public void testParameterRedeclaration_ParameterRedeclaration() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/parameterRedeclaration/parameterRedeclaration.test");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("propertyAccidentalOverrideSubclass/propertyAccidentalOverrideSubclass.test")
|
@TestMetadata("propertyAccidentalOverrideSubclass/propertyAccidentalOverrideSubclass.test")
|
||||||
public void testPropertyAccidentalOverrideSubclass_PropertyAccidentalOverrideSubclass() throws Exception {
|
public void testPropertyAccidentalOverrideSubclass_PropertyAccidentalOverrideSubclass() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/propertyAccidentalOverrideSubclass/propertyAccidentalOverrideSubclass.test");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/propertyAccidentalOverrideSubclass/propertyAccidentalOverrideSubclass.test");
|
||||||
|
|||||||
Reference in New Issue
Block a user