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 36b37888bd5..b6005f1e757 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/renameConflictUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/renameConflictUtils.kt @@ -29,16 +29,16 @@ import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator import org.jetbrains.kotlin.idea.core.copied import org.jetbrains.kotlin.idea.imports.importableFqName +import org.jetbrains.kotlin.idea.project.TargetPlatformDetector import org.jetbrains.kotlin.idea.refactoring.explicateAsText import org.jetbrains.kotlin.idea.refactoring.getThisLabelName import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.search.and import org.jetbrains.kotlin.idea.search.restrictToKotlinSources -import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers -import org.jetbrains.kotlin.idea.util.getAllAccessibleFunctions -import org.jetbrains.kotlin.idea.util.getAllAccessibleVariables -import org.jetbrains.kotlin.idea.util.getResolutionScope +import org.jetbrains.kotlin.idea.util.* import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.js.resolve.JsPlatform +import org.jetbrains.kotlin.js.resolve.JsTypeSpecificityComparator import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* @@ -46,11 +46,15 @@ import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DelegatingBindingTrace +import org.jetbrains.kotlin.resolve.OverloadChecker import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getExplicitReceiverValue import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getImplicitReceiverValue +import org.jetbrains.kotlin.resolve.calls.results.TypeSpecificityComparator import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor +import org.jetbrains.kotlin.resolve.jvm.JvmTypeSpecificityComparator +import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter import org.jetbrains.kotlin.resolve.scopes.LexicalScope @@ -61,6 +65,7 @@ import org.jetbrains.kotlin.resolve.scopes.utils.getImplicitReceiversHierarchy import org.jetbrains.kotlin.resolve.source.getPsi import org.jetbrains.kotlin.types.ErrorUtils import org.jetbrains.kotlin.utils.SmartList +import org.jetbrains.kotlin.utils.addIfNotNull import java.util.* internal fun ResolvedCall<*>.noReceivers() = dispatchReceiver == null && extensionReceiver == null @@ -107,11 +112,11 @@ internal fun checkRedeclarations( return getDescriptorsFiltered(descriptorKindFilter) { it.asString() == newName }.filter { it != descriptor } } - fun getSiblingWithNewName(): DeclarationDescriptor? { + fun getSiblingsWithNewName(): List { val containingDescriptor = descriptor.containingDeclaration if (descriptor is ValueParameterDescriptor) { - return (containingDescriptor as CallableDescriptor).valueParameters.firstOrNull { it.name.asString() == newName } + return (containingDescriptor as CallableDescriptor).valueParameters.filter { it.name.asString() == newName } } if (descriptor is TypeParameterDescriptor) { @@ -120,44 +125,64 @@ internal fun checkRedeclarations( is CallableDescriptor -> containingDescriptor.typeParameters else -> emptyList() } - typeParameters.firstOrNull { it.name.asString() == newName }?.let { return it } - val containingDeclaration = (containingDescriptor as? DeclarationDescriptorWithSource)?.source?.getPsi() as? KtDeclaration - ?: return null - val dummyVar = KtPsiFactory(containingDeclaration).createProperty("val foo: $newName") - val outerScope = containingDeclaration.getResolutionScope() - val context = dummyVar.analyzeInContext(outerScope, containingDeclaration) - return context[BindingContext.VARIABLE, dummyVar]?.type?.constructor?.declarationDescriptor + return SmartList().apply { + typeParameters.filterTo(this) { it.name.asString() == newName } + val containingDeclaration = (containingDescriptor as? DeclarationDescriptorWithSource)?.source?.getPsi() as? KtDeclaration + ?: return emptyList() + val dummyVar = KtPsiFactory(containingDeclaration).createProperty("val foo: $newName") + val outerScope = containingDeclaration.getResolutionScope() + val context = dummyVar.analyzeInContext(outerScope, containingDeclaration) + addIfNotNull(context[BindingContext.VARIABLE, dummyVar]?.type?.constructor?.declarationDescriptor) + } } return when (containingDescriptor) { - is ClassDescriptor -> containingDescriptor.unsubstitutedMemberScope.findSiblingsByName().firstOrNull() - is PackageFragmentDescriptor -> containingDescriptor.getMemberScope().findSiblingsByName().firstOrNull { + is ClassDescriptor -> containingDescriptor.unsubstitutedMemberScope.findSiblingsByName() + is PackageFragmentDescriptor -> containingDescriptor.getMemberScope().findSiblingsByName().filter { it != descriptor && (!(descriptor.isTopLevelPrivate() || it.isTopLevelPrivate()) || isInSameFile(descriptor, it)) } 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) { + ?: return emptyList() + block.statements.mapNotNull { + if (it.name != newName) return@mapNotNull null + val isAccepted = when (descriptor) { is ClassDescriptor -> it is KtClassOrObject is PropertyDescriptor -> it is KtProperty is FunctionDescriptor -> it is KtNamedFunction else -> false } - } as? KtDeclaration)?.unsafeResolveToDescriptor() + if (!isAccepted) return@mapNotNull null + (it as? KtDeclaration)?.unsafeResolveToDescriptor() + } } } } - 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) + val overloadChecker = when (descriptor) { + is PropertyDescriptor, + is FunctionDescriptor, + is ClassifierDescriptor -> { + val psi = (descriptor as? DeclarationDescriptorWithSource)?.source?.getPsi() as? KtElement ?: return + val typeSpecificityComparator = when (TargetPlatformDetector.getPlatform(psi.containingKtFile)) { + is JvmPlatform -> JvmTypeSpecificityComparator + is JsPlatform -> JsTypeSpecificityComparator + else -> TypeSpecificityComparator.NONE + } + OverloadChecker(typeSpecificityComparator) + } + else -> null + } + for (candidateDescriptor in getSiblingsWithNewName()) { + val candidate = (candidateDescriptor as? DeclarationDescriptorWithSource)?.source?.getPsi() as? KtNamedDeclaration ?: continue + if (overloadChecker != null && overloadChecker.isOverloadable(descriptor, candidateDescriptor)) continue + val what = candidate.renderDescription().capitalize() + val where = candidate.representativeContainer()?.renderDescription() ?: continue + val message = "$what is already declared in $where" + result += BasicUnresolvableCollisionUsageInfo(candidate, candidate, message) + } } private fun LexicalScope.getRelevantDescriptors( diff --git a/idea/testData/refactoring/rename/inplace/ExtensionAndNoReceiver.kt b/idea/testData/refactoring/rename/inplace/ExtensionAndNoReceiver.kt new file mode 100644 index 00000000000..3a6ee455808 --- /dev/null +++ b/idea/testData/refactoring/rename/inplace/ExtensionAndNoReceiver.kt @@ -0,0 +1,5 @@ +class A + +fun A.b() {} + +fun c() {} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/inplace/ExtensionAndNoReceiver.kt.after b/idea/testData/refactoring/rename/inplace/ExtensionAndNoReceiver.kt.after new file mode 100644 index 00000000000..05aa598c8d0 --- /dev/null +++ b/idea/testData/refactoring/rename/inplace/ExtensionAndNoReceiver.kt.after @@ -0,0 +1,5 @@ +class A + +fun A.b() {} + +fun b() {} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/inplace/TwoExtensions.kt b/idea/testData/refactoring/rename/inplace/TwoExtensions.kt new file mode 100644 index 00000000000..f06d6a648bd --- /dev/null +++ b/idea/testData/refactoring/rename/inplace/TwoExtensions.kt @@ -0,0 +1,5 @@ +class Foo +class Bar + +fun Foo.example() = "test" +fun Bar.other() = "test" \ No newline at end of file diff --git a/idea/testData/refactoring/rename/inplace/TwoExtensions.kt.after b/idea/testData/refactoring/rename/inplace/TwoExtensions.kt.after new file mode 100644 index 00000000000..b45f1812022 --- /dev/null +++ b/idea/testData/refactoring/rename/inplace/TwoExtensions.kt.after @@ -0,0 +1,5 @@ +class Foo +class Bar + +fun Foo.example() = "test" +fun Bar.example() = "test" \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/InplaceRenameTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/InplaceRenameTest.kt index ac37e68f90d..c55eab7f057 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/InplaceRenameTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/InplaceRenameTest.kt @@ -134,6 +134,14 @@ class InplaceRenameTest : LightPlatformCodeInsightTestCase() { doTestMemberInplaceRename("foo") } + fun testExtensionAndNoReceiver() { + doTestMemberInplaceRename("b") + } + + fun testTwoExtensions() { + doTestMemberInplaceRename("example") + } + private fun doTestImplicitLambdaParameter(newName: String) { configureByFile(getTestName(false) + ".kt")