From 6a42802240c4c080a4048308b42937217f842e8d Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Thu, 11 Aug 2016 16:25:31 +0300 Subject: [PATCH] Rename: Warn about function name conflicts #KT-13239 Fixed --- ChangeLog.md | 1 + .../rename/RenameKotlinFunctionProcessor.kt | 4 ++ .../refactoring/rename/renameConflictUtils.kt | 37 ++++++++++++++----- .../before/test/test.kt | 11 ++++++ .../memberFunctionRedeclaration.test | 7 ++++ .../before/test/test.kt | 9 +++++ .../topLevelFunctionRedeclaration.test | 7 ++++ .../rename/RenameTestGenerated.java | 12 ++++++ 8 files changed, 78 insertions(+), 10 deletions(-) create mode 100644 idea/testData/refactoring/rename/memberFunctionRedeclaration/before/test/test.kt create mode 100644 idea/testData/refactoring/rename/memberFunctionRedeclaration/memberFunctionRedeclaration.test create mode 100644 idea/testData/refactoring/rename/topLevelFunctionRedeclaration/before/test/test.kt create mode 100644 idea/testData/refactoring/rename/topLevelFunctionRedeclaration/topLevelFunctionRedeclaration.test diff --git a/ChangeLog.md b/ChangeLog.md index a40e848be02..b224d1abe92 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -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 diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameKotlinFunctionProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameKotlinFunctionProcessor.kt index db07f2d766b..545e29dc5d9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameKotlinFunctionProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameKotlinFunctionProcessor.kt @@ -80,7 +80,11 @@ class RenameKotlinFunctionProcessor : RenameKotlinPsiProcessor() { allRenames: Map, result: MutableList ) { + 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? { diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/renameConflictUtils.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/renameConflictUtils.kt index 86c632985fb..02e108872db 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/renameConflictUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/renameConflictUtils.kt @@ -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 ) { + 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 diff --git a/idea/testData/refactoring/rename/memberFunctionRedeclaration/before/test/test.kt b/idea/testData/refactoring/rename/memberFunctionRedeclaration/before/test/test.kt new file mode 100644 index 00000000000..320bea604a5 --- /dev/null +++ b/idea/testData/refactoring/rename/memberFunctionRedeclaration/before/test/test.kt @@ -0,0 +1,11 @@ +package test + +class A { + fun /*rename*/namedFunA() {} + fun namedFunB() {} + + fun useNames() { + namedFunA() + namedFunB() + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/memberFunctionRedeclaration/memberFunctionRedeclaration.test b/idea/testData/refactoring/rename/memberFunctionRedeclaration/memberFunctionRedeclaration.test new file mode 100644 index 00000000000..8ddc86cb89a --- /dev/null +++ b/idea/testData/refactoring/rename/memberFunctionRedeclaration/memberFunctionRedeclaration.test @@ -0,0 +1,7 @@ +{ + "type": "MARKED_ELEMENT", + "mainFile": "test/test.kt", + "newName": "namedFunB", + "withRuntime": "true", + "hint": "Function 'namedFunB' is already declared in class 'A'" +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/topLevelFunctionRedeclaration/before/test/test.kt b/idea/testData/refactoring/rename/topLevelFunctionRedeclaration/before/test/test.kt new file mode 100644 index 00000000000..9cbc47aaafc --- /dev/null +++ b/idea/testData/refactoring/rename/topLevelFunctionRedeclaration/before/test/test.kt @@ -0,0 +1,9 @@ +package test + +fun /*rename*/namedFunA() {} +fun namedFunB() {} + +fun useNames() { + namedFunA() + namedFunB() +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/topLevelFunctionRedeclaration/topLevelFunctionRedeclaration.test b/idea/testData/refactoring/rename/topLevelFunctionRedeclaration/topLevelFunctionRedeclaration.test new file mode 100644 index 00000000000..d35d7a1d768 --- /dev/null +++ b/idea/testData/refactoring/rename/topLevelFunctionRedeclaration/topLevelFunctionRedeclaration.test @@ -0,0 +1,7 @@ +{ + "type": "MARKED_ELEMENT", + "mainFile": "test/test.kt", + "newName": "namedFunB", + "withRuntime": "true", + "hint": "Function 'namedFunB' is already declared in package 'test'" +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/RenameTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/RenameTestGenerated.java index fe328c8a4af..85798530ae1 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/RenameTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/RenameTestGenerated.java @@ -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");