diff --git a/ChangeLog.md b/ChangeLog.md index f7bb7c33c67..cfdcc32a5a5 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -475,6 +475,7 @@ These artifacts include extensions for the types available in the latter JDKs, s - Extract Interface: Disable inline/external/lateinit members - [`KT-12704`](https://youtrack.jetbrains.com/issue/KT-12704), [`KT-15583`](https://youtrack.jetbrains.com/issue/KT-15583) Override/Implement Members: Support all nullability annotations respected by the Kotlin compiler - [`KT-15563`](https://youtrack.jetbrains.com/issue/KT-15563) Override Members: Allow overriding virtual synthetic members (e.g. equals(), hashCode(), toString(), etc.) in data classes +- [`KT-15355`](https://youtrack.jetbrains.com/issue/KT-15355) Extract Interface: Disable "Make abstract" and assume it to be true for abstract members of an interface #### Intention actions, inspections and quickfixes diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ImplementAbstractMemberIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ImplementAbstractMemberIntention.kt index 2e0c99c57fa..331c5c07d5e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ImplementAbstractMemberIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ImplementAbstractMemberIntention.kt @@ -41,13 +41,12 @@ import org.jetbrains.kotlin.idea.caches.resolve.getJavaClassDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideImplementMembersHandler import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMemberChooserObject -import org.jetbrains.kotlin.idea.refactoring.isInterfaceClass +import org.jetbrains.kotlin.idea.refactoring.isAbstract import org.jetbrains.kotlin.idea.runSynchronouslyWithProgress import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest import org.jetbrains.kotlin.idea.search.declarationsSearch.searchInheritors import org.jetbrains.kotlin.idea.util.application.executeCommand import org.jetbrains.kotlin.idea.util.application.runWriteAction -import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject @@ -64,16 +63,6 @@ abstract class ImplementAbstractMemberIntentionBase : private val LOG = Logger.getInstance("#${ImplementAbstractMemberIntentionBase::class.java.canonicalName}") } - private fun isAbstract(element: KtNamedDeclaration): Boolean { - if (element.hasModifier(KtTokens.ABSTRACT_KEYWORD)) return true - if (!(element.containingClassOrObject?.isInterfaceClass() ?: false)) return false - return when (element) { - is KtProperty -> element.initializer == null && element.delegate == null && element.accessors.isEmpty() - is KtNamedFunction -> !element.hasBody() - else -> false - } - } - protected fun findExistingImplementation( subClass: ClassDescriptor, superMember: CallableMemberDescriptor @@ -117,7 +106,7 @@ abstract class ImplementAbstractMemberIntentionBase : protected abstract fun computeText(element: KtNamedDeclaration): String? override fun applicabilityRange(element: KtNamedDeclaration): TextRange? { - if (!isAbstract(element)) return null + if (!element.isAbstract()) return null text = computeText(element) ?: return null diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractInterfaceDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractInterfaceDialog.kt index 03307f7583a..bbdd65887a3 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractInterfaceDialog.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractInterfaceDialog.kt @@ -25,6 +25,7 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.KotlinExtrac import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo import org.jetbrains.kotlin.idea.refactoring.memberInfo.extractClassMembers import org.jetbrains.kotlin.idea.refactoring.pullUp.getInterfaceContainmentVerifier +import org.jetbrains.kotlin.idea.refactoring.pullUp.isAbstractInInterface import org.jetbrains.kotlin.idea.refactoring.pullUp.mustBeAbstractInInterface import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* @@ -66,10 +67,12 @@ class KotlinExtractInterfaceDialog( override fun isAbstractEnabled(memberInfo: KotlinMemberInfo): Boolean { if (!super.isAbstractEnabled(memberInfo)) return false val member = memberInfo.member + if (member.isAbstractInInterface(originalClass)) return false return member is KtNamedFunction || (member is KtProperty && !member.mustBeAbstractInInterface()) || member is KtParameter } - override fun isAbstractWhenDisabled(member: KotlinMemberInfo) = member.member is KtProperty + override fun isAbstractWhenDisabled(member: KotlinMemberInfo) = + member.member is KtProperty || member.member.isAbstractInInterface(originalClass) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt index 83184588641..6b81ced05b2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt @@ -716,6 +716,16 @@ fun PsiNamedElement.isInterfaceClass(): Boolean = when (this) { else -> false } +fun KtNamedDeclaration.isAbstract(): Boolean { + if (hasModifier(KtTokens.ABSTRACT_KEYWORD)) return true + if (!(containingClassOrObject?.isInterfaceClass() ?: false)) return false + return when (this) { + is KtProperty -> initializer == null && delegate == null && accessors.isEmpty() + is KtNamedFunction -> !hasBody() + else -> false + } +} + fun replaceListPsiAndKeepDelimiters( originalList: ListType, newList: ListType, diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpDialog.kt index 4b64c1d909a..8dfbbf0d702 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpDialog.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpDialog.kt @@ -77,6 +77,7 @@ class KotlinPullUpDialog( if (member.hasModifier(KtTokens.INLINE_KEYWORD) || member.hasModifier(KtTokens.EXTERNAL_KEYWORD) || member.hasModifier(KtTokens.LATEINIT_KEYWORD)) return false + if (member.isAbstractInInterface(sourceClass)) return false if (member.isCompanionMemberOf(sourceClass)) return false if (!superClass.isInterface()) return true @@ -87,6 +88,7 @@ class KotlinPullUpDialog( override fun isAbstractWhenDisabled(memberInfo: KotlinMemberInfo): Boolean { val member = memberInfo.member if (member.isCompanionMemberOf(sourceClass)) return false + if (member.isAbstractInInterface(sourceClass)) return true return ((member is KtProperty || member is KtParameter) && superClass !is PsiClass) || (member is KtNamedFunction && superClass is PsiClass) } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpHelper.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpHelper.kt index 49b90bc1946..0201bfeff74 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpHelper.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpHelper.kt @@ -39,6 +39,7 @@ import org.jetbrains.kotlin.idea.core.replaced import org.jetbrains.kotlin.idea.core.setType import org.jetbrains.kotlin.idea.refactoring.createJavaField import org.jetbrains.kotlin.idea.refactoring.dropOverrideKeywordIfNecessary +import org.jetbrains.kotlin.idea.refactoring.isAbstract import org.jetbrains.kotlin.idea.refactoring.isCompanionMemberOf import org.jetbrains.kotlin.idea.refactoring.memberInfo.KtPsiClassWrapper import org.jetbrains.kotlin.idea.refactoring.memberInfo.toKtDeclarationWrapperAware @@ -360,7 +361,7 @@ class KotlinPullUpHelper( } private fun removeOriginalMemberOrAddOverride(member: KtCallableDeclaration) { - if (member.hasModifier(KtTokens.ABSTRACT_KEYWORD)) { + if (member.isAbstract()) { member.delete() } else { diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/pullUpUtils.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/pullUpUtils.kt index fa35f628852..35a18392735 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/pullUpUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/pullUpUtils.kt @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet import org.jetbrains.kotlin.idea.core.replaced import org.jetbrains.kotlin.idea.core.setType +import org.jetbrains.kotlin.idea.refactoring.isAbstract import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo import org.jetbrains.kotlin.idea.refactoring.memberInfo.lightElementForMemberInfo import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers @@ -40,6 +41,9 @@ import org.jetbrains.kotlin.types.typeUtil.isUnit fun KtProperty.mustBeAbstractInInterface() = hasInitializer() || hasDelegate() || (!hasInitializer() && !hasDelegate() && accessors.isEmpty()) +fun KtNamedDeclaration.isAbstractInInterface(originalClass: KtClassOrObject) = + originalClass is KtClass && originalClass.isInterface() && isAbstract() + fun KtNamedDeclaration.canMoveMemberToJavaClass(targetClass: PsiClass): Boolean { return when (this) { is KtProperty, is KtParameter -> { diff --git a/idea/testData/refactoring/pullUp/k2k/abstractFromInterfaceToInterface.kt b/idea/testData/refactoring/pullUp/k2k/abstractFromInterfaceToInterface.kt new file mode 100644 index 00000000000..3a79e24c648 --- /dev/null +++ b/idea/testData/refactoring/pullUp/k2k/abstractFromInterfaceToInterface.kt @@ -0,0 +1,9 @@ +// WITH_RUNTIME +interface T + +interface U: T { + // INFO: {"checked": "true"} + val x: Int + // INFO: {"checked": "true"} + fun foo(n: Int): Boolean +} \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/k2k/abstractFromInterfaceToInterface.kt.after b/idea/testData/refactoring/pullUp/k2k/abstractFromInterfaceToInterface.kt.after new file mode 100644 index 00000000000..7b183b71821 --- /dev/null +++ b/idea/testData/refactoring/pullUp/k2k/abstractFromInterfaceToInterface.kt.after @@ -0,0 +1,11 @@ +// WITH_RUNTIME +interface T { + // INFO: {"checked": "true"} + val x: Int + + // INFO: {"checked": "true"} + fun foo(n: Int): Boolean +} + +interface U: T { +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/pullUp/PullUpTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/pullUp/PullUpTestGenerated.java index 65e727eaed5..d73dbc2cdca 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/pullUp/PullUpTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/pullUp/PullUpTestGenerated.java @@ -34,6 +34,12 @@ public class PullUpTestGenerated extends AbstractPullUpTest { @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) public static class K2K extends AbstractPullUpTest { + @TestMetadata("abstractFromInterfaceToInterface.kt") + public void testAbstractFromInterfaceToInterface() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/k2k/abstractFromInterfaceToInterface.kt"); + doKotlinTest(fileName); + } + @TestMetadata("accidentalOverrides.kt") public void testAccidentalOverrides() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/k2k/accidentalOverrides.kt");