diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/moveConflictUtils.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/moveConflictUtils.kt index 73e212917a7..23c342ce0fc 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/moveConflictUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/moveConflictUtils.kt @@ -59,6 +59,7 @@ import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor +import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode @@ -451,6 +452,38 @@ class MoveConflictChecker( } } + private fun checkSealedClassMove(conflicts: MultiMap) { + val visited = HashSet() + for (elementToMove in elementsToMove) { + if (!visited.add(elementToMove)) continue + if (elementToMove !is KtClassOrObject) continue + + val rootClass: KtClass + val rootClassDescriptor: ClassDescriptor + if (elementToMove is KtClass && elementToMove.isSealed()) { + rootClass = elementToMove + rootClassDescriptor = rootClass.resolveToDescriptorIfAny() as? ClassDescriptor ?: return + } + else { + val classDescriptor = elementToMove.resolveToDescriptorIfAny() as? ClassDescriptor ?: return + val superClassDescriptor = classDescriptor.getSuperClassNotAny() ?: return + if (superClassDescriptor.modality != Modality.SEALED) return + rootClassDescriptor = superClassDescriptor + rootClass = rootClassDescriptor.source.getPsi() as? KtClass ?: return + } + + val subclasses = rootClassDescriptor.sealedSubclasses.mapNotNull { it.source.getPsi() } + if (subclasses.isEmpty()) continue + + visited.add(rootClass) + visited.addAll(subclasses) + + if (isToBeMoved(rootClass) && subclasses.all { isToBeMoved(it) }) continue + + conflicts.putValue(rootClass, "Sealed class '${rootClass.name}' must be moved with all its subclasses") + } + } + fun checkAllConflicts( externalUsages: MutableSet, internalUsages: MutableSet, @@ -461,6 +494,7 @@ class MoveConflictChecker( checkVisibilityInUsages(externalUsages, conflicts) checkVisibilityInDeclarations(conflicts) checkInternalMemberUsages(conflicts) + checkSealedClassMove(conflicts) } } diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithAllSubclasses/after/source/Foo.kt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithAllSubclasses/after/source/Foo.kt new file mode 100644 index 00000000000..99acff8d12c --- /dev/null +++ b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithAllSubclasses/after/source/Foo.kt @@ -0,0 +1,2 @@ +package source + diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithAllSubclasses/after/source/dummy.txt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithAllSubclasses/after/source/dummy.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithAllSubclasses/after/target/Foo.kt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithAllSubclasses/after/target/Foo.kt new file mode 100644 index 00000000000..ce06007f722 --- /dev/null +++ b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithAllSubclasses/after/target/Foo.kt @@ -0,0 +1,6 @@ +package target + +sealed class Expr +data class Const(val number: Double) : Expr() +data class Sum(val e1: Expr, val e2: Expr) : Expr() +object NotANumber : Expr() \ No newline at end of file diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithAllSubclasses/before/source/Foo.kt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithAllSubclasses/before/source/Foo.kt new file mode 100644 index 00000000000..efcd7c7f764 --- /dev/null +++ b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithAllSubclasses/before/source/Foo.kt @@ -0,0 +1,6 @@ +package source + +sealed class Expr +data class Const(val number: Double) : Expr() +data class Sum(val e1: Expr, val e2: Expr) : Expr() +object NotANumber : Expr() \ No newline at end of file diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithAllSubclasses/before/source/dummy.txt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithAllSubclasses/before/source/dummy.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithAllSubclasses/sealedClassWithAllSubclasses.test b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithAllSubclasses/sealedClassWithAllSubclasses.test new file mode 100644 index 00000000000..1a649bb5efc --- /dev/null +++ b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithAllSubclasses/sealedClassWithAllSubclasses.test @@ -0,0 +1,5 @@ +{ + "mainFile": "source/Foo.kt", + "type": "MOVE_KOTLIN_TOP_LEVEL_DECLARATIONS", + "targetPackage": "target" +} diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithSkippedSubclasses/after/source/Foo.kt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithSkippedSubclasses/after/source/Foo.kt new file mode 100644 index 00000000000..2158e3ba303 --- /dev/null +++ b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithSkippedSubclasses/after/source/Foo.kt @@ -0,0 +1,7 @@ +package source + +import target.Expr + +data class Const(val number: Double) : Expr() +data class Sum(val e1: Expr, val e2: Expr) : Expr() +object NotANumber : Expr() \ No newline at end of file diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithSkippedSubclasses/after/source/dummy.txt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithSkippedSubclasses/after/source/dummy.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithSkippedSubclasses/after/target/Expr.kt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithSkippedSubclasses/after/target/Expr.kt new file mode 100644 index 00000000000..2e47c77daa5 --- /dev/null +++ b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithSkippedSubclasses/after/target/Expr.kt @@ -0,0 +1,3 @@ +package target + +sealed class Expr \ No newline at end of file diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithSkippedSubclasses/before/source/Foo.kt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithSkippedSubclasses/before/source/Foo.kt new file mode 100644 index 00000000000..5cb14c9b97f --- /dev/null +++ b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithSkippedSubclasses/before/source/Foo.kt @@ -0,0 +1,6 @@ +package source + +sealed class Expr +data class Const(val number: Double) : Expr() +data class Sum(val e1: Expr, val e2: Expr) : Expr() +object NotANumber : Expr() \ No newline at end of file diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithSkippedSubclasses/before/source/dummy.txt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithSkippedSubclasses/before/source/dummy.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithSkippedSubclasses/conflicts.txt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithSkippedSubclasses/conflicts.txt new file mode 100644 index 00000000000..756eb0a8202 --- /dev/null +++ b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithSkippedSubclasses/conflicts.txt @@ -0,0 +1 @@ +Sealed class 'Expr' must be moved with all its subclasses diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithSkippedSubclasses/sealedClassWithSkippedSubclasses.test b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithSkippedSubclasses/sealedClassWithSkippedSubclasses.test new file mode 100644 index 00000000000..1a649bb5efc --- /dev/null +++ b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithSkippedSubclasses/sealedClassWithSkippedSubclasses.test @@ -0,0 +1,5 @@ +{ + "mainFile": "source/Foo.kt", + "type": "MOVE_KOTLIN_TOP_LEVEL_DECLARATIONS", + "targetPackage": "target" +} diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedSubclassWithSkippedRoot/after/source/Foo.kt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedSubclassWithSkippedRoot/after/source/Foo.kt new file mode 100644 index 00000000000..bf5d08ed03c --- /dev/null +++ b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedSubclassWithSkippedRoot/after/source/Foo.kt @@ -0,0 +1,5 @@ +package source + +sealed class Expr +data class Sum(val e1: Expr, val e2: Expr) : Expr() +object NotANumber : Expr() \ No newline at end of file diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedSubclassWithSkippedRoot/after/source/dummy.txt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedSubclassWithSkippedRoot/after/source/dummy.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedSubclassWithSkippedRoot/after/target/Const.kt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedSubclassWithSkippedRoot/after/target/Const.kt new file mode 100644 index 00000000000..b129fc50628 --- /dev/null +++ b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedSubclassWithSkippedRoot/after/target/Const.kt @@ -0,0 +1,5 @@ +package target + +import source.Expr + +data class Const(val number: Double) : Expr() \ No newline at end of file diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedSubclassWithSkippedRoot/before/source/Foo.kt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedSubclassWithSkippedRoot/before/source/Foo.kt new file mode 100644 index 00000000000..737027eefe1 --- /dev/null +++ b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedSubclassWithSkippedRoot/before/source/Foo.kt @@ -0,0 +1,6 @@ +package source + +sealed class Expr +data class Const(val number: Double) : Expr() +data class Sum(val e1: Expr, val e2: Expr) : Expr() +object NotANumber : Expr() \ No newline at end of file diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedSubclassWithSkippedRoot/before/source/dummy.txt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedSubclassWithSkippedRoot/before/source/dummy.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedSubclassWithSkippedRoot/conflicts.txt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedSubclassWithSkippedRoot/conflicts.txt new file mode 100644 index 00000000000..756eb0a8202 --- /dev/null +++ b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedSubclassWithSkippedRoot/conflicts.txt @@ -0,0 +1 @@ +Sealed class 'Expr' must be moved with all its subclasses diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedSubclassWithSkippedRoot/sealedSubclassWithSkippedRoot.test b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedSubclassWithSkippedRoot/sealedSubclassWithSkippedRoot.test new file mode 100644 index 00000000000..1a649bb5efc --- /dev/null +++ b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedSubclassWithSkippedRoot/sealedSubclassWithSkippedRoot.test @@ -0,0 +1,5 @@ +{ + "mainFile": "source/Foo.kt", + "type": "MOVE_KOTLIN_TOP_LEVEL_DECLARATIONS", + "targetPackage": "target" +} 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 373a6368a11..044716267ca 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/MoveTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/MoveTestGenerated.java @@ -613,6 +613,24 @@ public class MoveTestGenerated extends AbstractMoveTest { doTest(fileName); } + @TestMetadata("kotlin/moveTopLevelDeclarations/misc/sealedClassWithAllSubclasses/sealedClassWithAllSubclasses.test") + public void testKotlin_moveTopLevelDeclarations_misc_sealedClassWithAllSubclasses_SealedClassWithAllSubclasses() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithAllSubclasses/sealedClassWithAllSubclasses.test"); + doTest(fileName); + } + + @TestMetadata("kotlin/moveTopLevelDeclarations/misc/sealedClassWithSkippedSubclasses/sealedClassWithSkippedSubclasses.test") + public void testKotlin_moveTopLevelDeclarations_misc_sealedClassWithSkippedSubclasses_SealedClassWithSkippedSubclasses() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithSkippedSubclasses/sealedClassWithSkippedSubclasses.test"); + doTest(fileName); + } + + @TestMetadata("kotlin/moveTopLevelDeclarations/misc/sealedSubclassWithSkippedRoot/sealedSubclassWithSkippedRoot.test") + public void testKotlin_moveTopLevelDeclarations_misc_sealedSubclassWithSkippedRoot_SealedSubclassWithSkippedRoot() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedSubclassWithSkippedRoot/sealedSubclassWithSkippedRoot.test"); + doTest(fileName); + } + @TestMetadata("kotlin/moveTopLevelDeclarations/misc/selfReferences/selfReferences.test") public void testKotlin_moveTopLevelDeclarations_misc_selfReferences_SelfReferences() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/selfReferences/selfReferences.test");