Rename: Warn about function name conflicts

#KT-13239 Fixed
This commit is contained in:
Alexey Sedunov
2016-08-11 16:25:31 +03:00
parent 385640c86f
commit 6a42802240
8 changed files with 78 additions and 10 deletions
+1
View File
@@ -263,6 +263,7 @@ Using 'this' as function argument in constructor of non-final class
- [`KT-13277`](https://youtrack.jetbrains.com/issue/KT-13277) Change Signature: Fix usage processing to prevent interfering with Python support plugin
- [`KT-13254`](https://youtrack.jetbrains.com/issue/KT-13254) Rename: Conflict detection for type parameters
- [`KT-13282`](https://youtrack.jetbrains.com/issue/KT-13282), [`KT-13283`](https://youtrack.jetbrains.com/issue/KT-13283) Rename: Fix name quoting for automatic renamers
- [`KT-13239`](https://youtrack.jetbrains.com/issue/KT-13239) Rename: Warn about function name conflicts
##### New features
@@ -80,7 +80,11 @@ class RenameKotlinFunctionProcessor : RenameKotlinPsiProcessor() {
allRenames: Map<out PsiElement, String>,
result: MutableList<UsageInfo>
) {
if (newName == null) return
val declaration = element.unwrapped as? KtNamedFunction ?: return
val descriptor = declaration.resolveToDescriptor()
checkConflictsAndReplaceUsageInfos(element, allRenames, result)
checkRedeclarations(descriptor, newName, result)
}
override fun substituteElementToRename(element: PsiElement?, editor: Editor?): PsiElement? {
@@ -52,6 +52,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
import org.jetbrains.kotlin.resolve.scopes.utils.getImplicitReceiversHierarchy
@@ -81,6 +82,16 @@ internal fun checkRedeclarations(
newName: String,
result: MutableList<UsageInfo>
) {
fun MemberScope.findSiblingByName(): DeclarationDescriptor? {
val descriptorKindFilter = when (descriptor) {
is ClassDescriptor -> DescriptorKindFilter.CLASSIFIERS
is PropertyDescriptor -> DescriptorKindFilter.VARIABLES
is FunctionDescriptor -> DescriptorKindFilter.FUNCTIONS
else -> return null
}
return getDescriptorsFiltered(descriptorKindFilter) { it.asString() == newName }.firstOrNull { it != descriptor }
}
fun getSiblingWithNewName(): DeclarationDescriptor? {
val containingDescriptor = descriptor.containingDeclaration
@@ -104,17 +115,23 @@ internal fun checkRedeclarations(
return context[BindingContext.VARIABLE, dummyVar]?.type?.constructor?.declarationDescriptor
}
val containingScope = when (containingDescriptor) {
is ClassDescriptor -> containingDescriptor.unsubstitutedMemberScope
is PackageFragmentDescriptor -> containingDescriptor.getMemberScope()
else -> return null
return when (containingDescriptor) {
is ClassDescriptor -> containingDescriptor.unsubstitutedMemberScope.findSiblingByName()
is PackageFragmentDescriptor -> containingDescriptor.getMemberScope().findSiblingByName()
else -> {
val block = (descriptor as? DeclarationDescriptorWithSource)?.source?.getPsi()?.parent as? KtBlockExpression
?: return null
(block.statements.firstOrNull {
if (it.name != newName) return@firstOrNull false
when (descriptor) {
is ClassDescriptor -> it is KtClassOrObject
is PropertyDescriptor -> it is KtProperty
is FunctionDescriptor -> it is KtNamedFunction
else -> false
}
} as? KtDeclaration)?.resolveToDescriptor()
}
}
val descriptorKindFilter = when (descriptor) {
is ClassDescriptor -> DescriptorKindFilter.CLASSIFIERS
is PropertyDescriptor -> DescriptorKindFilter.VARIABLES
else -> return null
}
return containingScope.getDescriptorsFiltered(descriptorKindFilter) { it.asString() == newName }.firstOrNull()
}
val candidateDescriptor = getSiblingWithNewName() ?: return
@@ -0,0 +1,11 @@
package test
class A {
fun /*rename*/namedFunA() {}
fun namedFunB() {}
fun useNames() {
namedFunA()
namedFunB()
}
}
@@ -0,0 +1,7 @@
{
"type": "MARKED_ELEMENT",
"mainFile": "test/test.kt",
"newName": "namedFunB",
"withRuntime": "true",
"hint": "Function 'namedFunB' is already declared in class 'A'"
}
@@ -0,0 +1,9 @@
package test
fun /*rename*/namedFunA() {}
fun namedFunB() {}
fun useNames() {
namedFunA()
namedFunB()
}
@@ -0,0 +1,7 @@
{
"type": "MARKED_ELEMENT",
"mainFile": "test/test.kt",
"newName": "namedFunB",
"withRuntime": "true",
"hint": "Function 'namedFunB' is already declared in package 'test'"
}
@@ -185,6 +185,12 @@ public class RenameTestGenerated extends AbstractRenameTest {
doTest(fileName);
}
@TestMetadata("memberFunctionRedeclaration/memberFunctionRedeclaration.test")
public void testMemberFunctionRedeclaration_MemberFunctionRedeclaration() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/memberFunctionRedeclaration/memberFunctionRedeclaration.test");
doTest(fileName);
}
@TestMetadata("memberPropertyRedeclaration/memberPropertyRedeclaration.test")
public void testMemberPropertyRedeclaration_MemberPropertyRedeclaration() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/memberPropertyRedeclaration/memberPropertyRedeclaration.test");
@@ -1007,6 +1013,12 @@ public class RenameTestGenerated extends AbstractRenameTest {
doTest(fileName);
}
@TestMetadata("topLevelFunctionRedeclaration/topLevelFunctionRedeclaration.test")
public void testTopLevelFunctionRedeclaration_TopLevelFunctionRedeclaration() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/topLevelFunctionRedeclaration/topLevelFunctionRedeclaration.test");
doTest(fileName);
}
@TestMetadata("topLevelPropertyRedeclaration/topLevelPropertyRedeclaration.test")
public void testTopLevelPropertyRedeclaration_TopLevelPropertyRedeclaration() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/topLevelPropertyRedeclaration/topLevelPropertyRedeclaration.test");