From 90e4413fc9e38b5b56af4c0114c4543286158087 Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Fri, 2 Mar 2018 15:37:59 +0300 Subject: [PATCH] Create actual: put top-level actual declarations in the same file When multiple expect declarations are in the same file, and some of them already have actual declarations for this platform, then "Create actual fix" put other declarations into the same file with existing actual declarations So #KT-17058 Fixed So #KT-21082 Fixed --- .../highlighter/markers/HasActualMarker.kt | 15 +++++++---- .../quickfix/expectactual/CreateActualFix.kt | 11 ++++++++ .../functionSameFile/header/header.kt | 5 ++++ .../functionSameFile/header/header.kt.after | 5 ++++ .../functionSameFile/jvm/foo.kt | 3 +++ .../functionSameFile/jvm/foo.kt.after | 6 +++++ .../sealedSubclass/header/header.kt | 7 +++++ .../sealedSubclass/header/header.kt.after | 7 +++++ .../sealedSubclass/js/Sealed.kt | 5 ++++ .../sealedSubclass/js/Sealed.kt.after | 6 +++++ .../idea/quickfix/QuickFixMultiModuleTest.kt | 26 +++++++++++++------ 11 files changed, 83 insertions(+), 13 deletions(-) create mode 100644 idea/testData/multiModuleQuickFix/functionSameFile/header/header.kt create mode 100644 idea/testData/multiModuleQuickFix/functionSameFile/header/header.kt.after create mode 100644 idea/testData/multiModuleQuickFix/functionSameFile/jvm/foo.kt create mode 100644 idea/testData/multiModuleQuickFix/functionSameFile/jvm/foo.kt.after create mode 100644 idea/testData/multiModuleQuickFix/sealedSubclass/header/header.kt create mode 100644 idea/testData/multiModuleQuickFix/sealedSubclass/header/header.kt.after create mode 100644 idea/testData/multiModuleQuickFix/sealedSubclass/js/Sealed.kt create mode 100644 idea/testData/multiModuleQuickFix/sealedSubclass/js/Sealed.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasActualMarker.kt b/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasActualMarker.kt index 894ea0d7f6f..cdab81a3066 100644 --- a/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasActualMarker.kt +++ b/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasActualMarker.kt @@ -21,6 +21,7 @@ import com.intellij.ide.util.DefaultPsiElementCellRenderer import com.intellij.psi.PsiElement import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor import org.jetbrains.kotlin.idea.core.toDescriptor import org.jetbrains.kotlin.idea.facet.implementingDescriptors @@ -29,6 +30,7 @@ import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.MultiTargetPlatform import org.jetbrains.kotlin.resolve.descriptorUtil.module import org.jetbrains.kotlin.resolve.getMultiTargetPlatform +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver import java.awt.event.MouseEvent @@ -87,8 +89,11 @@ private fun DeclarationDescriptor.actualsForExpected(): Collection { - return unsafeResolveToDescriptor().actualsForExpected().mapNotNullTo(LinkedHashSet()) { - DescriptorToSourceUtils.descriptorToDeclaration(it) as? KtDeclaration - } -} \ No newline at end of file +// null means "any platform" here +internal fun KtDeclaration.actualsForExpected(platform: MultiTargetPlatform? = null): Set = + resolveToDescriptorIfAny(BodyResolveMode.FULL) + ?.actualsForExpected() + ?.filter { platform == null || it.module.getMultiTargetPlatform() == platform } + ?.mapNotNullTo(LinkedHashSet()) { + DescriptorToSourceUtils.descriptorToDeclaration(it) as? KtDeclaration + } ?: emptySet() \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt index 7c01a7c6d9d..d478fdafc05 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt @@ -32,6 +32,7 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticFactory import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.idea.core.* import org.jetbrains.kotlin.idea.facet.implementingModules +import org.jetbrains.kotlin.idea.highlighter.markers.actualsForExpected import org.jetbrains.kotlin.idea.project.TargetPlatformDetector import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory @@ -40,6 +41,7 @@ import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.hasExpectModifier import org.jetbrains.kotlin.resolve.MultiTargetPlatform import org.jetbrains.kotlin.resolve.getMultiTargetPlatform @@ -94,6 +96,15 @@ sealed class CreateActualFix( private fun getOrCreateImplementationFile(): KtFile? { val declaration = element as? KtNamedDeclaration ?: return null + val parent = declaration.parent + if (parent is KtFile) { + for (otherDeclaration in parent.declarations) { + if (otherDeclaration === declaration) continue + if (!otherDeclaration.hasExpectModifier()) continue + val actualDeclaration = otherDeclaration.actualsForExpected(actualPlatform).singleOrNull() ?: continue + return actualDeclaration.containingKtFile + } + } val name = declaration.name ?: return null val expectedDir = declaration.containingFile.containingDirectory diff --git a/idea/testData/multiModuleQuickFix/functionSameFile/header/header.kt b/idea/testData/multiModuleQuickFix/functionSameFile/header/header.kt new file mode 100644 index 00000000000..e1495a7ea40 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/functionSameFile/header/header.kt @@ -0,0 +1,5 @@ +// "Create actual function for platform JVM" "true" + +expect fun bar() + +expect fun foo(arg: Int): String \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/functionSameFile/header/header.kt.after b/idea/testData/multiModuleQuickFix/functionSameFile/header/header.kt.after new file mode 100644 index 00000000000..34a61d2f679 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/functionSameFile/header/header.kt.after @@ -0,0 +1,5 @@ +// "Create actual function for platform JVM" "true" + +expect fun bar() + +expect fun foo(arg: Int): String \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/functionSameFile/jvm/foo.kt b/idea/testData/multiModuleQuickFix/functionSameFile/jvm/foo.kt new file mode 100644 index 00000000000..7bd63a01cff --- /dev/null +++ b/idea/testData/multiModuleQuickFix/functionSameFile/jvm/foo.kt @@ -0,0 +1,3 @@ +// foo: to be implemented + +actual fun bar() {} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/functionSameFile/jvm/foo.kt.after b/idea/testData/multiModuleQuickFix/functionSameFile/jvm/foo.kt.after new file mode 100644 index 00000000000..1907c67287e --- /dev/null +++ b/idea/testData/multiModuleQuickFix/functionSameFile/jvm/foo.kt.after @@ -0,0 +1,6 @@ +// foo: to be implemented + +actual fun bar() {} +actual fun foo(arg: Int): String { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/sealedSubclass/header/header.kt b/idea/testData/multiModuleQuickFix/sealedSubclass/header/header.kt new file mode 100644 index 00000000000..f2badd93349 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/sealedSubclass/header/header.kt @@ -0,0 +1,7 @@ +// "Create actual class for platform JS" "true" + +expect sealed class Sealed + +expect object Obj : Sealed + +expect class Klass(x: Int) : Sealed diff --git a/idea/testData/multiModuleQuickFix/sealedSubclass/header/header.kt.after b/idea/testData/multiModuleQuickFix/sealedSubclass/header/header.kt.after new file mode 100644 index 00000000000..dd1494bda80 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/sealedSubclass/header/header.kt.after @@ -0,0 +1,7 @@ +// "Create actual class for platform JS" "true" + +expect sealed class Sealed + +expect object Obj : Sealed + +expect class Klass(x: Int) : Sealed diff --git a/idea/testData/multiModuleQuickFix/sealedSubclass/js/Sealed.kt b/idea/testData/multiModuleQuickFix/sealedSubclass/js/Sealed.kt new file mode 100644 index 00000000000..444ae9e8fb4 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/sealedSubclass/js/Sealed.kt @@ -0,0 +1,5 @@ +// Sealed: to be implemented + +actual sealed class Sealed + +actual object Obj : Sealed() \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/sealedSubclass/js/Sealed.kt.after b/idea/testData/multiModuleQuickFix/sealedSubclass/js/Sealed.kt.after new file mode 100644 index 00000000000..705d42755fe --- /dev/null +++ b/idea/testData/multiModuleQuickFix/sealedSubclass/js/Sealed.kt.after @@ -0,0 +1,6 @@ +// Sealed: to be implemented + +actual sealed class Sealed + +actual object Obj : Sealed() +actual class Klass actual constructor(x: Int) : Sealed \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTest.kt b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTest.kt index 05d8db98e80..846e0eb08d0 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTest.kt @@ -23,9 +23,9 @@ import org.junit.Test class QuickFixMultiModuleTest : AbstractQuickFixMultiModuleTest() { private fun doMultiPlatformTest( - expectName: String = "header", // todo: change dir name - vararg impls: Pair> = arrayOf("jvm" to TargetPlatformKind.Jvm[JvmTarget.JVM_1_6]), - withTests: Boolean = false + expectName: String = "header", // todo: change dir name + vararg impls: Pair> = arrayOf("jvm" to TargetPlatformKind.Jvm[JvmTarget.JVM_1_6]), + withTests: Boolean = false ) { val commonModule = module(expectName, hasTestRoot = withTests) commonModule.createFacet(TargetPlatformKind.Common, false) @@ -40,7 +40,7 @@ class QuickFixMultiModuleTest : AbstractQuickFixMultiModuleTest() { doQuickFixTest() } - private fun doTestHeaderWithJvmAndJs(expectName: String = "header") { + private fun doTestHeaderWithJvmAndJs() { doMultiPlatformTest(impls = *arrayOf("jvm" to TargetPlatformKind.Jvm[JvmTarget.JVM_1_6], "js" to TargetPlatformKind.JavaScript)) } @@ -109,6 +109,11 @@ class QuickFixMultiModuleTest : AbstractQuickFixMultiModuleTest() { doMultiPlatformTest() } + @Test + fun testFunctionSameFile() { + doMultiPlatformTest() + } + @Test fun testInterface() { doMultiPlatformTest() @@ -169,6 +174,11 @@ class QuickFixMultiModuleTest : AbstractQuickFixMultiModuleTest() { doMultiPlatformTest(impls = *arrayOf("js" to TargetPlatformKind.JavaScript)) } + @Test + fun testSealedSubclass() { + doMultiPlatformTest(impls = *arrayOf("js" to TargetPlatformKind.JavaScript)) + } + @Test fun testSecondaryConstructorAbsence() { doMultiPlatformTest() @@ -265,14 +275,14 @@ class QuickFixMultiModuleTest : AbstractQuickFixMultiModuleTest() { fun testCreateVarInExpectClass() = doMultiPlatformTest() @Test - fun testConvertExpectSealedClassToEnum() = doTestHeaderWithJvmAndJs("header") + fun testConvertExpectSealedClassToEnum() = doTestHeaderWithJvmAndJs() @Test - fun testConvertActualSealedClassToEnum() = doTestHeaderWithJvmAndJs("js") + fun testConvertActualSealedClassToEnum() = doTestHeaderWithJvmAndJs() @Test - fun testConvertExpectEnumToSealedClass() = doTestHeaderWithJvmAndJs("header") + fun testConvertExpectEnumToSealedClass() = doTestHeaderWithJvmAndJs() @Test - fun testConvertActualEnumToSealedClass() = doTestHeaderWithJvmAndJs("js") + fun testConvertActualEnumToSealedClass() = doTestHeaderWithJvmAndJs() }