diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/expectActualUtil.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/expectActualUtil.kt index c171636d03f..5e9a159e474 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/expectActualUtil.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/expectActualUtil.kt @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo import org.jetbrains.kotlin.idea.caches.project.implementedDescriptors import org.jetbrains.kotlin.idea.caches.project.implementingDescriptors import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny +import org.jetbrains.kotlin.idea.core.setVisibility import org.jetbrains.kotlin.idea.core.toDescriptor import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor import org.jetbrains.kotlin.psi.KtClassOrObject @@ -126,3 +127,17 @@ fun KtDeclaration.isEffectivelyActual(): Boolean { private fun MemberDescriptor.isEnumEntryInActual() = (DescriptorUtils.isEnumEntry(this) && (containingDeclaration as? MemberDescriptor)?.isActual == true) + +fun KtDeclaration.runOnExpectAndAllActuals(checkExpect: Boolean = true, f: (KtDeclaration) -> Unit) { + if (hasActualModifier()) { + val expectElement = liftToExpected() + expectElement?.actualsForExpected()?.forEach { + if (it !== this) { + f(it) + } + } + expectElement?.let { f(it) } + } else if (!checkExpect || isExpectDeclaration()) { + actualsForExpected().forEach { f(it) } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddModifierFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddModifierFix.kt index cab5345612c..6b69154d9cb 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddModifierFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddModifierFix.kt @@ -30,6 +30,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.core.quickfix.QuickFixUtil import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix import org.jetbrains.kotlin.idea.refactoring.canRefactor +import org.jetbrains.kotlin.idea.util.runOnExpectAndAllActuals import org.jetbrains.kotlin.lexer.KtModifierKeywordToken import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens.* @@ -55,11 +56,11 @@ open class AddModifierFix( override fun getFamilyName() = "Add modifier" - override fun invoke(project: Project, editor: Editor?, file: KtFile) { + private fun invokeOnElement(element: KtModifierListOwner?) { element?.addModifier(modifier) if (modifier == KtTokens.ABSTRACT_KEYWORD && (element is KtProperty || element is KtNamedFunction)) { - element?.containingClass()?.run { + element.containingClass()?.run { if (!hasModifier(KtTokens.ABSTRACT_KEYWORD) && !hasModifier(KtTokens.SEALED_KEYWORD)) { addModifier(KtTokens.ABSTRACT_KEYWORD) } @@ -67,12 +68,24 @@ open class AddModifierFix( } } + override fun invoke(project: Project, editor: Editor?, file: KtFile) { + val originalElement = element + if (originalElement is KtDeclaration && modifier.isMultiplatformPersistent()) { + originalElement.runOnExpectAndAllActuals { invokeOnElement(it) } + } + invokeOnElement(originalElement) + } + override fun isAvailable(project: Project, editor: Editor?, file: KtFile): Boolean { val element = element ?: return false return element.canRefactor() } companion object { + + private fun KtModifierKeywordToken.isMultiplatformPersistent(): Boolean = + this in KtTokens.MODALITY_MODIFIERS || this == KtTokens.INLINE_KEYWORD + private val modalityModifiers = setOf(ABSTRACT_KEYWORD, OPEN_KEYWORD, FINAL_KEYWORD) fun getElementName(modifierListOwner: KtModifierListOwner): String { diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeVisibilityFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeVisibilityFix.kt index e5f2b03b41d..8317a1d8cf7 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeVisibilityFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeVisibilityFix.kt @@ -26,8 +26,10 @@ import org.jetbrains.kotlin.descriptors.Visibility import org.jetbrains.kotlin.idea.core.canBePrivate import org.jetbrains.kotlin.idea.core.canBeProtected import org.jetbrains.kotlin.idea.core.setVisibility +import org.jetbrains.kotlin.idea.util.runOnExpectAndAllActuals import org.jetbrains.kotlin.lexer.KtModifierKeywordToken import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtModifierListOwner import org.jetbrains.kotlin.resolve.ExposedVisibilityChecker @@ -42,6 +44,11 @@ open class ChangeVisibilityFix( override fun getFamilyName() = "Make $visibilityModifier" override fun invoke(project: Project, editor: Editor?, file: KtFile) { + val originalElement = element + if (originalElement is KtDeclaration) { + originalElement.runOnExpectAndAllActuals { it.setVisibility(visibilityModifier) } + } + element?.setVisibility(visibilityModifier) } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinSafeDeleteProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinSafeDeleteProcessor.kt index 298e47a9035..f2586677240 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinSafeDeleteProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinSafeDeleteProcessor.kt @@ -53,9 +53,9 @@ import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOpt import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchParameters import org.jetbrains.kotlin.idea.search.projectScope import org.jetbrains.kotlin.idea.search.usagesSearch.processDelegationCallConstructorUsages -import org.jetbrains.kotlin.idea.util.actualsForExpected import org.jetbrains.kotlin.idea.util.isExpectDeclaration import org.jetbrains.kotlin.idea.util.liftToExpected +import org.jetbrains.kotlin.idea.util.runOnExpectAndAllActuals import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.* @@ -400,19 +400,7 @@ class KotlinSafeDeleteProcessor : JavaSafeDeleteProcessor() { override fun prepareForDeletion(element: PsiElement) { if (element is KtDeclaration) { - if (element.hasActualModifier()) { - val expectElement = element.liftToExpected() - expectElement?.actualsForExpected()?.forEach { - if (it !== element) { - it.removeOrClean() - } - } - expectElement?.removeOrClean() - } else { - element.actualsForExpected().forEach { - it.removeOrClean() - } - } + element.runOnExpectAndAllActuals(checkExpect = false) { it.removeOrClean() } } when (element) { diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinSafeDeleteProcessor.kt.172 b/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinSafeDeleteProcessor.kt.172 index 31b5f246a81..f946ad13cfe 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinSafeDeleteProcessor.kt.172 +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinSafeDeleteProcessor.kt.172 @@ -51,9 +51,9 @@ import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOpt import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchParameters import org.jetbrains.kotlin.idea.search.projectScope import org.jetbrains.kotlin.idea.search.usagesSearch.processDelegationCallConstructorUsages -import org.jetbrains.kotlin.idea.util.actualsForExpected import org.jetbrains.kotlin.idea.util.isExpectDeclaration import org.jetbrains.kotlin.idea.util.liftToExpected +import org.jetbrains.kotlin.idea.util.runOnExpectAndAllActuals import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType @@ -392,19 +392,7 @@ class KotlinSafeDeleteProcessor : JavaSafeDeleteProcessor() { override fun prepareForDeletion(element: PsiElement) { if (element is KtDeclaration) { - if (element.hasActualModifier()) { - val expectElement = element.liftToExpected() - expectElement?.actualsForExpected()?.forEach { - if (it !== element) { - it.removeOrClean() - } - } - expectElement?.removeOrClean() - } else { - element.actualsForExpected().forEach { - it.removeOrClean() - } - } + element.runOnExpectAndAllActuals(checkExpect = false) { it.removeOrClean() } } when (element) { diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinSafeDeleteProcessor.kt.173 b/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinSafeDeleteProcessor.kt.173 index e50ced40c8f..810dd5bfc5b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinSafeDeleteProcessor.kt.173 +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinSafeDeleteProcessor.kt.173 @@ -53,9 +53,9 @@ import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOpt import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchParameters import org.jetbrains.kotlin.idea.search.projectScope import org.jetbrains.kotlin.idea.search.usagesSearch.processDelegationCallConstructorUsages -import org.jetbrains.kotlin.idea.util.actualsForExpected import org.jetbrains.kotlin.idea.util.isExpectDeclaration import org.jetbrains.kotlin.idea.util.liftToExpected +import org.jetbrains.kotlin.idea.util.runOnExpectAndAllActuals import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType @@ -402,19 +402,7 @@ class KotlinSafeDeleteProcessor : JavaSafeDeleteProcessor() { override fun prepareForDeletion(element: PsiElement) { if (element is KtDeclaration) { - if (element.hasActualModifier()) { - val expectElement = element.liftToExpected() - expectElement?.actualsForExpected()?.forEach { - if (it !== element) { - it.removeOrClean() - } - } - expectElement?.removeOrClean() - } else { - element.actualsForExpected().forEach { - it.removeOrClean() - } - } + element.runOnExpectAndAllActuals(checkExpect = false) { it.removeOrClean() } } when (element) { diff --git a/idea/testData/multiModuleQuickFix/makeInlineFromExpect/common/common.kt b/idea/testData/multiModuleQuickFix/makeInlineFromExpect/common/common.kt new file mode 100644 index 00000000000..103564becce --- /dev/null +++ b/idea/testData/multiModuleQuickFix/makeInlineFromExpect/common/common.kt @@ -0,0 +1,4 @@ +// "Add 'inline' modifier" "true" +// ERROR: Only type parameters of inline functions can be reified + +expect fun ied T> inlineFun(t: T) \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/makeInlineFromExpect/common/common.kt.after b/idea/testData/multiModuleQuickFix/makeInlineFromExpect/common/common.kt.after new file mode 100644 index 00000000000..29fb206be25 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/makeInlineFromExpect/common/common.kt.after @@ -0,0 +1,4 @@ +// "Add 'inline' modifier" "true" +// ERROR: Only type parameters of inline functions can be reified + +expect inline fun inlineFun(t: T) \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/makeInlineFromExpect/jvm/jvm.kt b/idea/testData/multiModuleQuickFix/makeInlineFromExpect/jvm/jvm.kt new file mode 100644 index 00000000000..00216e09676 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/makeInlineFromExpect/jvm/jvm.kt @@ -0,0 +1 @@ +actual fun inlineFun(t: T) {} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/makeInlineFromExpect/jvm/jvm.kt.after b/idea/testData/multiModuleQuickFix/makeInlineFromExpect/jvm/jvm.kt.after new file mode 100644 index 00000000000..d86b12c088c --- /dev/null +++ b/idea/testData/multiModuleQuickFix/makeInlineFromExpect/jvm/jvm.kt.after @@ -0,0 +1 @@ +actual inline fun inlineFun(t: T) {} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/makeInternalFromExpect/common/common.kt b/idea/testData/multiModuleQuickFix/makeInternalFromExpect/common/common.kt new file mode 100644 index 00000000000..ff949997e36 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/makeInternalFromExpect/common/common.kt @@ -0,0 +1,6 @@ +// "Make 'getInternal' internal" "true" +// ERROR: 'public' function exposes its 'internal' return type Internal + +internal expect class Internal + +expect fun getInternal(): Internal \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/makeInternalFromExpect/common/common.kt.after b/idea/testData/multiModuleQuickFix/makeInternalFromExpect/common/common.kt.after new file mode 100644 index 00000000000..9ca845c1f04 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/makeInternalFromExpect/common/common.kt.after @@ -0,0 +1,6 @@ +// "Make 'getInternal' internal" "true" +// ERROR: 'public' function exposes its 'internal' return type Internal + +internal expect class Internal + +internal expect fun getInternal(): Internal \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/makeInternalFromExpect/jvm/jvm.kt b/idea/testData/multiModuleQuickFix/makeInternalFromExpect/jvm/jvm.kt new file mode 100644 index 00000000000..1eafd5d80d1 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/makeInternalFromExpect/jvm/jvm.kt @@ -0,0 +1,5 @@ +internal actual class Internal + +actual fun getInternal(): Internal { + return Internal() +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/makeInternalFromExpect/jvm/jvm.kt.after b/idea/testData/multiModuleQuickFix/makeInternalFromExpect/jvm/jvm.kt.after new file mode 100644 index 00000000000..77e54881aef --- /dev/null +++ b/idea/testData/multiModuleQuickFix/makeInternalFromExpect/jvm/jvm.kt.after @@ -0,0 +1,5 @@ +internal actual class Internal + +internal actual fun getInternal(): Internal { + return Internal() +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/makeOpenFromActual/common/common.kt b/idea/testData/multiModuleQuickFix/makeOpenFromActual/common/common.kt new file mode 100644 index 00000000000..fde70e794cd --- /dev/null +++ b/idea/testData/multiModuleQuickFix/makeOpenFromActual/common/common.kt @@ -0,0 +1 @@ +expect class One diff --git a/idea/testData/multiModuleQuickFix/makeOpenFromActual/common/common.kt.after b/idea/testData/multiModuleQuickFix/makeOpenFromActual/common/common.kt.after new file mode 100644 index 00000000000..64a8f0d745a --- /dev/null +++ b/idea/testData/multiModuleQuickFix/makeOpenFromActual/common/common.kt.after @@ -0,0 +1 @@ +expect open class One diff --git a/idea/testData/multiModuleQuickFix/makeOpenFromActual/jvm/jvm.kt b/idea/testData/multiModuleQuickFix/makeOpenFromActual/jvm/jvm.kt new file mode 100644 index 00000000000..67c027790a1 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/makeOpenFromActual/jvm/jvm.kt @@ -0,0 +1,5 @@ +// "Make 'One' open" "true" +// ERROR: This type is final, so it cannot be inherited from + +actual class One +class Two : One() \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/makeOpenFromActual/jvm/jvm.kt.after b/idea/testData/multiModuleQuickFix/makeOpenFromActual/jvm/jvm.kt.after new file mode 100644 index 00000000000..fe34761a86d --- /dev/null +++ b/idea/testData/multiModuleQuickFix/makeOpenFromActual/jvm/jvm.kt.after @@ -0,0 +1,5 @@ +// "Make 'One' open" "true" +// ERROR: This type is final, so it cannot be inherited from + +actual open class One +class Two : One() \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/makeOpenFromExpect/common/common.kt b/idea/testData/multiModuleQuickFix/makeOpenFromExpect/common/common.kt new file mode 100644 index 00000000000..389ffe85659 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/makeOpenFromExpect/common/common.kt @@ -0,0 +1,5 @@ +// "Make 'One' open" "true" +// ERROR: This type is final, so it cannot be inherited from + +expect class One +expect class Two : One \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/makeOpenFromExpect/common/common.kt.after b/idea/testData/multiModuleQuickFix/makeOpenFromExpect/common/common.kt.after new file mode 100644 index 00000000000..49cfc13d34f --- /dev/null +++ b/idea/testData/multiModuleQuickFix/makeOpenFromExpect/common/common.kt.after @@ -0,0 +1,5 @@ +// "Make 'One' open" "true" +// ERROR: This type is final, so it cannot be inherited from + +expect open class One +expect class Two : One \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/makeOpenFromExpect/jvm/jvm.kt b/idea/testData/multiModuleQuickFix/makeOpenFromExpect/jvm/jvm.kt new file mode 100644 index 00000000000..5869754f467 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/makeOpenFromExpect/jvm/jvm.kt @@ -0,0 +1 @@ +actual class One \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/makeOpenFromExpect/jvm/jvm.kt.after b/idea/testData/multiModuleQuickFix/makeOpenFromExpect/jvm/jvm.kt.after new file mode 100644 index 00000000000..276a9ddea80 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/makeOpenFromExpect/jvm/jvm.kt.after @@ -0,0 +1 @@ +actual open class One \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java index d67dca7e080..13697f1ab55 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java @@ -274,6 +274,26 @@ public class QuickFixMultiModuleTestGenerated extends AbstractQuickFixMultiModul runTest("idea/testData/multiModuleQuickFix/interface/"); } + @TestMetadata("makeInlineFromExpect") + public void testMakeInlineFromExpect() throws Exception { + runTest("idea/testData/multiModuleQuickFix/makeInlineFromExpect/"); + } + + @TestMetadata("makeInternalFromExpect") + public void testMakeInternalFromExpect() throws Exception { + runTest("idea/testData/multiModuleQuickFix/makeInternalFromExpect/"); + } + + @TestMetadata("makeOpenFromActual") + public void testMakeOpenFromActual() throws Exception { + runTest("idea/testData/multiModuleQuickFix/makeOpenFromActual/"); + } + + @TestMetadata("makeOpenFromExpect") + public void testMakeOpenFromExpect() throws Exception { + runTest("idea/testData/multiModuleQuickFix/makeOpenFromExpect/"); + } + @TestMetadata("memberFunParameterToReceiverByHeader") public void testMemberFunParameterToReceiverByHeader() throws Exception { runTest("idea/testData/multiModuleQuickFix/memberFunParameterToReceiverByHeader/");