Move: Process internal usages after all declarations are moved

This commit is contained in:
Alexey Sedunov
2017-04-04 19:45:06 +03:00
parent a9e7d3d5cf
commit 4a168ff780
8 changed files with 171 additions and 136 deletions
@@ -24,7 +24,6 @@ import com.intellij.util.IncorrectOperationException
import com.intellij.util.SmartList
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet
import org.jetbrains.kotlin.idea.codeInsight.shorten.isToBeShortened
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.core.copied
import org.jetbrains.kotlin.idea.core.quoteIfNeeded
@@ -38,7 +37,10 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.isOneSegmentFQN
import org.jetbrains.kotlin.plugin.references.SimpleNameReferenceExtension
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementOrCallableRef
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DataClassDescriptorResolver
import org.jetbrains.kotlin.types.expressions.OperatorConventions
@@ -130,7 +132,6 @@ class KtSimpleNameReference(expression: KtSimpleNameExpression) : KtSimpleRefere
enum class ShorteningMode {
NO_SHORTENING,
DELAYED_SHORTENING,
DELAYED_SHORTENING_VIA_USER_DATA,
FORCED_SHORTENING
}
@@ -159,7 +160,6 @@ class KtSimpleNameReference(expression: KtSimpleNameExpression) : KtSimpleRefere
if (needToShorten) {
when (shorteningMode) {
ShorteningMode.FORCED_SHORTENING -> ShortenReferences.DEFAULT.process(newQualifiedElement)
ShorteningMode.DELAYED_SHORTENING_VIA_USER_DATA -> newQualifiedElement.isToBeShortened = true
else -> newQualifiedElement.addToShorteningWaitSet()
}
}
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations
import com.intellij.ide.util.EditorHelper
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.Ref
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiElement
@@ -29,6 +30,7 @@ import com.intellij.refactoring.move.MoveCallback
import com.intellij.refactoring.move.MoveMultipleElementsViewDescriptor
import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassHandler
import com.intellij.refactoring.rename.RenameUtil
import com.intellij.refactoring.util.MoveRenameUsageInfo
import com.intellij.refactoring.util.NonCodeUsageInfo
import com.intellij.refactoring.util.RefactoringUIUtil
import com.intellij.refactoring.util.TextOccurrencesUtil
@@ -47,9 +49,9 @@ import org.jetbrains.kotlin.idea.core.deleteSingle
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.idea.refactoring.move.*
import org.jetbrains.kotlin.idea.refactoring.move.moveFilesOrDirectories.MoveKotlinClassHandler
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference.ShorteningMode
import org.jetbrains.kotlin.idea.search.projectScope
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
@@ -81,6 +83,8 @@ interface Mover : (KtNamedDeclaration, KtElement) -> KtNamedDeclaration {
}
}
internal var KtSimpleNameExpression.internalUsageInfo: UsageInfo? by CopyableUserDataProperty(Key.create("INTERNAL_USAGE_INFO"))
class MoveDeclarationsDescriptor(
val elementsToMove: Collection<KtNamedDeclaration>,
val moveTarget: KotlinMoveTarget,
@@ -122,12 +126,6 @@ class MoveKotlinDeclarationsProcessor(
fun getConflictsAsUsages(): List<UsageInfo> = conflicts.entrySet().map { ConflictUsageInfo(it.key, it.value) }
class UsageToProcessBeforeMoveWrapper private constructor (element: PsiElement, val originalUsage: UsageInfo): UsageInfo(element) {
companion object {
fun create(originalUsage: UsageInfo) = originalUsage.element?.let { UsageToProcessBeforeMoveWrapper(it, originalUsage) }
}
}
public override fun findUsages(): Array<UsageInfo> {
if (elementsToMove.isEmpty()) return UsageInfo.EMPTY_ARRAY
@@ -194,12 +192,8 @@ class MoveKotlinDeclarationsProcessor(
conflictChecker.checkAllConflicts(externalUsages, internalUsages, conflicts)
descriptor.delegate.collectConflicts(descriptor, internalUsages, conflicts)
usages += internalUsages
usages += externalUsages
val (usagesToProcessLater, usagesToProcessEarly) = internalUsages.partition { it is UnqualifiableMoveRenameUsageInfo }
usages += usagesToProcessLater
usagesToProcessBeforeMove += usagesToProcessEarly
usages += usagesToProcessBeforeMove.mapNotNull { UsageToProcessBeforeMoveWrapper.create(it) }
}
descriptor.delegate.collectConflicts(descriptor, usagesToProcessBeforeMove, conflicts)
@@ -212,32 +206,25 @@ class MoveKotlinDeclarationsProcessor(
}
override fun performRefactoring(usages: Array<out UsageInfo>) {
fun moveDeclaration(declaration: KtNamedDeclaration, moveTarget: KotlinMoveTarget): KtNamedDeclaration? {
val file = declaration.containingFile as? KtFile
assert(file != null) { "${declaration::class.java}: ${declaration.text}" }
fun moveDeclaration(declaration: KtNamedDeclaration, moveTarget: KotlinMoveTarget): KtNamedDeclaration {
val targetContainer = moveTarget.getOrCreateTargetPsi(declaration)
?: throw AssertionError("Couldn't create Kotlin file for: ${declaration::class.java}: ${declaration.text}")
descriptor.delegate.preprocessDeclaration(descriptor, declaration)
val newElement = mover(declaration, targetContainer)
newElement.addToBeShortenedDescendantsToWaitingSet()
return newElement
return mover(declaration, targetContainer).apply {
addToBeShortenedDescendantsToWaitingSet()
}
}
val usageList = usages.toList()
val (oldInternalUsages, externalUsages) = usageList.partition { (it as? InternableUsage)?.scope != null }
val newInternalUsages = ArrayList<UsageInfo>()
oldInternalUsages.forEach { (it.element as? KtSimpleNameExpression)?.internalUsageInfo = it }
val usagesToProcess = ArrayList(externalUsages)
try {
val usageList = usages.toList()
val (usagesToProcessBeforeMoveWrappers, usagesToProcessAfterMove) =
usageList.partition { it is UsageToProcessBeforeMoveWrapper }
val usagesToProcessBeforeMove = usagesToProcessBeforeMoveWrappers.map { (it as UsageToProcessBeforeMoveWrapper).originalUsage }
descriptor.delegate.preprocessUsages(project, descriptor, usagesToProcessBeforeMove)
descriptor.delegate.preprocessUsages(project, descriptor, usagesToProcessAfterMove)
postProcessMoveUsages(usagesToProcessBeforeMove, shorteningMode = ShorteningMode.DELAYED_SHORTENING_VIA_USER_DATA)
descriptor.delegate.preprocessUsages(project, descriptor, usageList)
val oldToNewElementsMapping = THashMap<PsiElement, PsiElement>(
object: TObjectHashingStrategy<PsiElement> {
@@ -259,18 +246,17 @@ class MoveKotlinDeclarationsProcessor(
}
}
)
val newDeclarations = ArrayList<KtNamedDeclaration>()
for ((sourceFile, kotlinToLightElements) in kotlinToLightElementsBySourceFile) {
for ((oldDeclaration, oldLightElements) in kotlinToLightElements) {
val elementListener = transaction!!.getElementListener(oldDeclaration)
val newDeclaration = moveDeclaration(oldDeclaration, descriptor.moveTarget)
if (newDeclaration == null) {
for (oldElement in oldLightElements) {
oldToNewElementsMapping[oldElement] = oldElement
}
continue
}
newDeclarations += newDeclaration
oldToNewElementsMapping[oldDeclaration] = newDeclaration
oldToNewElementsMapping[sourceFile] = newDeclaration.containingKtFile
elementListener.elementMoved(newDeclaration)
@@ -288,12 +274,33 @@ class MoveKotlinDeclarationsProcessor(
}
}
nonCodeUsages = postProcessMoveUsages(usagesToProcessAfterMove, oldToNewElementsMapping).toTypedArray()
newDeclarations.forEach { newDeclaration ->
newInternalUsages += newDeclaration.collectDescendantsOfType<KtSimpleNameExpression>().mapNotNull {
val usageInfo = it.internalUsageInfo
if (usageInfo?.element != null) return@mapNotNull usageInfo
val referencedElement = (usageInfo as? MoveRenameUsageInfo)?.referencedElement ?: return@mapNotNull null
val newReferencedElement = oldToNewElementsMapping[referencedElement] ?: referencedElement
if (!newReferencedElement.isValid) return@mapNotNull null
(usageInfo as? InternableUsage)?.refresh(it, newReferencedElement)
}
}
usagesToProcess += newInternalUsages
nonCodeUsages = postProcessMoveUsages(usagesToProcess, oldToNewElementsMapping).toTypedArray()
}
catch (e: IncorrectOperationException) {
nonCodeUsages = null
RefactoringUIUtil.processIncorrectOperation(myProject, e)
}
finally {
(newInternalUsages + oldInternalUsages).forEach {
val element = it.element as? KtSimpleNameExpression ?: return@forEach
if (element.isValid) {
element.internalUsageInfo = null
}
}
}
}
override fun performPsiSpoilingRefactoring() {
@@ -22,6 +22,7 @@ import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.util.Comparing
import com.intellij.openapi.util.Key
import com.intellij.psi.*
import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference
import com.intellij.refactoring.RefactoringBundle
import com.intellij.refactoring.copy.CopyFilesOrDirectoriesHandler
import com.intellij.refactoring.move.MoveCallback
@@ -123,11 +124,9 @@ fun KtElement.lazilyProcessInternalReferencesToUpdateOnPackageNameChange(
val descriptor = bindingContext[BindingContext.REFERENCE_TARGET, refExpr]?.getImportableDescriptor() ?: return null
val containingDescriptor = descriptor.containingDeclaration ?: return null
val declaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, descriptor) ?: return null
var declaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, descriptor) ?: return null
val callableKind = (descriptor as? CallableMemberDescriptor)?.kind
val containingDeclarationForNonRealMember = if (callableKind != CallableMemberDescriptor.Kind.DECLARATION) {
DescriptorToSourceUtilsIde.getAnyDeclaration(project, containingDescriptor) as? KtClassOrObject
} else null
if (callableKind != null && callableKind != CallableMemberDescriptor.Kind.DECLARATION) return null
// Special case for enum entry superclass references (they have empty text and don't need to be processed by the refactoring)
if (refExpr.textRange.isEmpty) return null
@@ -156,13 +155,13 @@ fun KtElement.lazilyProcessInternalReferencesToUpdateOnPackageNameChange(
}
}
val fqName = DescriptorUtils.getFqName(descriptor).let {
if (DescriptorUtils.isCompanionObject(descriptor)
&& bindingContext[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, refExpr] != null) {
it.parent()
}
else it
if (declaration is KtObjectDeclaration
&& declaration.isCompanion()
&& bindingContext[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, refExpr] != null) {
declaration = declaration.containingClassOrObject ?: declaration
}
val fqName = DescriptorUtils.getFqName(descriptor)
if (!fqName.isSafe) return null
val (oldContainer, newContainer) = containerChangeInfo
@@ -178,30 +177,10 @@ fun KtElement.lazilyProcessInternalReferencesToUpdateOnPackageNameChange(
}
.firstOrNull()
fun doCreateUsageInfo(refExpr: KtSimpleNameExpression): UsageInfo? {
// Check container instead of declaration itself as the latter may be not real (e.g. fake override)
if (isAncestor(containingDeclarationForNonRealMember ?: declaration, false)) {
if (descriptor.importableFqName == null) return null
if (getReferenceKind(refExpr.mainReference, declaration) != ReferenceKind.QUALIFIABLE) return null
if (containerFqName == null || newContainer is ContainerInfo.UnknownPackage) return null
return fqName.asString().let {
val prefix = containerFqName.asString()
val prefixOffset = it.indexOf(prefix)
val newFqName = if (prefix.isEmpty()) {
val newContainerFqName = newContainer.fqName!!
FqName(if (newContainerFqName.isRoot) it else "${newContainer.fqName!!.asString()}.$it")
}
else {
FqName(it.replaceRange(prefixOffset..prefixOffset + prefix.length - 1, newContainer.fqName!!.asString()))
}
MoveRenameSelfUsageInfo(refExpr.mainReference, declaration, newFqName)
}
}
return createMoveUsageInfoIfPossible(refExpr.mainReference, declaration, false, fqName.toSafe())
if (isExtension || containerFqName != null || isImported(descriptor)) return {
createMoveUsageInfoIfPossible(it.mainReference, declaration, false, this@lazilyProcessInternalReferencesToUpdateOnPackageNameChange)
}
if (isExtension || containerFqName != null || isImported(descriptor)) return ::doCreateUsageInfo
return null
}
@@ -216,49 +195,52 @@ fun KtElement.lazilyProcessInternalReferencesToUpdateOnPackageNameChange(
class ImplicitCompanionAsDispatchReceiverUsageInfo(callee: KtSimpleNameExpression) : UsageInfo(callee)
interface InternableUsage {
val scope: PsiElement?
fun refresh(refExpr: KtSimpleNameExpression, referencedElement: PsiElement): UsageInfo?
}
class UnqualifiableMoveRenameUsageInfo(
element: PsiElement,
reference: PsiReference,
startOffset: Int,
endOffset: Int,
referencedElement: PsiElement,
val originalFile: PsiFile,
val addImportToOriginalFile: Boolean
): MoveRenameUsageInfo(element, reference, startOffset, endOffset, referencedElement, false)
class MoveRenameSelfUsageInfo(ref: KtSimpleNameReference, refTarget: PsiElement, val newFqName: FqName):
MoveRenameUsageInfo(ref.element, ref, ref.rangeInElement.startOffset, ref.rangeInElement.endOffset, refTarget, false) {
override fun getReference() = super.getReference() as? KtSimpleNameReference
val addImportToOriginalFile: Boolean,
override val scope: PsiElement?
): MoveRenameUsageInfo(element, reference, reference.rangeInElement.startOffset, reference.rangeInElement.endOffset, referencedElement, false), InternableUsage {
override fun refresh(refExpr: KtSimpleNameExpression, referencedElement: PsiElement): UsageInfo? {
return UnqualifiableMoveRenameUsageInfo(refExpr, refExpr.mainReference, referencedElement, originalFile, addImportToOriginalFile, scope)
}
}
class QualifiableMoveRenameUsageInfo(
element: PsiElement,
reference: PsiReference,
startOffset: Int,
endOffset: Int,
referencedElement: PsiElement,
val newFqName: FqName?
): MoveRenameUsageInfo(element, reference, startOffset, endOffset, referencedElement, false)
override val scope: PsiElement?
): MoveRenameUsageInfo(element, reference, reference.rangeInElement.startOffset, reference.rangeInElement.endOffset, referencedElement, false),
InternableUsage {
override fun refresh(refExpr: KtSimpleNameExpression, referencedElement: PsiElement): UsageInfo? {
return QualifiableMoveRenameUsageInfo(refExpr, refExpr.mainReference, referencedElement, scope)
}
}
fun createMoveUsageInfoIfPossible(
reference: PsiReference,
referencedElement: PsiElement,
addImportToOriginalFile: Boolean,
newFqName: FqName?
scope: PsiElement?
): UsageInfo? {
val element = reference.element
if (element.getStrictParentOfType<KtSuperExpression>() != null) return null
val range = reference.rangeInElement!!
val startOffset = range.startOffset
val endOffset = range.endOffset
return when (getReferenceKind(reference, referencedElement)) {
ReferenceKind.QUALIFIABLE -> QualifiableMoveRenameUsageInfo(
element, reference, startOffset, endOffset, referencedElement, newFqName
element, reference, referencedElement, scope
)
ReferenceKind.UNQUALIFIABLE -> UnqualifiableMoveRenameUsageInfo(
element, reference, startOffset, endOffset, referencedElement, element.containingFile!!, addImportToOriginalFile
element, reference, referencedElement, element.containingFile!!, addImportToOriginalFile, scope
)
else -> null
}
@@ -332,6 +314,48 @@ private fun updateJavaReference(reference: PsiReferenceExpression, oldElement: P
return false
}
private fun mapToNewOrThis(e: PsiElement, oldToNewElementsMapping: Map<PsiElement, PsiElement>) = oldToNewElementsMapping[e] ?: e
private fun postProcessMoveUsage(
usage: UsageInfo,
oldToNewElementsMapping: Map<PsiElement, PsiElement>,
nonCodeUsages: ArrayList<NonCodeUsageInfo>,
shorteningMode: ShorteningMode
) {
when (usage) {
is NonCodeUsageInfo -> {
nonCodeUsages.add(usage)
}
is UnqualifiableMoveRenameUsageInfo -> {
val file = with(usage) { if (addImportToOriginalFile) originalFile else mapToNewOrThis(originalFile, oldToNewElementsMapping) } as KtFile
val declaration = mapToNewOrThis(usage.referencedElement!!, oldToNewElementsMapping)
addDelayedImportRequest(declaration, file)
}
is MoveRenameUsageInfo -> {
val oldElement = usage.referencedElement!!
val newElement = mapToNewOrThis(oldElement, oldToNewElementsMapping)
val reference = usage.reference ?: (usage.element as? KtSimpleNameExpression)?.mainReference
processReference(reference, newElement, shorteningMode, oldElement)
}
}
}
private fun processReference(reference: PsiReference?, newElement: PsiElement, shorteningMode: ShorteningMode, oldElement: PsiElement) {
try {
when {
reference is PsiMultiReference -> reference.references.forEach { processReference(it, newElement, shorteningMode, oldElement) }
reference is KtSimpleNameReference -> reference.bindToElement(newElement, shorteningMode)
reference is PsiReferenceExpression && updateJavaReference(reference, oldElement, newElement) -> return
else -> reference?.bindToElement(newElement)
}
}
catch (e: IncorrectOperationException) {
// Suppress exception if bindToElement is not implemented
}
}
/**
* Perform usage postprocessing and return non-code usages
*/
@@ -339,9 +363,7 @@ fun postProcessMoveUsages(usages: Collection<UsageInfo>,
oldToNewElementsMapping: Map<PsiElement, PsiElement> = Collections.emptyMap(),
shorteningMode: ShorteningMode = ShorteningMode.DELAYED_SHORTENING
): List<NonCodeUsageInfo> {
fun counterpart(e: PsiElement) = oldToNewElementsMapping[e] ?: e
val sortedUsages = usages.sortedWith(
val sortedUsages = usages.sortedWith(
Comparator<UsageInfo> { o1, o2 ->
val file1 = o1.virtualFile
val file2 = o2.virtualFile
@@ -362,45 +384,7 @@ fun postProcessMoveUsages(usages: Collection<UsageInfo>,
val nonCodeUsages = ArrayList<NonCodeUsageInfo>()
usageLoop@ for (usage in sortedUsages) {
when (usage) {
is NonCodeUsageInfo -> {
nonCodeUsages.add(usage)
}
is MoveRenameSelfUsageInfo -> {
usage.reference?.bindToFqName(usage.newFqName, shorteningMode)
}
is UnqualifiableMoveRenameUsageInfo -> {
val file = with(usage) { if (addImportToOriginalFile) originalFile else counterpart(originalFile) } as KtFile
val declaration = counterpart(usage.referencedElement!!)
addDelayedImportRequest(declaration, file)
}
is MoveRenameUsageInfo -> {
val oldElement = usage.referencedElement!!
val newElement = counterpart(oldElement)
val reference = usage.reference ?: (usage.element as? KtSimpleNameExpression)?.mainReference
try {
when {
reference is KtSimpleNameReference -> {
val newFqName = (usage as? QualifiableMoveRenameUsageInfo)?.newFqName
if (newFqName != null) {
reference.bindToFqName(newFqName, shorteningMode)
}
else {
reference.bindToElement(newElement, shorteningMode)
}
}
reference is PsiReferenceExpression && updateJavaReference(reference, oldElement, newElement) -> continue@usageLoop
else -> reference?.bindToElement(newElement)
}
}
catch (e: IncorrectOperationException) {
// Suppress exception if bindToElement is not implemented
}
}
}
postProcessMoveUsage(usage, oldToNewElementsMapping, nonCodeUsages, shorteningMode)
}
return nonCodeUsages
@@ -0,0 +1,3 @@
package pack
class C
@@ -0,0 +1,15 @@
package pack2
import pack.C
class A {
val a = A()
val b = B()
val c = C()
}
class B {
val a = A()
val b = B()
val c = C()
}
@@ -0,0 +1,15 @@
package pack
class <caret>A {
val a = A()
val b = B()
val c = C()
}
class <caret>B {
val a = A()
val b = B()
val c = C()
}
class C
@@ -0,0 +1,5 @@
{
"mainFile": "pack/test.kt",
"type": "MOVE_KOTLIN_TOP_LEVEL_DECLARATIONS",
"targetPackage": "pack2"
}
@@ -534,6 +534,12 @@ public class MoveTestGenerated extends AbstractMoveTest {
doTest(fileName);
}
@TestMetadata("kotlin/moveTopLevelDeclarations/misc/mutualDependency/mutualDependency.test")
public void testKotlin_moveTopLevelDeclarations_misc_mutualDependency_MutualDependency() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/mutualDependency/mutualDependency.test");
doTest(fileName);
}
@TestMetadata("kotlin/moveTopLevelDeclarations/misc/selfReferences/selfReferences.test")
public void testKotlin_moveTopLevelDeclarations_misc_selfReferences_SelfReferences() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/selfReferences/selfReferences.test");