diff --git a/ChangeLog.md b/ChangeLog.md index b1395f50c47..67b02b04d30 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -149,6 +149,7 @@ These artifacts include extensions for the types available in the latter JDKs, s - [`KT-11017`](https://youtrack.jetbrains.com/issue/KT-11017) Implement "Extract Superclass" refactoring - [`KT-11017`](https://youtrack.jetbrains.com/issue/KT-11017) Implement "Extract Interface" refactoring Pull Up: Support properties declared in the primary constructor +Pull Up: Support members declared in the companion object of the original class #### Android Lint diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtNamedDeclarationStub.java b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtNamedDeclarationStub.java index 7aa837be3eb..8f0cad2fd78 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtNamedDeclarationStub.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtNamedDeclarationStub.java @@ -139,4 +139,17 @@ abstract class KtNamedDeclarationStub> extends } return KtNamedDeclarationUtil.getFQName(this); } + + @Override + public void delete() throws IncorrectOperationException { + KtClassOrObject classOrObject = KtPsiUtilKt.getContainingClassOrObject(this); + + super.delete(); + + if (classOrObject instanceof KtObjectDeclaration + && ((KtObjectDeclaration) classOrObject).isCompanion() + && classOrObject.getDeclarations().isEmpty()) { + classOrObject.delete(); + } + } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt index bcccab1ed7e..214e5e71d51 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt @@ -880,3 +880,8 @@ fun checkSuperMethods( return askUserForMethodsToSearch(declarationDescriptor, overriddenElementsToDescriptor) } + +fun KtNamedDeclaration.isCompanionMemberOf(klass: KtClassOrObject): Boolean { + val containingObject = containingClassOrObject as? KtObjectDeclaration ?: return false + return containingObject.isCompanion() && containingObject.containingClassOrObject == klass +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinMemberInfo.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinMemberInfo.kt index 75fb3429d18..0dcb2fc8a8e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinMemberInfo.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinMemberInfo.kt @@ -36,7 +36,11 @@ import org.jetbrains.kotlin.renderer.DescriptorRendererModifier import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptySet -class KotlinMemberInfo(member: KtNamedDeclaration, val isSuperClass: Boolean = false) : MemberInfoBase(member) { +class KotlinMemberInfo @JvmOverloads constructor( + member: KtNamedDeclaration, + val isSuperClass: Boolean = false, + val isCompanionMember: Boolean = false +) : MemberInfoBase(member) { companion object { private val RENDERER = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.withOptions { modifiers = DescriptorRendererModifier.INNER.singletonOrEmptySet() @@ -62,6 +66,9 @@ class KotlinMemberInfo(member: KtNamedDeclaration, val isSuperClass: Boolean = f if (memberDescriptor is MemberDescriptor && memberDescriptor.modality == Modality.ABSTRACT) { displayName = "abstract $displayName" } + if (isCompanionMember) { + displayName = "companion $displayName" + } val overriddenDescriptors = (memberDescriptor as? CallableMemberDescriptor)?.overriddenDescriptors ?: emptySet() if (overriddenDescriptors.isNotEmpty()) { diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinMemberInfoStorage.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinMemberInfoStorage.kt index 2f7c0711ccc..f186160c5bb 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinMemberInfoStorage.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinMemberInfoStorage.kt @@ -85,6 +85,21 @@ fun extractClassMembers( collectSuperTypeEntries: Boolean = true, filter: ((KtNamedDeclaration) -> Boolean)? = null ): List { + fun KtClassOrObject.extractFromClassBody( + filter: ((KtNamedDeclaration) -> Boolean)?, + isCompanion: Boolean, + result: MutableCollection + ) { + declarations + .filter { + it is KtNamedDeclaration + && it !is KtConstructor<*> + && !(it is KtObjectDeclaration && it.isCompanion()) + && (filter == null || filter(it)) + } + .mapTo(result) { KotlinMemberInfo(it as KtNamedDeclaration, isCompanionMember = isCompanion) } + } + if (aClass !is KtClassOrObject) return emptyList() val result = ArrayList() @@ -98,8 +113,8 @@ fun extractClassMembers( val classDescriptor = type?.constructor?.declarationDescriptor as? ClassDescriptor classDescriptor?.source?.getPsi() as? KtClass } - .filter { it.isInterface() } - .mapTo(result) { KotlinMemberInfo(it, true) } + .filter { it.isInterface() } + .mapTo(result) { KotlinMemberInfo(it, true) } } aClass.getPrimaryConstructor() @@ -107,12 +122,8 @@ fun extractClassMembers( ?.filter { it.hasValOrVar() } ?.mapTo(result) { KotlinMemberInfo(it) } - aClass.declarations - .filter { it is KtNamedDeclaration - && it !is KtConstructor<*> - && !(it is KtObjectDeclaration && it.isCompanion()) - && (filter == null || filter(it)) } - .mapTo(result) { KotlinMemberInfo(it as KtNamedDeclaration) } + aClass.extractFromClassBody(filter, false, result) + (aClass as? KtClass)?.getCompanionObjects()?.firstOrNull()?.extractFromClassBody(filter, true, result) return result } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinMemberSelectionTable.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinMemberSelectionTable.kt index 96ae9414eed..89685af836e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinMemberSelectionTable.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinMemberSelectionTable.kt @@ -34,7 +34,7 @@ class KotlinMemberSelectionTable( abstractColumnHeader: String? ) : AbstractMemberSelectionTable(memberInfos, memberInfoModel, abstractColumnHeader) { override fun getAbstractColumnValue(memberInfo: KotlinMemberInfo): Any? { - if (memberInfo.isStatic()) return null + if (memberInfo.isStatic || memberInfo.isCompanionMember) return null val member = memberInfo.member if (member !is KtNamedFunction && member !is KtProperty && member !is KtParameter) return null 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 d05fd19c310..2a3911714be 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpDialog.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpDialog.kt @@ -28,8 +28,12 @@ import com.intellij.refactoring.classMembers.MemberInfoModel import com.intellij.refactoring.memberPullUp.PullUpProcessor import com.intellij.refactoring.util.DocCommentPolicy import org.jetbrains.kotlin.asJava.toLightClass +import org.jetbrains.kotlin.idea.refactoring.isCompanionMemberOf import org.jetbrains.kotlin.idea.refactoring.isInterfaceClass -import org.jetbrains.kotlin.idea.refactoring.memberInfo.* +import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo +import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfoStorage +import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionTable +import org.jetbrains.kotlin.idea.refactoring.memberInfo.toJavaMemberInfo import org.jetbrains.kotlin.psi.* class KotlinPullUpDialog( @@ -62,14 +66,18 @@ class KotlinPullUpDialog( val superClass = superClass ?: return false if (superClass is PsiClass) return false if (superClass !is KtClass) return false - if (!superClass.isInterface()) return true val member = memberInfo.member + if (member.isCompanionMemberOf(sourceClass)) return false + + if (!superClass.isInterface()) return true + return member is KtNamedFunction || (member is KtProperty && !member.mustBeAbstractInInterface()) || member is KtParameter } override fun isAbstractWhenDisabled(memberInfo: KotlinMemberInfo): Boolean { val member = memberInfo.member + if (member.isCompanionMemberOf(sourceClass)) return false return ((member is KtProperty || member is KtParameter) && superClass !is PsiClass) || (member is KtNamedFunction && superClass is PsiClass) } @@ -78,7 +86,10 @@ class KotlinPullUpDialog( val superClass = superClass ?: return false val member = memberInfo.member - if (superClass is PsiClass && !member.canMoveMemberToJavaClass(superClass)) return false + if (superClass is PsiClass) { + if (!member.canMoveMemberToJavaClass(superClass)) return false + if (member.isCompanionMemberOf(sourceClass)) return false + } if (memberInfo in memberInfoStorage.getDuplicatedMemberInfos(superClass)) return false if (member in memberInfoStorage.getExtending(superClass)) return false return true 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 ad9f0195da2..ccf4505baf4 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpHelper.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpHelper.kt @@ -34,10 +34,12 @@ import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.KotlinLanguage import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet import org.jetbrains.kotlin.idea.core.dropDefaultValue +import org.jetbrains.kotlin.idea.core.getOrCreateCompanionObject import org.jetbrains.kotlin.idea.core.replaced import org.jetbrains.kotlin.idea.intentions.setType import org.jetbrains.kotlin.idea.refactoring.createJavaField import org.jetbrains.kotlin.idea.refactoring.dropOverrideKeywordIfNecessary +import org.jetbrains.kotlin.idea.refactoring.isCompanionMemberOf import org.jetbrains.kotlin.idea.refactoring.safeDelete.removeOverrideModifier import org.jetbrains.kotlin.idea.util.anonymousObjectSuperTypeOrNull import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiUnifier @@ -383,6 +385,9 @@ class KotlinPullUpHelper( val newType = substitutor.substitute(lightMethod.returnType) val newField = createJavaField(member, data.targetClass) newField.typeElement?.replace(elementFactory.createTypeElement(newType)) + if (member.isCompanionMemberOf(data.sourceClass)) { + newField.modifierList?.setModifierProperty(PsiModifier.STATIC, true) + } if (member is KtParameter) { (member.parent as? KtParameterList)?.removeParameter(member) } @@ -461,12 +466,15 @@ class KotlinPullUpHelper( member is KtProperty -> member.mustBeAbstractInInterface() else -> false } + + val classToAddTo = if (member.isCompanionMemberOf(data.sourceClass)) data.targetClass.getOrCreateCompanionObject() else data.targetClass + if (toAbstract) { if (!originalIsAbstract) { makeAbstract(memberCopy, data.memberDescriptors[member] as CallableMemberDescriptor, data.sourceToTargetClassSubstitutor, data.targetClass) } - movedMember = doAddCallableMember(memberCopy, clashingSuper, data.targetClass) + movedMember = doAddCallableMember(memberCopy, clashingSuper, classToAddTo) if (member.typeReference == null) { movedMember.typeReference?.addToShorteningWaitSet() } @@ -474,7 +482,7 @@ class KotlinPullUpHelper( removeOriginalMemberOrAddOverride(member) } else { - movedMember = doAddCallableMember(memberCopy, clashingSuper, data.targetClass) + movedMember = doAddCallableMember(memberCopy, clashingSuper, classToAddTo) if (member is KtParameter && movedMember is KtParameter) { member.valOrVarKeyword?.delete() diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/pullUpConflictsUtils.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/pullUpConflictsUtils.kt index f3cdf31cfab..ca87e878bdf 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/pullUpConflictsUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/pullUpConflictsUtils.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.idea.refactoring.pullUp import com.intellij.openapi.project.Project +import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement import com.intellij.psi.PsiNamedElement import com.intellij.refactoring.RefactoringBundle @@ -32,6 +33,7 @@ import org.jetbrains.kotlin.idea.search.declarationsSearch.searchInheritors 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.renderer.DescriptorRenderer import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy import org.jetbrains.kotlin.resolve.source.getPsi @@ -109,6 +111,13 @@ private fun KotlinPullUpData.checkClashWithSuperDeclaration( conflicts.putValue(member, message.capitalize()) } +private fun PsiClass.isSourceOrTarget(data: KotlinPullUpData): Boolean { + var element = unwrapped + if (element is KtObjectDeclaration && element.isCompanion()) element = element.containingClassOrObject + + return element == data.sourceClass || element == data.targetClass +} + private fun KotlinPullUpData.checkAccidentalOverrides( member: KtNamedDeclaration, memberDescriptor: DeclarationDescriptor, @@ -119,7 +128,7 @@ private fun KotlinPullUpData.checkAccidentalOverrides( HierarchySearchRequest(targetClass, targetClass.useScope) .searchInheritors() .asSequence() - .filterNot { it.unwrapped == sourceClass || it.unwrapped == targetClass } + .filterNot { it.isSourceOrTarget(this) } .mapNotNull { it.unwrapped as? KtClassOrObject } .forEach { val subClassDescriptor = resolutionFacade.resolveToDescriptor(it) as ClassDescriptor 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 791e514af57..e3e2ed216b1 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/pullUpUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/pullUpUtils.kt @@ -64,18 +64,18 @@ fun addMemberToTarget(targetMember: KtNamedDeclaration, targetClass: KtClassOrOb return movedMember } +private fun KtParameter.needToBeAbstract(targetClass: KtClassOrObject): Boolean { + return hasModifier(KtTokens.ABSTRACT_KEYWORD) || targetClass is KtClass && targetClass.isInterface() +} + private fun KtParameter.toProperty(): KtProperty = KtPsiFactory(this).createProperty(text) fun doAddCallableMember( memberCopy: KtCallableDeclaration, clashingSuper: KtCallableDeclaration?, - targetClass: KtClass + targetClass: KtClassOrObject ): KtCallableDeclaration { - val memberToAdd = - if (memberCopy is KtParameter && (memberCopy.hasModifier(KtTokens.ABSTRACT_KEYWORD) || targetClass.isInterface())) { - memberCopy.toProperty() - } - else memberCopy + val memberToAdd = if (memberCopy is KtParameter && memberCopy.needToBeAbstract(targetClass)) memberCopy.toProperty() else memberCopy if (clashingSuper != null && clashingSuper.hasModifier(KtTokens.ABSTRACT_KEYWORD)) { return clashingSuper.replaced(if (memberToAdd is KtParameter && clashingSuper is KtProperty) memberToAdd.toProperty() else memberToAdd) diff --git a/idea/testData/refactoring/inline/inlineVariableOrProperty/property/ClassObjectProperty.kt.after b/idea/testData/refactoring/inline/inlineVariableOrProperty/property/ClassObjectProperty.kt.after index c9527a17e86..bc788f1ea14 100644 --- a/idea/testData/refactoring/inline/inlineVariableOrProperty/property/ClassObjectProperty.kt.after +++ b/idea/testData/refactoring/inline/inlineVariableOrProperty/property/ClassObjectProperty.kt.after @@ -1,6 +1,4 @@ class Class { - companion object { - } } fun f() { diff --git a/idea/testData/refactoring/pullUp/k2j/fromClassToClass.1.java.after b/idea/testData/refactoring/pullUp/k2j/fromClassToClass.1.java.after index e03d73f3b7e..b49713bcf4c 100644 --- a/idea/testData/refactoring/pullUp/k2j/fromClassToClass.1.java.after +++ b/idea/testData/refactoring/pullUp/k2j/fromClassToClass.1.java.after @@ -3,6 +3,7 @@ import org.jetbrains.annotations.NotNull; abstract class A { public final int x; + public static final int _x; public abstract boolean foo(int n); diff --git a/idea/testData/refactoring/pullUp/k2j/fromClassToClass.kt b/idea/testData/refactoring/pullUp/k2j/fromClassToClass.kt index 123e0bad392..c22e18ae388 100644 --- a/idea/testData/refactoring/pullUp/k2j/fromClassToClass.kt +++ b/idea/testData/refactoring/pullUp/k2j/fromClassToClass.kt @@ -1,5 +1,10 @@ // WITH_RUNTIME abstract class B: A() { + companion object { + // INFO: {"checked": "true"} + val _x = 1 + } + // INFO: {"checked": "true"} val x = 1 // INFO: {"checked": "true"} diff --git a/idea/testData/refactoring/pullUp/k2j/fromClassToClass.kt.after b/idea/testData/refactoring/pullUp/k2j/fromClassToClass.kt.after index 562f4e3f123..99f6a815d93 100644 --- a/idea/testData/refactoring/pullUp/k2j/fromClassToClass.kt.after +++ b/idea/testData/refactoring/pullUp/k2j/fromClassToClass.kt.after @@ -1,5 +1,6 @@ // WITH_RUNTIME abstract class B: A() { + // INFO: {"checked": "true"} val y: Int get() = 2 // INFO: {"checked": "true"} diff --git a/idea/testData/refactoring/pullUp/k2k/fromClassToClass.kt b/idea/testData/refactoring/pullUp/k2k/fromClassToClass.kt index 97b2159441c..0d40114bf82 100644 --- a/idea/testData/refactoring/pullUp/k2k/fromClassToClass.kt +++ b/idea/testData/refactoring/pullUp/k2k/fromClassToClass.kt @@ -2,6 +2,14 @@ open class A abstract class B: A() { + companion object { + // INFO: {"checked": "true"} + val _x = 1 + + // INFO: {"checked": "true"} + fun _foo(n: Int): Boolean = n > 0 + } + // INFO: {"checked": "true"} val x = 1 // INFO: {"checked": "true"} diff --git a/idea/testData/refactoring/pullUp/k2k/fromClassToClass.kt.after b/idea/testData/refactoring/pullUp/k2k/fromClassToClass.kt.after index ee08ebb1364..25eaa7b6c7a 100644 --- a/idea/testData/refactoring/pullUp/k2k/fromClassToClass.kt.after +++ b/idea/testData/refactoring/pullUp/k2k/fromClassToClass.kt.after @@ -24,6 +24,14 @@ abstract class A { class Y { } + + companion object { + // INFO: {"checked": "true"} + val _x = 1 + + // INFO: {"checked": "true"} + fun _foo(n: Int): Boolean = n > 0 + } } abstract class B: A() { diff --git a/idea/testData/refactoring/pullUp/k2k/fromClassToInterface.kt b/idea/testData/refactoring/pullUp/k2k/fromClassToInterface.kt index 451ca4d2ce5..499fb7d8702 100644 --- a/idea/testData/refactoring/pullUp/k2k/fromClassToInterface.kt +++ b/idea/testData/refactoring/pullUp/k2k/fromClassToInterface.kt @@ -2,6 +2,14 @@ interface T abstract class B: T { + companion object { + // INFO: {"checked": "true"} + val _x = 1 + + // INFO: {"checked": "true"} + fun foo(n: Int): Boolean = n > 0 + } + // INFO: {"checked": "true"} val x = 1 // INFO: {"checked": "true"} diff --git a/idea/testData/refactoring/pullUp/k2k/fromClassToInterface.kt.after b/idea/testData/refactoring/pullUp/k2k/fromClassToInterface.kt.after index 6357bbd4af6..2ed2f473555 100644 --- a/idea/testData/refactoring/pullUp/k2k/fromClassToInterface.kt.after +++ b/idea/testData/refactoring/pullUp/k2k/fromClassToInterface.kt.after @@ -19,9 +19,23 @@ interface T { class Y { } + + companion object { + // INFO: {"checked": "true"} + val _x: Int + + // INFO: {"checked": "true"} + fun foo(n: Int): Boolean = n > 0 + } } abstract class B: T { + companion object { + // INFO: {"checked": "true"} + override val _x = 1 + + } + // INFO: {"checked": "true"} override val x = 1 // INFO: {"checked": "true"}