Pull Up/Push Down/Extract Super: Support references to Java-declared superinterfaces

This commit is contained in:
Alexey Sedunov
2016-09-26 13:05:31 +03:00
parent 22271f0af0
commit 924bb44862
24 changed files with 148 additions and 42 deletions
@@ -150,4 +150,4 @@ private fun <T : DeclarationDescriptorWithSource> Collection<T>.findByJavaElemen
false
}
}
}
}
@@ -80,6 +80,7 @@ import org.jetbrains.kotlin.idea.intentions.RemoveCurlyBracesFromTemplateIntenti
import org.jetbrains.kotlin.idea.j2k.IdeaJavaToKotlinServices
import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinValVar
import org.jetbrains.kotlin.idea.refactoring.changeSignature.toValVar
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KtPsiClassWrapper
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.idea.util.string.collapseSpaces
@@ -714,7 +715,12 @@ fun FqNameUnsafe.hasIdentifiersOnly(): Boolean = pathSegments().all { KotlinName
fun FqName.hasIdentifiersOnly(): Boolean = pathSegments().all { KotlinNameSuggester.isIdentifier(it.asString().quoteIfNeeded()) }
fun PsiNamedElement.isInterfaceClass(): Boolean = this is KtClass && isInterface() || this is PsiClass && isInterface
fun PsiNamedElement.isInterfaceClass(): Boolean = when (this) {
is KtClass -> isInterface()
is PsiClass -> isInterface
is KtPsiClassWrapper -> psiClass.isInterface
else -> false
}
fun <ListType : KtElement> replaceListPsiAndKeepDelimiters(
originalList: ListType,
@@ -30,7 +30,7 @@ import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.MemberDescriptor
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.refactoring.isInterfaceClass
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.renderer.DescriptorRendererModifier
@@ -49,11 +49,11 @@ class KotlinMemberInfo @JvmOverloads constructor(
}
init {
val memberDescriptor = member.resolveToDescriptor()
val memberDescriptor = member.resolveToDescriptorWrapperAware()
isStatic = member.parent is KtFile
if (member is KtClass && isSuperClass) {
if (member.isInterface()) {
if ((member is KtClass || member is KtPsiClassWrapper) && isSuperClass) {
if (member.isInterfaceClass()) {
displayName = RefactoringBundle.message("member.info.implements.0", member.name)
overrides = false
}
@@ -86,6 +86,7 @@ fun lightElementForMemberInfo(declaration: KtNamedDeclaration?): PsiMember? {
it.firstIsInstanceOrNull<PsiMethod>() ?: it.firstIsInstanceOrNull<PsiField>()
} as PsiMember?
is KtClassOrObject -> declaration.toLightClass()
is KtPsiClassWrapper -> declaration.psiClass
else -> null
}
}
@@ -24,7 +24,7 @@ import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.refactoring.isInterfaceClass
import org.jetbrains.kotlin.idea.resolve.frontendService
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
@@ -40,8 +40,8 @@ class KotlinMemberInfoStorage(
filter: (KtNamedDeclaration) -> Boolean = { true }
): AbstractMemberInfoStorage<KtNamedDeclaration, PsiNamedElement, KotlinMemberInfo>(classOrObject, filter) {
override fun memberConflict(member1: KtNamedDeclaration, member: KtNamedDeclaration): Boolean {
val descriptor1 = member1.resolveToDescriptor()
val descriptor = member.resolveToDescriptor()
val descriptor1 = member1.resolveToDescriptorWrapperAware()
val descriptor = member.resolveToDescriptorWrapperAware()
if (descriptor1.name != descriptor.name) return false
return when {
@@ -111,9 +111,14 @@ fun extractClassMembers(
val typeReference = it.typeReference ?: return@mapNotNull null
val type = typeReference.analyze(BodyResolveMode.PARTIAL)[BindingContext.TYPE, typeReference]
val classDescriptor = type?.constructor?.declarationDescriptor as? ClassDescriptor
classDescriptor?.source?.getPsi() as? KtClass
val classPsi = classDescriptor?.source?.getPsi()
when (classPsi) {
is KtClass -> classPsi
is PsiClass -> KtPsiClassWrapper(classPsi)
else -> null
}
}
.filter { it.isInterface() }
.filter { it.isInterfaceClass() }
.mapTo(result) { KotlinMemberInfo(it, true) }
}
@@ -0,0 +1,37 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.refactoring.memberInfo
import com.intellij.psi.PsiClass
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtPsiFactory
interface KtPsiClassWrapper : KtNamedDeclaration {
val psiClass: PsiClass
}
fun KtPsiClassWrapper(psiClass: PsiClass): KtPsiClassWrapper {
val dummyKtClass = KtPsiFactory(psiClass.project).createClass("class ${psiClass.name}")
return object : KtPsiClassWrapper, KtNamedDeclaration by dummyKtClass {
override fun equals(other: Any?) = psiClass == (other as? KtPsiClassWrapper)?.psiClass
override fun hashCode() = psiClass.hashCode()
override val psiClass: PsiClass
get() = psiClass
}
}
@@ -18,8 +18,12 @@ package org.jetbrains.kotlin.idea.refactoring.memberInfo
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMember
import com.intellij.psi.PsiNamedElement
import org.jetbrains.kotlin.asJava.classes.KtLightClass
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.getJavaClassDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
@@ -59,4 +63,14 @@ fun KotlinMemberInfo.getChildrenToAnalyze(): List<PsiElement> {
}
}
return childrenToCheck
}
internal fun KtNamedDeclaration.resolveToDescriptorWrapperAware(resolutionFacade: ResolutionFacade? = null): DeclarationDescriptor {
if (this is KtPsiClassWrapper) return psiClass.getJavaClassDescriptor(resolutionFacade)!!
return (resolutionFacade ?: getResolutionFacade()).resolveToDescriptor(this)
}
internal fun PsiMember.toKtDeclarationWrapperAware(): KtNamedDeclaration? {
if (this is PsiClass && this !is KtLightClass) return KtPsiClassWrapper(this)
return namedUnwrappedElement as? KtNamedDeclaration
}
@@ -20,7 +20,9 @@ import com.intellij.psi.PsiNamedElement
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.getJavaClassDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KtPsiClassWrapper
import org.jetbrains.kotlin.idea.refactoring.memberInfo.getClassDescriptorIfAny
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.psi.KtClassOrObject
@@ -45,11 +47,10 @@ class KotlinPullUpData(val sourceClass: KtClassOrObject,
val sourceClassDescriptor = sourceClassContext[BindingContext.DECLARATION_TO_DESCRIPTOR, sourceClass] as ClassDescriptor
val memberDescriptors = membersToMove.keysToMap {
if (it is KtParameter) {
sourceClassContext[BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, it]!!
}
else {
sourceClassContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it]!!
when (it) {
is KtPsiClassWrapper -> it.psiClass.getJavaClassDescriptor(resolutionFacade)!!
is KtParameter -> sourceClassContext[BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, it]!!
else -> sourceClassContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it]!!
}
}
@@ -29,10 +29,7 @@ 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.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.idea.refactoring.memberInfo.*
import org.jetbrains.kotlin.psi.*
class KotlinPullUpDialog(
@@ -40,6 +40,8 @@ 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.memberInfo.KtPsiClassWrapper
import org.jetbrains.kotlin.idea.refactoring.memberInfo.toKtDeclarationWrapperAware
import org.jetbrains.kotlin.idea.refactoring.safeDelete.removeOverrideModifier
import org.jetbrains.kotlin.idea.util.anonymousObjectSuperTypeOrNull
import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiUnifier
@@ -325,7 +327,8 @@ class KotlinPullUpHelper(
return clashingSuperDescriptor.source.getPsi() as? KtCallableDeclaration
}
private fun moveSuperInterface(member: KtClass, substitutor: PsiSubstitutor) {
private fun moveSuperInterface(member: PsiNamedElement, substitutor: PsiSubstitutor) {
val realMemberPsi = if (member is KtPsiClassWrapper) member.psiClass else member
val classDescriptor = data.memberDescriptors[member] as? ClassDescriptor ?: return
val currentSpecifier = data.sourceClass.getSuperTypeEntryByDescriptor(classDescriptor, data.sourceClassContext) ?: return
when (data.targetClass) {
@@ -340,7 +343,7 @@ class KotlinPullUpHelper(
val sourcePsiClass = data.sourceClass.toLightClass() ?: return
val superRef = sourcePsiClass.implementsList
?.referenceElements
?.firstOrNull { it.resolve()?.unwrapped == member }
?.firstOrNull { it.resolve()?.unwrapped == realMemberPsi }
?: return
val superTypeForTarget = substitutor.substitute(elementFactory.createType(superRef))
@@ -426,9 +429,9 @@ class KotlinPullUpHelper(
}
override fun move(info: MemberInfoBase<PsiMember>, substitutor: PsiSubstitutor) {
val member = info.member.namedUnwrappedElement as? KtNamedDeclaration ?: return
val member = info.member.toKtDeclarationWrapperAware() ?: return
if (member is KtClass && info.overrides != null) {
if ((member is KtClass || member is KtPsiClassWrapper) && info.overrides != null) {
moveSuperInterface(member, substitutor)
return
}
@@ -23,13 +23,13 @@ import com.intellij.refactoring.memberPullUp.JavaPullUpHelper
import com.intellij.refactoring.memberPullUp.PullUpData
import com.intellij.refactoring.memberPullUp.PullUpHelper
import com.intellij.refactoring.memberPullUp.PullUpHelperFactory
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.asJava.classes.KtLightClass
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.refactoring.createJavaClass
import org.jetbrains.kotlin.idea.refactoring.memberInfo.toKtDeclarationWrapperAware
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.psi.psiUtil.startOffset
@@ -38,7 +38,7 @@ class KotlinPullUpHelperFactory : PullUpHelperFactory {
val sourceClass = sourceClass.unwrapped as? KtClassOrObject ?: return null
val targetClass = targetClass.unwrapped as? PsiNamedElement ?: return null
val membersToMove = membersToMove
.mapNotNull { it.namedUnwrappedElement as? KtNamedDeclaration }
.mapNotNull { it.toKtDeclarationWrapperAware() }
.sortedBy { it.startOffset }
return KotlinPullUpData(sourceClass, targetClass, membersToMove)
}
@@ -56,7 +56,7 @@ class KotlinPullUpHelperFactory : PullUpHelperFactory {
}
class JavaToKotlinPullUpHelperFactory : PullUpHelperFactory {
private fun createJavaToKotlinPullUpHelper(data: PullUpData): JavaToKotlinPreconversionPullUpHelper? {
private fun createJavaToKotlinPullUpHelper(data: PullUpData): PullUpHelper<*>? {
if (!data.sourceClass.isInheritor(data.targetClass, true)) return null
val dummyTargetClass = createDummyTargetClass(data) ?: return null
val dataForDelegate = object : PullUpData by data {
@@ -96,6 +96,8 @@ class JavaToKotlinPullUpHelperFactory : PullUpHelperFactory {
}
override fun createPullUpHelper(data: PullUpData): PullUpHelper<*> {
if (data.sourceClass is KtLightClass) return KotlinPullUpHelperFactory().createPullUpHelper(data)
createJavaToKotlinPullUpHelper(data)?.let { return it }
return PullUpHelper.INSTANCE
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo
import org.jetbrains.kotlin.idea.refactoring.memberInfo.getChildrenToAnalyze
import org.jetbrains.kotlin.idea.refactoring.memberInfo.resolveToDescriptorWrapperAware
import org.jetbrains.kotlin.idea.references.KtReference
import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest
import org.jetbrains.kotlin.idea.search.declarationsSearch.searchInheritors
@@ -57,7 +58,7 @@ fun checkConflicts(project: Project,
with(pullUpData) {
for (memberInfo in memberInfos) {
val member = memberInfo.member
val memberDescriptor = resolutionFacade.resolveToDescriptor(member)
val memberDescriptor = member.resolveToDescriptorWrapperAware(resolutionFacade)
checkClashWithSuperDeclaration(member, memberDescriptor, conflicts)
checkAccidentalOverrides(member, memberDescriptor, conflicts)
@@ -131,7 +132,7 @@ private fun KotlinPullUpData.checkAccidentalOverrides(
.filterNot { it.isSourceOrTarget(this) }
.mapNotNull { it.unwrapped as? KtClassOrObject }
.forEach {
val subClassDescriptor = resolutionFacade.resolveToDescriptor(it) as ClassDescriptor
val subClassDescriptor = it.resolveToDescriptorWrapperAware(resolutionFacade) as ClassDescriptor
val substitutor = getTypeSubstitutor(targetClassDescriptor.defaultType,
subClassDescriptor.defaultType) ?: TypeSubstitutor.EMPTY
val memberDescriptorInSubClass =
@@ -142,7 +143,7 @@ private fun KotlinPullUpData.checkAccidentalOverrides(
val message = memberDescriptor.renderForConflicts() +
" in super class would clash with existing member of " +
resolutionFacade.resolveToDescriptor(it).renderForConflicts()
it.resolveToDescriptorWrapperAware(resolutionFacade).renderForConflicts()
conflicts.putValue(clashingMember, message.capitalize())
}
}
@@ -31,10 +31,12 @@ import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.idea.caches.resolve.getJavaClassDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet
import org.jetbrains.kotlin.idea.refactoring.runSynchronouslyWithProgress
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KtPsiClassWrapper
import org.jetbrains.kotlin.idea.refactoring.pullUp.*
import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest
import org.jetbrains.kotlin.idea.search.declarationsSearch.searchInheritors
@@ -62,7 +64,12 @@ class KotlinPushDownContext(
val memberDescriptors = membersToMove
.map { it.member }
.keysToMap { sourceClassContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it]!! }
.keysToMap {
when (it) {
is KtPsiClassWrapper -> it.psiClass.getJavaClassDescriptor(resolutionFacade)!!
else -> sourceClassContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it]!!
}
}
}
class KotlinPushDownProcessor(
@@ -161,7 +168,7 @@ class KotlinPushDownProcessor(
}
}
is KtClassOrObject -> {
is KtClassOrObject, is KtPsiClassWrapper -> {
if (memberInfo.overrides != null) {
context.sourceClass.getSuperTypeEntryByDescriptor(
memberDescriptor as ClassDescriptor,
@@ -202,7 +209,7 @@ class KotlinPushDownProcessor(
member.delete()
}
}
is KtClassOrObject -> {
is KtClassOrObject, is KtPsiClassWrapper -> {
if (memberInfo.overrides != null) {
context.sourceClass.getSuperTypeEntryByDescriptor(
memberDescriptor as ClassDescriptor,
@@ -24,6 +24,7 @@ import com.intellij.usageView.UsageInfo
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KtPsiClassWrapper
import org.jetbrains.kotlin.idea.refactoring.pullUp.renderForConflicts
import org.jetbrains.kotlin.idea.references.KtReference
import org.jetbrains.kotlin.idea.references.mainReference
@@ -50,7 +51,7 @@ fun analyzePushDownConflicts(context: KotlinPushDownContext,
val membersToKeepAbstract = ArrayList<KtNamedDeclaration>()
for (info in context.membersToMove) {
val member = info.member
if (!info.isChecked || (member is KtClassOrObject && info.overrides != null)) continue
if (!info.isChecked || ((member is KtClassOrObject || member is KtPsiClassWrapper) && info.overrides != null)) continue
membersToPush += member
if ((member is KtNamedFunction || member is KtProperty)
@@ -1,3 +1,8 @@
class A implements Z {
}
// INFO: {"checked": "true"}
interface J {
}
@@ -1,3 +1,8 @@
class A implements Z, X {
class A implements Z, X, J {
}
// INFO: {"checked": "true"}
interface J {
}
@@ -7,4 +7,4 @@ interface Y
// INFO: {"checked": "true"}
interface Z
class <caret>B: A(), X, Y, Z
class <caret>B: A(), X, Y, Z, J
@@ -1,3 +1,8 @@
interface I extends Z {
}
// INFO: {"checked": "true"}
interface J {
}
@@ -1,3 +1,8 @@
interface I extends Z, X {
interface I extends Z, X, J {
}
// INFO: {"checked": "true"}
interface J {
}
@@ -7,4 +7,4 @@ interface Y
// INFO: {"checked": "true"}
interface Z
class <caret>B: I, X, Y, Z
class <caret>B: I, X, Y, Z, J
@@ -0,0 +1,4 @@
// INFO: {"checked": "true"}
interface J {
}
@@ -0,0 +1,4 @@
// INFO: {"checked": "true"}
interface J {
}
@@ -11,4 +11,4 @@ interface Y
// INFO: {"checked": "true"}
interface Z
class <caret>B: A(), X, Y, Z
class <caret>B: A(), X, Y, Z, J
@@ -1,4 +1,4 @@
open class A: Z, X, Y {
open class A: Z, X, Y, J {
}
@@ -25,6 +25,7 @@ import com.intellij.refactoring.BaseRefactoringProcessor
import com.intellij.refactoring.classMembers.MemberInfoBase
import com.intellij.refactoring.util.CommonRefactoringUtil
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KtPsiClassWrapper
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
@@ -64,6 +65,7 @@ abstract class AbstractMemberPullPushTest : KotlinLightCodeInsightFixtureTestCas
try {
markMembersInfo(file)
extraFilesToPsi.keys.forEach(::markMembersInfo)
action(file)
@@ -105,7 +107,8 @@ internal var PsiElement.elementInfo: ElementInfo by NotNullableUserDataProperty(
internal fun <T : MemberInfoBase<*>> chooseMembers(members: List<T>): List<T> {
members.forEach {
val info = it.member.elementInfo
val memberPsi = it.member.let { if (it is KtPsiClassWrapper) it.psiClass else it }
val info = memberPsi.elementInfo
it.isChecked = info.checked
it.isToAbstract = info.toAbstract
}