diff --git a/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt b/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt index 87661cd9879..9d3c0653db0 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt @@ -405,4 +405,6 @@ fun DeclarationDescriptor.isPublishedApi(): Boolean { fun DeclarationDescriptor.isAncestorOf(descriptor: DeclarationDescriptor, strict: Boolean): Boolean = DescriptorUtils.isAncestor(this, descriptor, strict) -fun DeclarationDescriptor.isCompanionObject(): Boolean = DescriptorUtils.isCompanionObject(this) \ No newline at end of file +fun DeclarationDescriptor.isCompanionObject(): Boolean = DescriptorUtils.isCompanionObject(this) + +fun ClassDescriptor.isSubclassOf(superclass: ClassDescriptor): Boolean = DescriptorUtils.isSubclass(this, superclass) \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveDeclarationsDelegate.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveDeclarationsDelegate.kt index a4ceac24534..030bd6cc8f2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveDeclarationsDelegate.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveDeclarationsDelegate.kt @@ -31,6 +31,7 @@ import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject +import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf sealed class MoveDeclarationsDelegate { abstract fun getContainerChangeInfo(originalDeclaration: KtNamedDeclaration, moveTarget: KotlinMoveTarget): ContainerChangeInfo @@ -82,6 +83,23 @@ sealed class MoveDeclarationsDelegate { return collectOuterInstanceReferences(classToMove) } + private fun isValidTargetForImplicitCompanionAsDispatchReceiver( + moveDescriptor: MoveDeclarationsDescriptor, + companionDescriptor: ClassDescriptor + ): Boolean { + val moveTarget = moveDescriptor.moveTarget + return when (moveTarget) { + is KotlinMoveTargetForCompanion -> true + is KotlinMoveTargetForExistingElement -> { + val targetClass = moveTarget.targetElement as? KtClassOrObject ?: return false + val targetClassDescriptor = targetClass.resolveToDescriptor() as ClassDescriptor + val companionClassDescriptor = companionDescriptor.containingDeclaration as? ClassDescriptor ?: return false + targetClassDescriptor.isSubclassOf(companionClassDescriptor) + } + else -> false + } + } + override fun collectConflicts( descriptor: MoveDeclarationsDescriptor, internalUsages: MutableSet, @@ -94,7 +112,7 @@ sealed class MoveDeclarationsDelegate { val isConflict = when (usage) { is ImplicitCompanionAsDispatchReceiverUsageInfo -> { - if (descriptor.moveTarget !is KotlinMoveTargetForCompanion) { + if (!isValidTargetForImplicitCompanionAsDispatchReceiver(descriptor, usage.companionDescriptor)) { conflicts.putValue(element, "Implicit companion object will be inaccessible: ${element.text}") } true diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt index 28ca53f7685..77642d2c431 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt @@ -129,7 +129,7 @@ fun KtElement.processInternalReferencesToUpdateOnPackageNameChange( val dispatchReceiver = refExpr.getResolvedCall(bindingContext)?.dispatchReceiver val implicitClass = (dispatchReceiver as? ImplicitClassReceiver)?.classDescriptor if (implicitClass?.isCompanionObject ?: false) { - return ::ImplicitCompanionAsDispatchReceiverUsageInfo + return { ImplicitCompanionAsDispatchReceiverUsageInfo(it, implicitClass!!) } } if (dispatchReceiver != null || containingDescriptor.kind != ClassKind.OBJECT) return null } @@ -178,7 +178,10 @@ fun KtElement.processInternalReferencesToUpdateOnPackageNameChange( } } -class ImplicitCompanionAsDispatchReceiverUsageInfo(callee: KtSimpleNameExpression) : UsageInfo(callee) +class ImplicitCompanionAsDispatchReceiverUsageInfo( + callee: KtSimpleNameExpression, + val companionDescriptor: ClassDescriptor +) : UsageInfo(callee) interface KotlinMoveUsage { val isInternal: Boolean diff --git a/idea/testData/refactoring/move/kotlin/moveNestedClass/nonInnerToSubclassWithCompanionAsImplicitDispatchReceiver/after/test.kt b/idea/testData/refactoring/move/kotlin/moveNestedClass/nonInnerToSubclassWithCompanionAsImplicitDispatchReceiver/after/test.kt new file mode 100644 index 00000000000..0a2dd984083 --- /dev/null +++ b/idea/testData/refactoring/move/kotlin/moveNestedClass/nonInnerToSubclassWithCompanionAsImplicitDispatchReceiver/after/test.kt @@ -0,0 +1,19 @@ +package test + +open class A { + companion object { + fun Int.extFoo(n: Int) {} + + val Int.extBar: Int get() = 1 + } + +} + +class C : A() { + class B { + fun test() { + 1.extFoo(1.extBar) + } + } + +} \ No newline at end of file diff --git a/idea/testData/refactoring/move/kotlin/moveNestedClass/nonInnerToSubclassWithCompanionAsImplicitDispatchReceiver/before/test.kt b/idea/testData/refactoring/move/kotlin/moveNestedClass/nonInnerToSubclassWithCompanionAsImplicitDispatchReceiver/before/test.kt new file mode 100644 index 00000000000..2c88a84a000 --- /dev/null +++ b/idea/testData/refactoring/move/kotlin/moveNestedClass/nonInnerToSubclassWithCompanionAsImplicitDispatchReceiver/before/test.kt @@ -0,0 +1,19 @@ +package test + +open class A { + companion object { + fun Int.extFoo(n: Int) {} + + val Int.extBar: Int get() = 1 + } + + class B { + fun test() { + 1.extFoo(1.extBar) + } + } +} + +class C : A() { + +} \ No newline at end of file diff --git a/idea/testData/refactoring/move/kotlin/moveNestedClass/nonInnerToSubclassWithCompanionAsImplicitDispatchReceiver/nonInnerToSubclassWithCompanionAsImplicitDispatchReceiver.test b/idea/testData/refactoring/move/kotlin/moveNestedClass/nonInnerToSubclassWithCompanionAsImplicitDispatchReceiver/nonInnerToSubclassWithCompanionAsImplicitDispatchReceiver.test new file mode 100644 index 00000000000..5ea16e42eb3 --- /dev/null +++ b/idea/testData/refactoring/move/kotlin/moveNestedClass/nonInnerToSubclassWithCompanionAsImplicitDispatchReceiver/nonInnerToSubclassWithCompanionAsImplicitDispatchReceiver.test @@ -0,0 +1,6 @@ +{ + "mainFile": "test.kt", + "type": "MOVE_KOTLIN_NESTED_CLASS", + "targetClass": "test.C", + "withRuntime": "true" +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/MoveTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/MoveTestGenerated.java index 55889e4e572..771f3661225 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/MoveTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/MoveTestGenerated.java @@ -372,6 +372,12 @@ public class MoveTestGenerated extends AbstractMoveTest { doTest(fileName); } + @TestMetadata("kotlin/moveNestedClass/nonInnerToSubclassWithCompanionAsImplicitDispatchReceiver/nonInnerToSubclassWithCompanionAsImplicitDispatchReceiver.test") + public void testKotlin_moveNestedClass_nonInnerToSubclassWithCompanionAsImplicitDispatchReceiver_NonInnerToSubclassWithCompanionAsImplicitDispatchReceiver() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveNestedClass/nonInnerToSubclassWithCompanionAsImplicitDispatchReceiver/nonInnerToSubclassWithCompanionAsImplicitDispatchReceiver.test"); + doTest(fileName); + } + @TestMetadata("kotlin/moveNestedClass/nonInnerToTopLevel/nonInnerToTopLevel.test") public void testKotlin_moveNestedClass_nonInnerToTopLevel_NonInnerToTopLevel() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveNestedClass/nonInnerToTopLevel/nonInnerToTopLevel.test");