From 96f255225b1706313fe4f9b29c2e3d9e5c4b58b9 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Tue, 11 Aug 2015 14:05:27 +0300 Subject: [PATCH] Push Down: Conflict analysis --- .../idea/refactoring/jetRefactoringUtil.kt | 7 + .../pullUp/pullUpConflictsUtils.kt | 2 +- .../pushDown/KotlinPushDownProcessor.kt | 87 ++++--- .../pushDown/pushDownConflictsUtils.kt | 220 ++++++++++++++++++ .../pushDown/accidentalOverrides.kt | 24 ++ .../pushDown/accidentalOverrides.kt.messages | 4 + .../refactoring/pushDown/clashingMembers.kt | 32 +++ .../pushDown/clashingMembers.kt.messages | 3 + .../refactoring/pushDown/classToInterface.kt | 6 + .../pushDown/classToInterface.kt.messages | 2 + .../pushDown/conflictingSuperCall.kt | 30 +++ .../pushDown/conflictingSuperCall.kt.messages | 1 + .../refactoring/pushDown/kotlinToJava.java | 3 + .../refactoring/pushDown/kotlinToJava.kt | 3 + .../pushDown/kotlinToJava.kt.messages | 1 + .../refactoring/pushDown/liftPrivate.kt | 8 +- .../refactoring/pushDown/liftPrivate.kt.after | 6 +- .../pushDown/pushMembersUsingPrivates.kt | 14 ++ .../pushMembersUsingPrivates.kt.messages | 1 + .../pushDown/pushMembersWithExternalUsages.kt | 37 +++ .../pushMembersWithExternalUsages.kt.messages | 3 + .../pushDown/PushDownTestGenerated.java | 42 ++++ 22 files changed, 502 insertions(+), 34 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/pushDown/pushDownConflictsUtils.kt create mode 100644 idea/testData/refactoring/pushDown/accidentalOverrides.kt create mode 100644 idea/testData/refactoring/pushDown/accidentalOverrides.kt.messages create mode 100644 idea/testData/refactoring/pushDown/clashingMembers.kt create mode 100644 idea/testData/refactoring/pushDown/clashingMembers.kt.messages create mode 100644 idea/testData/refactoring/pushDown/classToInterface.kt create mode 100644 idea/testData/refactoring/pushDown/classToInterface.kt.messages create mode 100644 idea/testData/refactoring/pushDown/conflictingSuperCall.kt create mode 100644 idea/testData/refactoring/pushDown/conflictingSuperCall.kt.messages create mode 100644 idea/testData/refactoring/pushDown/kotlinToJava.java create mode 100644 idea/testData/refactoring/pushDown/kotlinToJava.kt create mode 100644 idea/testData/refactoring/pushDown/kotlinToJava.kt.messages create mode 100644 idea/testData/refactoring/pushDown/pushMembersUsingPrivates.kt create mode 100644 idea/testData/refactoring/pushDown/pushMembersUsingPrivates.kt.messages create mode 100644 idea/testData/refactoring/pushDown/pushMembersWithExternalUsages.kt create mode 100644 idea/testData/refactoring/pushDown/pushMembersWithExternalUsages.kt.messages diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt index 62dfc51be05..87d29ed4848 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt @@ -29,6 +29,7 @@ import com.intellij.openapi.editor.markup.HighlighterTargetArea import com.intellij.openapi.editor.markup.RangeHighlighter import com.intellij.openapi.editor.markup.TextAttributes import com.intellij.openapi.options.ConfigurationException +import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.project.Project import com.intellij.openapi.roots.JavaProjectRootsUtil import com.intellij.openapi.ui.popup.JBPopup @@ -638,4 +639,10 @@ public fun JetElement.validateElement(errorMessage: String) { catch(e: Exception) { throw ConfigurationException(errorMessage) } +} + +public fun Project.runSynchronouslyWithProgress(progressTitle: String, canBeCanceled: Boolean, action: () -> T): T? { + var result: T? = null + ProgressManager.getInstance().runProcessWithProgressSynchronously( { result = action() }, progressTitle, canBeCanceled, this) + return result } \ No newline at end of file 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 20c7b9ee564..317907c4db9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/pullUpConflictsUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/pullUpConflictsUtils.kt @@ -74,7 +74,7 @@ private val CALLABLE_RENDERER = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_I startFromName = false } -private fun DeclarationDescriptor.renderForConflicts(): String { +fun DeclarationDescriptor.renderForConflicts(): String { return when (this) { is ClassDescriptor -> "${DescriptorRenderer.getClassKindPrefix(this)} ${IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(this)}" is FunctionDescriptor -> "function '${CALLABLE_RENDERER.render(this)}'" diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/pushDown/KotlinPushDownProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/pushDown/KotlinPushDownProcessor.kt index 51fe12eda80..30b1daad4ed 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/pushDown/KotlinPushDownProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/pushDown/KotlinPushDownProcessor.kt @@ -17,6 +17,8 @@ package org.jetbrains.kotlin.idea.refactoring.pushDown import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.Messages +import com.intellij.openapi.util.Ref import com.intellij.psi.PsiElement import com.intellij.refactoring.BaseRefactoringProcessor import com.intellij.refactoring.RefactoringBundle @@ -31,10 +33,12 @@ import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet +import org.jetbrains.kotlin.idea.core.refactoring.runSynchronouslyWithProgress import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo import org.jetbrains.kotlin.idea.refactoring.pullUp.* import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest import org.jetbrains.kotlin.idea.search.declarationsSearch.searchInheritors +import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.lexer.JetTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext @@ -45,25 +49,32 @@ import org.jetbrains.kotlin.util.findCallableMemberBySignature import org.jetbrains.kotlin.utils.keysToMap import java.util.ArrayList -public class KotlinPushDownProcessor( - project: Project, - private val sourceClass: JetClass, - private val membersToMove: List -) : BaseRefactoringProcessor(project) { - private val resolutionFacade = sourceClass.getResolutionFacade() +public class KotlinPushDownContext( + val sourceClass: JetClass, + val membersToMove: List +) { + val resolutionFacade = sourceClass.getResolutionFacade() - private val sourceClassContext = resolutionFacade.analyzeFullyAndGetResult(listOf(sourceClass)).bindingContext + val sourceClassContext = resolutionFacade.analyzeFullyAndGetResult(listOf(sourceClass)).bindingContext - private val sourceClassDescriptor = sourceClassContext[BindingContext.DECLARATION_TO_DESCRIPTOR, sourceClass] as ClassDescriptor + val sourceClassDescriptor = sourceClassContext[BindingContext.DECLARATION_TO_DESCRIPTOR, sourceClass] as ClassDescriptor - private val memberDescriptors = membersToMove + val memberDescriptors = membersToMove .map { it.member } .keysToMap { sourceClassContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it]!! } +} + +public class KotlinPushDownProcessor( + project: Project, + sourceClass: JetClass, + membersToMove: List +) : BaseRefactoringProcessor(project) { + private val context = KotlinPushDownContext(sourceClass, membersToMove) inner class UsageViewDescriptorImpl : UsageViewDescriptor { override fun getProcessedElementsHeader() = RefactoringBundle.message("push.down.members.elements.header") - override fun getElements() = arrayOf(sourceClass) + override fun getElements() = arrayOf(context.sourceClass) override fun getCodeReferencesText(usagesCount: Int, filesCount: Int) = RefactoringBundle.message("classes.to.push.down.members.to", UsageViewBundle.getReferencesString(usagesCount, filesCount)) @@ -78,8 +89,8 @@ public class KotlinPushDownProcessor( override fun createUsageViewDescriptor(usages: Array) = UsageViewDescriptorImpl() override fun getBeforeData() = RefactoringEventData().apply { - addElement(sourceClass) - addElements(membersToMove.map { it.member }.toTypedArray()) + addElement(context.sourceClass) + addElements(context.membersToMove.map { it.member }.toTypedArray()) } override fun getAfterData(usages: Array) = RefactoringEventData().apply { @@ -87,7 +98,7 @@ public class KotlinPushDownProcessor( } override fun findUsages(): Array { - return HierarchySearchRequest(sourceClass, sourceClass.useScope, false) + return HierarchySearchRequest(context.sourceClass, context.sourceClass.useScope, false) .searchInheritors() .map { it.unwrapped } .filterNotNull() @@ -95,13 +106,29 @@ public class KotlinPushDownProcessor( .toTypedArray() } + override fun preprocessUsages(refUsages: Ref>): Boolean { + val usages = refUsages.get() ?: UsageInfo.EMPTY_ARRAY + if (usages.isEmpty()) { + val message = "${context.sourceClassDescriptor.renderForConflicts()} doesn't have inheritors\n" + + "Pushing members down will result in them being deleted. Would you like to proceed?" + val answer = Messages.showYesNoDialog(message.capitalize(), PUSH_MEMBERS_DOWN, Messages.getWarningIcon()) + if (answer == Messages.NO) return false + } + + val conflicts = myProject.runSynchronouslyWithProgress(RefactoringBundle.message("detecting.possible.conflicts"), true) { + runReadAction { analyzePushDownConflicts(context, usages) } + } ?: return false + + return showConflicts(conflicts, usages) + } + private fun pushDownToClass(targetClass: JetClassOrObject) { - val targetClassDescriptor = resolutionFacade.resolveToDescriptor(targetClass) as ClassDescriptor - val substitutor = getTypeSubstitutor(sourceClassDescriptor.defaultType, targetClassDescriptor.defaultType) + val targetClassDescriptor = context.resolutionFacade.resolveToDescriptor(targetClass) as ClassDescriptor + val substitutor = getTypeSubstitutor(context.sourceClassDescriptor.defaultType, targetClassDescriptor.defaultType) ?: TypeSubstitutor.EMPTY - members@ for (memberInfo in membersToMove) { + members@ for (memberInfo in context.membersToMove) { val member = memberInfo.member - val memberDescriptor = memberDescriptors[member] ?: continue + val memberDescriptor = context.memberDescriptors[member] ?: continue val movedMember = when (member) { is JetProperty, is JetNamedFunction -> { @@ -122,7 +149,7 @@ public class KotlinPushDownProcessor( addModifierWithSpace(JetTokens.OVERRIDE_KEYWORD) } } ?: addMemberToTarget(member, targetClass).apply { - if (sourceClassDescriptor.kind == ClassKind.INTERFACE) { + if (context.sourceClassDescriptor.kind == ClassKind.INTERFACE) { if (targetClassDescriptor.kind != ClassKind.INTERFACE && memberDescriptor.modality == Modality.ABSTRACT) { addModifierWithSpace(JetTokens.ABSTRACT_KEYWORD) } @@ -138,8 +165,11 @@ public class KotlinPushDownProcessor( is JetClassOrObject -> { if (memberInfo.overrides != null) { - sourceClass.getDelegatorToSuperClassByDescriptor(memberDescriptor as ClassDescriptor, sourceClassContext)?.let { - addDelegatorToSuperClass(it, targetClass, targetClassDescriptor, sourceClassContext, substitutor) + context.sourceClass.getDelegatorToSuperClassByDescriptor( + memberDescriptor as ClassDescriptor, + context.sourceClassContext + )?.let { + addDelegatorToSuperClass(it, targetClass, targetClassDescriptor, context.sourceClassContext, substitutor) } continue@members } @@ -155,9 +185,9 @@ public class KotlinPushDownProcessor( } private fun removeOriginalMembers() { - for (memberInfo in membersToMove) { + for (memberInfo in context.membersToMove) { val member = memberInfo.member - val memberDescriptor = memberDescriptors[member] ?: continue + val memberDescriptor = context.memberDescriptors[member] ?: continue when (member) { is JetProperty, is JetNamedFunction -> { member as JetCallableDeclaration @@ -167,7 +197,7 @@ public class KotlinPushDownProcessor( if (member.hasModifier(JetTokens.PRIVATE_KEYWORD)) { member.addModifierWithSpace(JetTokens.PROTECTED_KEYWORD) } - makeAbstract(member, memberDescriptor, TypeSubstitutor.EMPTY, sourceClass) + makeAbstract(member, memberDescriptor, TypeSubstitutor.EMPTY, context.sourceClass) member.typeReference?.addToShorteningWaitSet() } else { @@ -176,8 +206,11 @@ public class KotlinPushDownProcessor( } is JetClassOrObject -> { if (memberInfo.overrides != null) { - sourceClass.getDelegatorToSuperClassByDescriptor(memberDescriptor as ClassDescriptor, sourceClassContext)?.let { - sourceClass.removeDelegationSpecifier(it) + context.sourceClass.getDelegatorToSuperClassByDescriptor( + memberDescriptor as ClassDescriptor, + context.sourceClassContext + )?.let { + context.sourceClass.removeDelegationSpecifier(it) } } else { @@ -191,7 +224,9 @@ public class KotlinPushDownProcessor( override fun performRefactoring(usages: Array) { val markedElements = ArrayList() try { - membersToMove.forEach { markedElements += markElements(it.member, sourceClassContext, sourceClassDescriptor, null) } + context.membersToMove.forEach { + markedElements += markElements(it.member, context.sourceClassContext, context.sourceClassDescriptor, null) + } usages.forEach { (it.element as? JetClassOrObject)?.let { pushDownToClass(it) } } removeOriginalMembers() } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/pushDown/pushDownConflictsUtils.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/pushDown/pushDownConflictsUtils.kt new file mode 100644 index 00000000000..5c27620b37f --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/pushDown/pushDownConflictsUtils.kt @@ -0,0 +1,220 @@ +/* + * Copyright 2010-2015 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.pushDown + +import com.intellij.psi.PsiElement +import com.intellij.psi.search.searches.ReferencesSearch +import com.intellij.refactoring.util.CommonRefactoringUtil +import com.intellij.refactoring.util.RefactoringUIUtil +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.pullUp.renderForConflicts +import org.jetbrains.kotlin.idea.references.JetReference +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.lexer.JetTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForReceiver +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.scopes.receivers.QualifierReceiver +import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue +import org.jetbrains.kotlin.resolve.source.getPsi +import org.jetbrains.kotlin.types.TypeSubstitutor +import org.jetbrains.kotlin.types.substitutions.getTypeSubstitutor +import org.jetbrains.kotlin.util.findCallableMemberBySignature +import java.util.ArrayList + +fun analyzePushDownConflicts(context: KotlinPushDownContext, + usages: Array): MultiMap { + val targetClasses = usages.map { it.element?.unwrapped }.filterNotNull() + + val conflicts = MultiMap() + + val membersToPush = ArrayList() + val membersToKeepAbstract = ArrayList() + for (info in context.membersToMove) { + val member = info.member + if (!info.isChecked || (member is JetClassOrObject && info.overrides != null)) continue + + membersToPush += member + if ((member is JetNamedFunction || member is JetProperty) + && info.isToAbstract + && (context.memberDescriptors[member] as CallableMemberDescriptor).modality != Modality.ABSTRACT) { + membersToKeepAbstract += member + } + } + + for (targetClass in targetClasses) { + checkConflicts(conflicts, context, targetClass, membersToKeepAbstract, membersToPush) + } + + return conflicts +} + +private fun checkConflicts( + conflicts: MultiMap, + context: KotlinPushDownContext, + targetClass: PsiElement, + membersToKeepAbstract: List, + membersToPush: ArrayList +) { + if (targetClass !is JetClassOrObject) { + conflicts.putValue( + targetClass, + "Non-Kotlin ${RefactoringUIUtil.getDescription(targetClass, false)} won't be affected by the refactoring" + ) + return + } + + val targetClassDescriptor = context.resolutionFacade.resolveToDescriptor(targetClass) as ClassDescriptor + val substitutor = getTypeSubstitutor(context.sourceClassDescriptor.defaultType, targetClassDescriptor.defaultType) + ?: TypeSubstitutor.EMPTY + + if (!context.sourceClass.isInterface() && targetClass is JetClass && targetClass.isInterface()) { + val message = "${targetClassDescriptor.renderForConflicts()} " + + "inherits from ${context.sourceClassDescriptor.renderForConflicts()}.\n" + + "It won't be affected by the refactoring" + conflicts.putValue(targetClass, message.capitalize()) + } + + for (member in membersToPush) { + checkMemberClashing(conflicts, context, member, membersToKeepAbstract, substitutor, targetClass, targetClassDescriptor) + checkSuperCalls(conflicts, context, member, membersToPush) + checkExternalUsages(conflicts, context, member, targetClassDescriptor) + checkVisibility(conflicts, context, member, targetClassDescriptor) + } +} + +private fun checkMemberClashing( + conflicts: MultiMap, + context: KotlinPushDownContext, + member: JetNamedDeclaration, + membersToKeepAbstract: List, + substitutor: TypeSubstitutor, + targetClass: JetClassOrObject, + targetClassDescriptor: ClassDescriptor) { + when (member) { + is JetNamedFunction, is JetProperty -> { + val memberDescriptor = context.memberDescriptors[member] as CallableMemberDescriptor + val clashingDescriptor = targetClassDescriptor.findCallableMemberBySignature(memberDescriptor.substitute(substitutor) as CallableMemberDescriptor) + val clashingDeclaration = clashingDescriptor?.source?.getPsi() as? JetNamedDeclaration + if (clashingDescriptor != null && clashingDeclaration != null) { + if (memberDescriptor.modality != Modality.ABSTRACT && member !in membersToKeepAbstract) { + val message = "${targetClassDescriptor.renderForConflicts()} already contains ${clashingDescriptor.renderForConflicts()}" + conflicts.putValue(clashingDeclaration, CommonRefactoringUtil.capitalize(message)) + } + if (!clashingDeclaration.hasModifier(JetTokens.OVERRIDE_KEYWORD)) { + val message = "${clashingDescriptor.renderForConflicts()} in ${targetClassDescriptor.renderForConflicts()} " + + "will override corresponding member of ${context.sourceClassDescriptor.renderForConflicts()} " + + "after refactoring" + conflicts.putValue(clashingDeclaration, CommonRefactoringUtil.capitalize(message)) + } + } + } + + is JetClassOrObject -> { + targetClass.declarations + .filterIsInstance() + .firstOrNull() { it.name == member.name } + ?.let { + val message = "${targetClassDescriptor.renderForConflicts()} " + + "already contains nested class named ${CommonRefactoringUtil.htmlEmphasize(member.name)}" + conflicts.putValue(it, message.capitalize()) + } + } + } +} + +private fun checkSuperCalls( + conflicts: MultiMap, + context: KotlinPushDownContext, + member: JetNamedDeclaration, + membersToPush: ArrayList +) { + member.accept( + object : JetTreeVisitorVoid() { + override fun visitSuperExpression(expression: JetSuperExpression) { + val qualifiedExpression = expression.getQualifiedExpressionForReceiver() ?: return + val refExpr = qualifiedExpression.selectorExpression.getCalleeExpressionIfAny() as? JetSimpleNameExpression ?: return + for (descriptor in refExpr.mainReference.resolveToDescriptors(context.sourceClassContext)) { + val memberDescriptor = descriptor as? CallableMemberDescriptor ?: continue + val containingClass = memberDescriptor.containingDeclaration as? ClassDescriptor ?: continue + if (!DescriptorUtils.isSubclass(context.sourceClassDescriptor, containingClass)) continue + val memberInSource = context.sourceClassDescriptor.findCallableMemberBySignature(memberDescriptor)?.source?.getPsi() + ?: continue + if (memberInSource !in membersToPush) { + conflicts.putValue(qualifiedExpression, + "Pushed member won't be available in '${qualifiedExpression.text}'") + } + } + } + } + ) +} + +private fun checkExternalUsages( + conflicts: MultiMap, + context: KotlinPushDownContext, + member: JetNamedDeclaration, + targetClassDescriptor: ClassDescriptor +) { + for (ref in ReferencesSearch.search(member, member.resolveScope, false)) { + val calleeExpr = ref.element as? JetSimpleNameExpression ?: continue + val resolvedCall = calleeExpr.getResolvedCall(context.resolutionFacade.analyze(calleeExpr)) ?: continue + val callElement = resolvedCall.call.callElement + val dispatchReceiver = resolvedCall.dispatchReceiver + if (!dispatchReceiver.exists() || dispatchReceiver is QualifierReceiver) continue + val receiverClassDescriptor = dispatchReceiver.type.constructor.declarationDescriptor as? ClassDescriptor ?: continue + if (!DescriptorUtils.isSubclass(receiverClassDescriptor, targetClassDescriptor)) { + conflicts.putValue(callElement, "Pushed member won't be available in '${callElement.text}'") + } + } +} + +private fun checkVisibility( + conflicts: MultiMap, + context: KotlinPushDownContext, + member: JetNamedDeclaration, + targetClassDescriptor: ClassDescriptor +) { + fun reportConflictIfAny(targetDescriptor: DeclarationDescriptor) { + val target = (targetDescriptor as? DeclarationDescriptorWithSource)?.source?.getPsi() ?: return + if (targetDescriptor is DeclarationDescriptorWithVisibility + && !Visibilities.isVisible(ReceiverValue.IRRELEVANT_RECEIVER, targetDescriptor, targetClassDescriptor)) { + val message = "${context.memberDescriptors[member]!!.renderForConflicts()} " + + "uses ${targetDescriptor.renderForConflicts()}, " + + "which is not accessible from the ${targetClassDescriptor.renderForConflicts()}" + conflicts.putValue(target, message.capitalize()) + } + } + + member.accept( + object : JetTreeVisitorVoid() { + override fun visitReferenceExpression(expression: JetReferenceExpression) { + super.visitReferenceExpression(expression) + + expression.references + .flatMap { (it as? JetReference)?.resolveToDescriptors(context.sourceClassContext) ?: emptyList() } + .forEach(::reportConflictIfAny) + + } + } + ) +} diff --git a/idea/testData/refactoring/pushDown/accidentalOverrides.kt b/idea/testData/refactoring/pushDown/accidentalOverrides.kt new file mode 100644 index 00000000000..d8517007a44 --- /dev/null +++ b/idea/testData/refactoring/pushDown/accidentalOverrides.kt @@ -0,0 +1,24 @@ +abstract class A { + // INFO: {"checked": "true"} + abstract val y: Boolean + // INFO: {"checked": "true", "toAbstract": "true"} + val z: Int = 1 + // INFO: {"checked": "true"} + abstract fun foo(n: Int, m: Int) + // INFO: {"checked": "true", "toAbstract": "true"} + fun foo(b: Boolean) = !b +} + +class B : A { + val x: Int = 2 + + val y: Boolean get() = x > 0 + + val z: Int = 3 + + fun foo(n: Int) = n + 2 + + fun foo(n: Int, m: Int) = n + m + + fun foo(b: Boolean) = true +} \ No newline at end of file diff --git a/idea/testData/refactoring/pushDown/accidentalOverrides.kt.messages b/idea/testData/refactoring/pushDown/accidentalOverrides.kt.messages new file mode 100644 index 00000000000..02ab7c3b125 --- /dev/null +++ b/idea/testData/refactoring/pushDown/accidentalOverrides.kt.messages @@ -0,0 +1,4 @@ +Function 'fun foo(Boolean): Boolean' in class B will override corresponding member of class A after refactoring +Function 'fun foo(Int, Int): Int' in class B will override corresponding member of class A after refactoring +Property 'val y: Boolean' in class B will override corresponding member of class A after refactoring +Property 'val z: Int' in class B will override corresponding member of class A after refactoring \ No newline at end of file diff --git a/idea/testData/refactoring/pushDown/clashingMembers.kt b/idea/testData/refactoring/pushDown/clashingMembers.kt new file mode 100644 index 00000000000..1b9c8869eba --- /dev/null +++ b/idea/testData/refactoring/pushDown/clashingMembers.kt @@ -0,0 +1,32 @@ +abstract class A { + // INFO: {"checked": "true"} + open val x: Int = 1 + // INFO: {"checked": "true"} + abstract val y: Boolean + // INFO: {"checked": "true", "toAbstract": "true"} + open val z: Int = 1 + // INFO: {"checked": "true"} + open fun foo(n: Int) = n + 1 + // INFO: {"checked": "true"} + abstract fun foo(n: Int, m: Int) + // INFO: {"checked": "true", "toAbstract": "true"} + open fun foo(b: Boolean) = !b + // INFO: {"checked": "true"} + class X +} + +class B : A { + override val x: Int = 2 + + override val y: Boolean get() = x > 0 + + override val z: Int = 3 + + override fun foo(n: Int) = n + 2 + + override fun foo(n: Int, m: Int) = n + m + + override fun foo(b: Boolean) = true + + class X +} \ No newline at end of file diff --git a/idea/testData/refactoring/pushDown/clashingMembers.kt.messages b/idea/testData/refactoring/pushDown/clashingMembers.kt.messages new file mode 100644 index 00000000000..b204e5d8b97 --- /dev/null +++ b/idea/testData/refactoring/pushDown/clashingMembers.kt.messages @@ -0,0 +1,3 @@ +Class B already contains function 'fun foo(Int): Int' +Class B already contains nested class named X +Class B already contains property 'val x: Int' \ No newline at end of file diff --git a/idea/testData/refactoring/pushDown/classToInterface.kt b/idea/testData/refactoring/pushDown/classToInterface.kt new file mode 100644 index 00000000000..887591740e3 --- /dev/null +++ b/idea/testData/refactoring/pushDown/classToInterface.kt @@ -0,0 +1,6 @@ +open class A { + // INFO: {"checked": "true"} + val x: Int +} + +interface I : A \ No newline at end of file diff --git a/idea/testData/refactoring/pushDown/classToInterface.kt.messages b/idea/testData/refactoring/pushDown/classToInterface.kt.messages new file mode 100644 index 00000000000..a7bc5cf36d5 --- /dev/null +++ b/idea/testData/refactoring/pushDown/classToInterface.kt.messages @@ -0,0 +1,2 @@ +Interface I inherits from class A. +It won't be affected by the refactoring \ No newline at end of file diff --git a/idea/testData/refactoring/pushDown/conflictingSuperCall.kt b/idea/testData/refactoring/pushDown/conflictingSuperCall.kt new file mode 100644 index 00000000000..fe7a873f8d4 --- /dev/null +++ b/idea/testData/refactoring/pushDown/conflictingSuperCall.kt @@ -0,0 +1,30 @@ +open class A { + open fun foo(n: Int) { + + } + + open fun bar(n: Int) { + + } +} + +open class B : A() { + // INFO: {"checked": "true"} + fun foo() { + super.foo(1) + super.bar(1) + } + + // INFO: {"checked": "true"} + override fun foo(n: Int) { + + } + + override fun bar(n: Int) { + + } +} + +class C : B() { + +} \ No newline at end of file diff --git a/idea/testData/refactoring/pushDown/conflictingSuperCall.kt.messages b/idea/testData/refactoring/pushDown/conflictingSuperCall.kt.messages new file mode 100644 index 00000000000..21e6b49d848 --- /dev/null +++ b/idea/testData/refactoring/pushDown/conflictingSuperCall.kt.messages @@ -0,0 +1 @@ +Pushed member won't be available in 'super.bar(1)' \ No newline at end of file diff --git a/idea/testData/refactoring/pushDown/kotlinToJava.java b/idea/testData/refactoring/pushDown/kotlinToJava.java new file mode 100644 index 00000000000..e64e5410e0b --- /dev/null +++ b/idea/testData/refactoring/pushDown/kotlinToJava.java @@ -0,0 +1,3 @@ +class B extends A { + +} \ No newline at end of file diff --git a/idea/testData/refactoring/pushDown/kotlinToJava.kt b/idea/testData/refactoring/pushDown/kotlinToJava.kt new file mode 100644 index 00000000000..cf00dbda916 --- /dev/null +++ b/idea/testData/refactoring/pushDown/kotlinToJava.kt @@ -0,0 +1,3 @@ +open class A { + val x: Int = 1 +} \ No newline at end of file diff --git a/idea/testData/refactoring/pushDown/kotlinToJava.kt.messages b/idea/testData/refactoring/pushDown/kotlinToJava.kt.messages new file mode 100644 index 00000000000..37670627482 --- /dev/null +++ b/idea/testData/refactoring/pushDown/kotlinToJava.kt.messages @@ -0,0 +1 @@ +Non-Kotlin class B won't be affected by the refactoring \ No newline at end of file diff --git a/idea/testData/refactoring/pushDown/liftPrivate.kt b/idea/testData/refactoring/pushDown/liftPrivate.kt index a148509db7a..864d50abf42 100644 --- a/idea/testData/refactoring/pushDown/liftPrivate.kt +++ b/idea/testData/refactoring/pushDown/liftPrivate.kt @@ -1,12 +1,12 @@ open class A { // INFO: {"checked": "true", "toAbstract": "true"} - private fun foo() { + private open fun foo() { } -} -class B : A { - fun foo() { + inner class B : A() { + override fun foo() { + } } } \ No newline at end of file diff --git a/idea/testData/refactoring/pushDown/liftPrivate.kt.after b/idea/testData/refactoring/pushDown/liftPrivate.kt.after index 9b0715ff346..87aff29b725 100644 --- a/idea/testData/refactoring/pushDown/liftPrivate.kt.after +++ b/idea/testData/refactoring/pushDown/liftPrivate.kt.after @@ -1,10 +1,10 @@ open class A { // INFO: {"checked": "true", "toAbstract": "true"} protected abstract fun foo() -} -class B : A { - override fun foo() { + inner class B : A() { + override fun foo() { + } } } \ No newline at end of file diff --git a/idea/testData/refactoring/pushDown/pushMembersUsingPrivates.kt b/idea/testData/refactoring/pushDown/pushMembersUsingPrivates.kt new file mode 100644 index 00000000000..91c1a721ec3 --- /dev/null +++ b/idea/testData/refactoring/pushDown/pushMembersUsingPrivates.kt @@ -0,0 +1,14 @@ +open class A { + private fun foo() { + + } + + // INFO: {"checked": "true"} + fun bar() { + foo() + } +} + +class B : A() { + +} \ No newline at end of file diff --git a/idea/testData/refactoring/pushDown/pushMembersUsingPrivates.kt.messages b/idea/testData/refactoring/pushDown/pushMembersUsingPrivates.kt.messages new file mode 100644 index 00000000000..49b765eb332 --- /dev/null +++ b/idea/testData/refactoring/pushDown/pushMembersUsingPrivates.kt.messages @@ -0,0 +1 @@ +Function 'fun bar()' uses function 'fun foo()', which is not accessible from the class B \ No newline at end of file diff --git a/idea/testData/refactoring/pushDown/pushMembersWithExternalUsages.kt b/idea/testData/refactoring/pushDown/pushMembersWithExternalUsages.kt new file mode 100644 index 00000000000..09ad0edbc0b --- /dev/null +++ b/idea/testData/refactoring/pushDown/pushMembersWithExternalUsages.kt @@ -0,0 +1,37 @@ +open class A { + fun foo() { + + } + + // INFO: {"checked": "true"} + fun foo(n: Int) { + + } + + fun test() { + foo() + foo(1) + } +} + +open class B : A() { + +} + +class C : B() { + +} + +fun A.test() { + foo() + foo(2) +} + +fun test() { + A().foo() + A().foo(3) + B().foo() + B().foo(4) + C().foo() + C().foo(5) +} \ No newline at end of file diff --git a/idea/testData/refactoring/pushDown/pushMembersWithExternalUsages.kt.messages b/idea/testData/refactoring/pushDown/pushMembersWithExternalUsages.kt.messages new file mode 100644 index 00000000000..4175bfc9bd5 --- /dev/null +++ b/idea/testData/refactoring/pushDown/pushMembersWithExternalUsages.kt.messages @@ -0,0 +1,3 @@ +Pushed member won't be available in 'foo(1)' +Pushed member won't be available in 'foo(2)' +Pushed member won't be available in 'foo(3)' \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/pushDown/PushDownTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/pushDown/PushDownTestGenerated.java index 217ce594a47..6e849897e7e 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/pushDown/PushDownTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/pushDown/PushDownTestGenerated.java @@ -31,10 +31,34 @@ import java.util.regex.Pattern; @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) public class PushDownTestGenerated extends AbstractPushDownTest { + @TestMetadata("accidentalOverrides.kt") + public void testAccidentalOverrides() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/accidentalOverrides.kt"); + doTest(fileName); + } + public void testAllFilesPresentInPushDown() throws Exception { JetTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/refactoring/pushDown"), Pattern.compile("^(.+)\\.kt$")); } + @TestMetadata("clashingMembers.kt") + public void testClashingMembers() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/clashingMembers.kt"); + doTest(fileName); + } + + @TestMetadata("classToInterface.kt") + public void testClassToInterface() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/classToInterface.kt"); + doTest(fileName); + } + + @TestMetadata("conflictingSuperCall.kt") + public void testConflictingSuperCall() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/conflictingSuperCall.kt"); + doTest(fileName); + } + @TestMetadata("finalClass.kt") public void testFinalClass() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/finalClass.kt"); @@ -47,6 +71,12 @@ public class PushDownTestGenerated extends AbstractPushDownTest { doTest(fileName); } + @TestMetadata("kotlinToJava.kt") + public void testKotlinToJava() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/kotlinToJava.kt"); + doTest(fileName); + } + @TestMetadata("liftPrivate.kt") public void testLiftPrivate() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/liftPrivate.kt"); @@ -101,6 +131,18 @@ public class PushDownTestGenerated extends AbstractPushDownTest { doTest(fileName); } + @TestMetadata("pushMembersUsingPrivates.kt") + public void testPushMembersUsingPrivates() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/pushMembersUsingPrivates.kt"); + doTest(fileName); + } + + @TestMetadata("pushMembersWithExternalUsages.kt") + public void testPushMembersWithExternalUsages() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/pushMembersWithExternalUsages.kt"); + doTest(fileName); + } + @TestMetadata("pushSuperInterfaces.kt") public void testPushSuperInterfaces() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/pushSuperInterfaces.kt");