Pull Up/Extract Super: Support members declared in the companion object of the original class

This commit is contained in:
Alexey Sedunov
2016-09-20 19:06:25 +03:00
parent 982d4d4ead
commit 8a5628cde5
18 changed files with 132 additions and 24 deletions
+1
View File
@@ -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 Superclass" refactoring
- [`KT-11017`](https://youtrack.jetbrains.com/issue/KT-11017) Implement "Extract Interface" 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 properties declared in the primary constructor
Pull Up: Support members declared in the companion object of the original class
#### Android Lint #### Android Lint
@@ -139,4 +139,17 @@ abstract class KtNamedDeclarationStub<T extends KotlinStubWithFqName<?>> extends
} }
return KtNamedDeclarationUtil.getFQName(this); 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();
}
}
} }
@@ -880,3 +880,8 @@ fun checkSuperMethods(
return askUserForMethodsToSearch(declarationDescriptor, overriddenElementsToDescriptor) return askUserForMethodsToSearch(declarationDescriptor, overriddenElementsToDescriptor)
} }
fun KtNamedDeclaration.isCompanionMemberOf(klass: KtClassOrObject): Boolean {
val containingObject = containingClassOrObject as? KtObjectDeclaration ?: return false
return containingObject.isCompanion() && containingObject.containingClassOrObject == klass
}
@@ -36,7 +36,11 @@ import org.jetbrains.kotlin.renderer.DescriptorRendererModifier
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptySet import org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptySet
class KotlinMemberInfo(member: KtNamedDeclaration, val isSuperClass: Boolean = false) : MemberInfoBase<KtNamedDeclaration>(member) { class KotlinMemberInfo @JvmOverloads constructor(
member: KtNamedDeclaration,
val isSuperClass: Boolean = false,
val isCompanionMember: Boolean = false
) : MemberInfoBase<KtNamedDeclaration>(member) {
companion object { companion object {
private val RENDERER = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.withOptions { private val RENDERER = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.withOptions {
modifiers = DescriptorRendererModifier.INNER.singletonOrEmptySet() modifiers = DescriptorRendererModifier.INNER.singletonOrEmptySet()
@@ -62,6 +66,9 @@ class KotlinMemberInfo(member: KtNamedDeclaration, val isSuperClass: Boolean = f
if (memberDescriptor is MemberDescriptor && memberDescriptor.modality == Modality.ABSTRACT) { if (memberDescriptor is MemberDescriptor && memberDescriptor.modality == Modality.ABSTRACT) {
displayName = "abstract $displayName" displayName = "abstract $displayName"
} }
if (isCompanionMember) {
displayName = "companion $displayName"
}
val overriddenDescriptors = (memberDescriptor as? CallableMemberDescriptor)?.overriddenDescriptors ?: emptySet() val overriddenDescriptors = (memberDescriptor as? CallableMemberDescriptor)?.overriddenDescriptors ?: emptySet()
if (overriddenDescriptors.isNotEmpty()) { if (overriddenDescriptors.isNotEmpty()) {
@@ -85,6 +85,21 @@ fun extractClassMembers(
collectSuperTypeEntries: Boolean = true, collectSuperTypeEntries: Boolean = true,
filter: ((KtNamedDeclaration) -> Boolean)? = null filter: ((KtNamedDeclaration) -> Boolean)? = null
): List<KotlinMemberInfo> { ): List<KotlinMemberInfo> {
fun KtClassOrObject.extractFromClassBody(
filter: ((KtNamedDeclaration) -> Boolean)?,
isCompanion: Boolean,
result: MutableCollection<KotlinMemberInfo>
) {
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() if (aClass !is KtClassOrObject) return emptyList()
val result = ArrayList<KotlinMemberInfo>() val result = ArrayList<KotlinMemberInfo>()
@@ -98,8 +113,8 @@ fun extractClassMembers(
val classDescriptor = type?.constructor?.declarationDescriptor as? ClassDescriptor val classDescriptor = type?.constructor?.declarationDescriptor as? ClassDescriptor
classDescriptor?.source?.getPsi() as? KtClass classDescriptor?.source?.getPsi() as? KtClass
} }
.filter { it.isInterface() } .filter { it.isInterface() }
.mapTo(result) { KotlinMemberInfo(it, true) } .mapTo(result) { KotlinMemberInfo(it, true) }
} }
aClass.getPrimaryConstructor() aClass.getPrimaryConstructor()
@@ -107,12 +122,8 @@ fun extractClassMembers(
?.filter { it.hasValOrVar() } ?.filter { it.hasValOrVar() }
?.mapTo(result) { KotlinMemberInfo(it) } ?.mapTo(result) { KotlinMemberInfo(it) }
aClass.declarations aClass.extractFromClassBody(filter, false, result)
.filter { it is KtNamedDeclaration (aClass as? KtClass)?.getCompanionObjects()?.firstOrNull()?.extractFromClassBody(filter, true, result)
&& it !is KtConstructor<*>
&& !(it is KtObjectDeclaration && it.isCompanion())
&& (filter == null || filter(it)) }
.mapTo(result) { KotlinMemberInfo(it as KtNamedDeclaration) }
return result return result
} }
@@ -34,7 +34,7 @@ class KotlinMemberSelectionTable(
abstractColumnHeader: String? abstractColumnHeader: String?
) : AbstractMemberSelectionTable<KtNamedDeclaration, KotlinMemberInfo>(memberInfos, memberInfoModel, abstractColumnHeader) { ) : AbstractMemberSelectionTable<KtNamedDeclaration, KotlinMemberInfo>(memberInfos, memberInfoModel, abstractColumnHeader) {
override fun getAbstractColumnValue(memberInfo: KotlinMemberInfo): Any? { override fun getAbstractColumnValue(memberInfo: KotlinMemberInfo): Any? {
if (memberInfo.isStatic()) return null if (memberInfo.isStatic || memberInfo.isCompanionMember) return null
val member = memberInfo.member val member = memberInfo.member
if (member !is KtNamedFunction && member !is KtProperty && member !is KtParameter) return null if (member !is KtNamedFunction && member !is KtProperty && member !is KtParameter) return null
@@ -28,8 +28,12 @@ import com.intellij.refactoring.classMembers.MemberInfoModel
import com.intellij.refactoring.memberPullUp.PullUpProcessor import com.intellij.refactoring.memberPullUp.PullUpProcessor
import com.intellij.refactoring.util.DocCommentPolicy import com.intellij.refactoring.util.DocCommentPolicy
import org.jetbrains.kotlin.asJava.toLightClass 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.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.* import org.jetbrains.kotlin.psi.*
class KotlinPullUpDialog( class KotlinPullUpDialog(
@@ -62,14 +66,18 @@ class KotlinPullUpDialog(
val superClass = superClass ?: return false val superClass = superClass ?: return false
if (superClass is PsiClass) return false if (superClass is PsiClass) return false
if (superClass !is KtClass) return false if (superClass !is KtClass) return false
if (!superClass.isInterface()) return true
val member = memberInfo.member 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 return member is KtNamedFunction || (member is KtProperty && !member.mustBeAbstractInInterface()) || member is KtParameter
} }
override fun isAbstractWhenDisabled(memberInfo: KotlinMemberInfo): Boolean { override fun isAbstractWhenDisabled(memberInfo: KotlinMemberInfo): Boolean {
val member = memberInfo.member val member = memberInfo.member
if (member.isCompanionMemberOf(sourceClass)) return false
return ((member is KtProperty || member is KtParameter) && superClass !is PsiClass) return ((member is KtProperty || member is KtParameter) && superClass !is PsiClass)
|| (member is KtNamedFunction && superClass is PsiClass) || (member is KtNamedFunction && superClass is PsiClass)
} }
@@ -78,7 +86,10 @@ class KotlinPullUpDialog(
val superClass = superClass ?: return false val superClass = superClass ?: return false
val member = memberInfo.member 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 (memberInfo in memberInfoStorage.getDuplicatedMemberInfos(superClass)) return false
if (member in memberInfoStorage.getExtending(superClass)) return false if (member in memberInfoStorage.getExtending(superClass)) return false
return true return true
@@ -34,10 +34,12 @@ import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.KotlinLanguage import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet
import org.jetbrains.kotlin.idea.core.dropDefaultValue 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.core.replaced
import org.jetbrains.kotlin.idea.intentions.setType import org.jetbrains.kotlin.idea.intentions.setType
import org.jetbrains.kotlin.idea.refactoring.createJavaField import org.jetbrains.kotlin.idea.refactoring.createJavaField
import org.jetbrains.kotlin.idea.refactoring.dropOverrideKeywordIfNecessary 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.refactoring.safeDelete.removeOverrideModifier
import org.jetbrains.kotlin.idea.util.anonymousObjectSuperTypeOrNull import org.jetbrains.kotlin.idea.util.anonymousObjectSuperTypeOrNull
import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiUnifier import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiUnifier
@@ -383,6 +385,9 @@ class KotlinPullUpHelper(
val newType = substitutor.substitute(lightMethod.returnType) val newType = substitutor.substitute(lightMethod.returnType)
val newField = createJavaField(member, data.targetClass) val newField = createJavaField(member, data.targetClass)
newField.typeElement?.replace(elementFactory.createTypeElement(newType)) newField.typeElement?.replace(elementFactory.createTypeElement(newType))
if (member.isCompanionMemberOf(data.sourceClass)) {
newField.modifierList?.setModifierProperty(PsiModifier.STATIC, true)
}
if (member is KtParameter) { if (member is KtParameter) {
(member.parent as? KtParameterList)?.removeParameter(member) (member.parent as? KtParameterList)?.removeParameter(member)
} }
@@ -461,12 +466,15 @@ class KotlinPullUpHelper(
member is KtProperty -> member.mustBeAbstractInInterface() member is KtProperty -> member.mustBeAbstractInInterface()
else -> false else -> false
} }
val classToAddTo = if (member.isCompanionMemberOf(data.sourceClass)) data.targetClass.getOrCreateCompanionObject() else data.targetClass
if (toAbstract) { if (toAbstract) {
if (!originalIsAbstract) { if (!originalIsAbstract) {
makeAbstract(memberCopy, data.memberDescriptors[member] as CallableMemberDescriptor, data.sourceToTargetClassSubstitutor, data.targetClass) 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) { if (member.typeReference == null) {
movedMember.typeReference?.addToShorteningWaitSet() movedMember.typeReference?.addToShorteningWaitSet()
} }
@@ -474,7 +482,7 @@ class KotlinPullUpHelper(
removeOriginalMemberOrAddOverride(member) removeOriginalMemberOrAddOverride(member)
} }
else { else {
movedMember = doAddCallableMember(memberCopy, clashingSuper, data.targetClass) movedMember = doAddCallableMember(memberCopy, clashingSuper, classToAddTo)
if (member is KtParameter && movedMember is KtParameter) { if (member is KtParameter && movedMember is KtParameter) {
member.valOrVarKeyword?.delete() member.valOrVarKeyword?.delete()
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.idea.refactoring.pullUp package org.jetbrains.kotlin.idea.refactoring.pullUp
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNamedElement import com.intellij.psi.PsiNamedElement
import com.intellij.refactoring.RefactoringBundle 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.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy
import org.jetbrains.kotlin.resolve.source.getPsi import org.jetbrains.kotlin.resolve.source.getPsi
@@ -109,6 +111,13 @@ private fun KotlinPullUpData.checkClashWithSuperDeclaration(
conflicts.putValue(member, message.capitalize()) 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( private fun KotlinPullUpData.checkAccidentalOverrides(
member: KtNamedDeclaration, member: KtNamedDeclaration,
memberDescriptor: DeclarationDescriptor, memberDescriptor: DeclarationDescriptor,
@@ -119,7 +128,7 @@ private fun KotlinPullUpData.checkAccidentalOverrides(
HierarchySearchRequest<PsiElement>(targetClass, targetClass.useScope) HierarchySearchRequest<PsiElement>(targetClass, targetClass.useScope)
.searchInheritors() .searchInheritors()
.asSequence() .asSequence()
.filterNot { it.unwrapped == sourceClass || it.unwrapped == targetClass } .filterNot { it.isSourceOrTarget(this) }
.mapNotNull { it.unwrapped as? KtClassOrObject } .mapNotNull { it.unwrapped as? KtClassOrObject }
.forEach { .forEach {
val subClassDescriptor = resolutionFacade.resolveToDescriptor(it) as ClassDescriptor val subClassDescriptor = resolutionFacade.resolveToDescriptor(it) as ClassDescriptor
@@ -64,18 +64,18 @@ fun addMemberToTarget(targetMember: KtNamedDeclaration, targetClass: KtClassOrOb
return movedMember 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) private fun KtParameter.toProperty(): KtProperty = KtPsiFactory(this).createProperty(text)
fun doAddCallableMember( fun doAddCallableMember(
memberCopy: KtCallableDeclaration, memberCopy: KtCallableDeclaration,
clashingSuper: KtCallableDeclaration?, clashingSuper: KtCallableDeclaration?,
targetClass: KtClass targetClass: KtClassOrObject
): KtCallableDeclaration { ): KtCallableDeclaration {
val memberToAdd = val memberToAdd = if (memberCopy is KtParameter && memberCopy.needToBeAbstract(targetClass)) memberCopy.toProperty() else memberCopy
if (memberCopy is KtParameter && (memberCopy.hasModifier(KtTokens.ABSTRACT_KEYWORD) || targetClass.isInterface())) {
memberCopy.toProperty()
}
else memberCopy
if (clashingSuper != null && clashingSuper.hasModifier(KtTokens.ABSTRACT_KEYWORD)) { if (clashingSuper != null && clashingSuper.hasModifier(KtTokens.ABSTRACT_KEYWORD)) {
return clashingSuper.replaced(if (memberToAdd is KtParameter && clashingSuper is KtProperty) memberToAdd.toProperty() else memberToAdd) return clashingSuper.replaced(if (memberToAdd is KtParameter && clashingSuper is KtProperty) memberToAdd.toProperty() else memberToAdd)
@@ -1,6 +1,4 @@
class Class { class Class {
companion object {
}
} }
fun f() { fun f() {
@@ -3,6 +3,7 @@ import org.jetbrains.annotations.NotNull;
abstract class A { abstract class A {
public final int x; public final int x;
public static final int _x;
public abstract boolean foo(int n); public abstract boolean foo(int n);
@@ -1,5 +1,10 @@
// WITH_RUNTIME // WITH_RUNTIME
abstract class <caret>B: A() { abstract class <caret>B: A() {
companion object {
// INFO: {"checked": "true"}
val _x = 1
}
// INFO: {"checked": "true"} // INFO: {"checked": "true"}
val x = 1 val x = 1
// INFO: {"checked": "true"} // INFO: {"checked": "true"}
@@ -1,5 +1,6 @@
// WITH_RUNTIME // WITH_RUNTIME
abstract class B: A() { abstract class B: A() {
// INFO: {"checked": "true"} // INFO: {"checked": "true"}
val y: Int get() = 2 val y: Int get() = 2
// INFO: {"checked": "true"} // INFO: {"checked": "true"}
@@ -2,6 +2,14 @@
open class A open class A
abstract class <caret>B: A() { abstract class <caret>B: A() {
companion object {
// INFO: {"checked": "true"}
val _x = 1
// INFO: {"checked": "true"}
fun _foo(n: Int): Boolean = n > 0
}
// INFO: {"checked": "true"} // INFO: {"checked": "true"}
val x = 1 val x = 1
// INFO: {"checked": "true"} // INFO: {"checked": "true"}
@@ -24,6 +24,14 @@ abstract class A {
class Y { class Y {
} }
companion object {
// INFO: {"checked": "true"}
val _x = 1
// INFO: {"checked": "true"}
fun _foo(n: Int): Boolean = n > 0
}
} }
abstract class B: A() { abstract class B: A() {
@@ -2,6 +2,14 @@
interface T interface T
abstract class <caret>B: T { abstract class <caret>B: T {
companion object {
// INFO: {"checked": "true"}
val _x = 1
// INFO: {"checked": "true"}
fun foo(n: Int): Boolean = n > 0
}
// INFO: {"checked": "true"} // INFO: {"checked": "true"}
val x = 1 val x = 1
// INFO: {"checked": "true"} // INFO: {"checked": "true"}
@@ -19,9 +19,23 @@ interface T {
class Y { class Y {
} }
companion object {
// INFO: {"checked": "true"}
val _x: Int
// INFO: {"checked": "true"}
fun foo(n: Int): Boolean = n > 0
}
} }
abstract class B: T { abstract class B: T {
companion object {
// INFO: {"checked": "true"}
override val _x = 1
}
// INFO: {"checked": "true"} // INFO: {"checked": "true"}
override val x = 1 override val x = 1
// INFO: {"checked": "true"} // INFO: {"checked": "true"}