#KT-23099: Fix name clashes when moving top-level objects with same signature

This commit is contained in:
Vadim Brilyantov
2019-02-11 17:25:31 +03:00
parent f7c4760cb9
commit 0c1e7e1d63
2 changed files with 194 additions and 88 deletions
@@ -41,24 +41,29 @@ import org.jetbrains.kotlin.asJava.toLightMethods
import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.MutablePackageFragmentDescriptor import org.jetbrains.kotlin.descriptors.impl.MutablePackageFragmentDescriptor
import org.jetbrains.kotlin.idea.caches.project.getModuleInfoByVirtualFile
import org.jetbrains.kotlin.idea.caches.project.forcedModuleInfo import org.jetbrains.kotlin.idea.caches.project.forcedModuleInfo
import org.jetbrains.kotlin.idea.caches.project.getModuleInfoByVirtualFile
import org.jetbrains.kotlin.idea.caches.project.implementedModules import org.jetbrains.kotlin.idea.caches.project.implementedModules
import org.jetbrains.kotlin.idea.caches.resolve.* import org.jetbrains.kotlin.idea.caches.resolve.*
import org.jetbrains.kotlin.idea.caches.resolve.util.getJavaMemberDescriptor import org.jetbrains.kotlin.idea.caches.resolve.util.getJavaMemberDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.getPackage
import org.jetbrains.kotlin.idea.core.isInTestSourceContentKotlinAware import org.jetbrains.kotlin.idea.core.isInTestSourceContentKotlinAware
import org.jetbrains.kotlin.idea.imports.importableFqName import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.project.forcedTargetPlatform import org.jetbrains.kotlin.idea.project.forcedTargetPlatform
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.idea.refactoring.getUsageContext import org.jetbrains.kotlin.idea.refactoring.getUsageContext
import org.jetbrains.kotlin.idea.refactoring.move.KotlinMoveUsage import org.jetbrains.kotlin.idea.refactoring.move.KotlinMoveUsage
import org.jetbrains.kotlin.idea.search.and import org.jetbrains.kotlin.idea.search.and
import org.jetbrains.kotlin.idea.search.not import org.jetbrains.kotlin.idea.search.not
import org.jetbrains.kotlin.idea.util.projectStructure.getModule import org.jetbrains.kotlin.idea.util.projectStructure.getModule
import org.jetbrains.kotlin.idea.util.projectStructure.module import org.jetbrains.kotlin.idea.util.projectStructure.module
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.load.java.JavaVisibilities import org.jetbrains.kotlin.load.java.JavaVisibilities
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.renderer.ClassifierNamePolicy import org.jetbrains.kotlin.renderer.ClassifierNamePolicy
@@ -74,18 +79,21 @@ import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.lazy.descriptors.findPackageFragmentForFile import org.jetbrains.kotlin.resolve.lazy.descriptors.findPackageFragmentForFile
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
import org.jetbrains.kotlin.resolve.source.getPsi import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.isAnyOrNullableAny
import org.jetbrains.kotlin.util.isJavaDescriptor import org.jetbrains.kotlin.util.isJavaDescriptor
import org.jetbrains.kotlin.util.supertypesWithAny
import org.jetbrains.kotlin.utils.SmartSet import org.jetbrains.kotlin.utils.SmartSet
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.util.* import java.util.*
class MoveConflictChecker( class MoveConflictChecker(
private val project: Project, private val project: Project,
private val elementsToMove: Collection<KtElement>, private val elementsToMove: Collection<KtElement>,
private val moveTarget: KotlinMoveTarget, private val moveTarget: KotlinMoveTarget,
contextElement: KtElement, contextElement: KtElement,
private val doNotGoIn: Collection<KtElement> = emptyList(), private val doNotGoIn: Collection<KtElement> = emptyList(),
allElementsToMove: Collection<PsiElement>? = null allElementsToMove: Collection<PsiElement>? = null
) { ) {
private val resolutionFacade = contextElement.getResolutionFacade() private val resolutionFacade = contextElement.getResolutionFacade()
@@ -101,10 +109,10 @@ class MoveConflictChecker(
} }
private fun getModuleDescriptor(sourceFile: VirtualFile) = private fun getModuleDescriptor(sourceFile: VirtualFile) =
getModuleInfoByVirtualFile( getModuleInfoByVirtualFile(
project, project,
sourceFile sourceFile
)?.let { resolutionFacade.findModuleDescriptor(it) } )?.let { resolutionFacade.findModuleDescriptor(it) }
private fun KotlinMoveTarget.getContainerDescriptor(): DeclarationDescriptor? { private fun KotlinMoveTarget.getContainerDescriptor(): DeclarationDescriptor? {
return when (this) { return when (this) {
@@ -115,9 +123,9 @@ class MoveConflictChecker(
is KtFile -> { is KtFile -> {
val packageFragment = val packageFragment =
targetElement targetElement
.findModuleDescriptor() .findModuleDescriptor()
.findPackageFragmentForFile(targetElement) .findPackageFragmentForFile(targetElement)
packageFragment?.withSource(targetElement) packageFragment?.withSource(targetElement)
} }
@@ -129,7 +137,7 @@ class MoveConflictChecker(
is KotlinDirectoryBasedMoveTarget -> { is KotlinDirectoryBasedMoveTarget -> {
val packageFqName = targetContainerFqName ?: return null val packageFqName = targetContainerFqName ?: return null
val targetModuleDescriptor = targetScope?.let { getModuleDescriptor(it) ?: return null } val targetModuleDescriptor = targetScope?.let { getModuleDescriptor(it) ?: return null }
?: resolutionFacade.moduleDescriptor ?: resolutionFacade.moduleDescriptor
MutablePackageFragmentDescriptor(targetModuleDescriptor, packageFqName).withSource(fakeFile) MutablePackageFragmentDescriptor(targetModuleDescriptor, packageFqName).withSource(fakeFile)
} }
@@ -154,7 +162,7 @@ class MoveConflictChecker(
val wrappedDescriptor = this val wrappedDescriptor = this
return when (wrappedDescriptor) { return when (wrappedDescriptor) {
// We rely on visibility not depending on more specific type of CallableMemberDescriptor // We rely on visibility not depending on more specific type of CallableMemberDescriptor
is CallableMemberDescriptor -> object : CallableMemberDescriptor by wrappedDescriptor { is CallableMemberDescriptor -> object : CallableMemberDescriptor by wrappedDescriptor {
override fun getOriginal() = this override fun getOriginal() = this
override fun getContainingDeclaration() = newContainer ?: wrappedDescriptor.containingDeclaration override fun getContainingDeclaration() = newContainer ?: wrappedDescriptor.containingDeclaration
@@ -162,7 +170,7 @@ class MoveConflictChecker(
override fun getSource() = override fun getSource() =
newContainer?.let { SourceElement { DescriptorUtils.getContainingSourceFile(it) } } ?: wrappedDescriptor.source newContainer?.let { SourceElement { DescriptorUtils.getContainingSourceFile(it) } } ?: wrappedDescriptor.source
} }
is ClassDescriptor -> object: ClassDescriptor by wrappedDescriptor { is ClassDescriptor -> object : ClassDescriptor by wrappedDescriptor {
override fun getOriginal() = this override fun getOriginal() = this
override fun getContainingDeclaration() = newContainer ?: wrappedDescriptor.containingDeclaration override fun getContainingDeclaration() = newContainer ?: wrappedDescriptor.containingDeclaration
override fun getVisibility(): Visibility = newVisibility ?: wrappedDescriptor.visibility override fun getVisibility(): Visibility = newVisibility ?: wrappedDescriptor.visibility
@@ -173,7 +181,10 @@ class MoveConflictChecker(
} }
} }
private fun DeclarationDescriptor.asPredicted(newContainer: DeclarationDescriptor, actualVisibility: Visibility?): DeclarationDescriptor? { private fun DeclarationDescriptor.asPredicted(
newContainer: DeclarationDescriptor,
actualVisibility: Visibility?
): DeclarationDescriptor? {
val visibility = actualVisibility ?: (this as? DeclarationDescriptorWithVisibility)?.visibility ?: return null val visibility = actualVisibility ?: (this as? DeclarationDescriptorWithVisibility)?.visibility ?: return null
val adjustedVisibility = if (visibility == Visibilities.PROTECTED && newContainer is PackageFragmentDescriptor) { val adjustedVisibility = if (visibility == Visibilities.PROTECTED && newContainer is PackageFragmentDescriptor) {
Visibilities.PUBLIC Visibilities.PUBLIC
@@ -197,10 +208,12 @@ class MoveConflictChecker(
private fun render(declaration: PsiElement) = RefactoringUIUtil.getDescription(declaration, false) private fun render(declaration: PsiElement) = RefactoringUIUtil.getDescription(declaration, false)
// Based on RefactoringConflictsUtil.analyzeModuleConflicts // Based on RefactoringConflictsUtil.analyzeModuleConflicts
fun analyzeModuleConflictsInUsages(project: Project, fun analyzeModuleConflictsInUsages(
usages: Collection<UsageInfo>, project: Project,
targetScope: VirtualFile, usages: Collection<UsageInfo>,
conflicts: MultiMap<PsiElement, String>) { targetScope: VirtualFile,
conflicts: MultiMap<PsiElement, String>
) {
val targetModule = targetScope.getModule(project) ?: return val targetModule = targetScope.getModule(project) ?: return
val isInTestSources = ModuleRootManager.getInstance(targetModule).fileIndex.isInTestSourceContentKotlinAware(targetScope) val isInTestSources = ModuleRootManager.getInstance(targetModule).fileIndex.isInTestSourceContentKotlinAware(targetScope)
@@ -216,16 +229,19 @@ class MoveConflictChecker(
val scopeDescription = RefactoringUIUtil.getDescription(element.getUsageContext(), true) val scopeDescription = RefactoringUIUtil.getDescription(element.getUsageContext(), true)
val referencedElement = (if (usage is MoveRenameUsageInfo) usage.referencedElement else usage.element) ?: error(usage) val referencedElement = (if (usage is MoveRenameUsageInfo) usage.referencedElement else usage.element) ?: error(usage)
val message = if (usageModule == targetModule && isInTestSources) { val message = if (usageModule == targetModule && isInTestSources) {
RefactoringBundle.message("0.referenced.in.1.will.not.be.accessible.from.production.of.module.2", RefactoringBundle.message(
RefactoringUIUtil.getDescription(referencedElement, true), "0.referenced.in.1.will.not.be.accessible.from.production.of.module.2",
scopeDescription, RefactoringUIUtil.getDescription(referencedElement, true),
CommonRefactoringUtil.htmlEmphasize(usageModule.name)) scopeDescription,
} CommonRefactoringUtil.htmlEmphasize(usageModule.name)
else { )
RefactoringBundle.message("0.referenced.in.1.will.not.be.accessible.from.module.2", } else {
RefactoringUIUtil.getDescription(referencedElement, true), RefactoringBundle.message(
scopeDescription, "0.referenced.in.1.will.not.be.accessible.from.module.2",
CommonRefactoringUtil.htmlEmphasize(usageModule.name)) RefactoringUIUtil.getDescription(referencedElement, true),
scopeDescription,
CommonRefactoringUtil.htmlEmphasize(usageModule.name)
)
} }
conflicts.putValue(referencedElement, CommonRefactoringUtil.capitalize(message)) conflicts.putValue(referencedElement, CommonRefactoringUtil.capitalize(message))
} }
@@ -240,7 +256,7 @@ class MoveConflictChecker(
val referencedElementsToSkip = newConflicts.keySet().mapNotNullTo(HashSet()) { it.namedUnwrappedElement } val referencedElementsToSkip = newConflicts.keySet().mapNotNullTo(HashSet()) { it.namedUnwrappedElement }
externalUsages.removeIf { externalUsages.removeIf {
it is MoveRenameUsageInfo && it is MoveRenameUsageInfo &&
it.referencedElement?.namedUnwrappedElement?.let { it in referencedElementsToSkip } ?: false it.referencedElement?.namedUnwrappedElement?.let { it in referencedElementsToSkip } ?: false
} }
conflicts.putAllValues(newConflicts) conflicts.putAllValues(newConflicts)
} }
@@ -266,14 +282,14 @@ class MoveConflictChecker(
if (targetPlatform is JvmPlatform) return baseScope if (targetPlatform is JvmPlatform) return baseScope
return ModuleRootManager.getInstance(this) return ModuleRootManager.getInstance(this)
.orderEntries .orderEntries
.filterIsInstance<JdkOrderEntry>() .filterIsInstance<JdkOrderEntry>()
.fold(baseScope as SearchScope) { scope, jdkEntry -> scope and !JdkScope(project, jdkEntry) } .fold(baseScope as SearchScope) { scope, jdkEntry -> scope and !JdkScope(project, jdkEntry) }
} }
fun checkModuleConflictsInDeclarations( fun checkModuleConflictsInDeclarations(
internalUsages: MutableSet<UsageInfo>, internalUsages: MutableSet<UsageInfo>,
conflicts: MultiMap<PsiElement, String> conflicts: MultiMap<PsiElement, String>
) { ) {
val targetScope = moveTarget.targetScope ?: return val targetScope = moveTarget.targetScope ?: return
val targetModule = targetScope.getModule(project) ?: return val targetModule = targetScope.getModule(project) ?: return
@@ -297,7 +313,8 @@ class MoveConflictChecker(
if (importableDescriptor is TypeAliasDescriptor if (importableDescriptor is TypeAliasDescriptor
&& newTargetDescriptors.any { && newTargetDescriptors.any {
it is ClassDescriptor && it.isExpect && it.importableFqName == importableDescriptor.importableFqName it is ClassDescriptor && it.isExpect && it.importableFqName == importableDescriptor.importableFqName
}) return true }
) return true
val renderedImportableTarget = DESCRIPTOR_RENDERER_FOR_COMPARISON.render(importableDescriptor) val renderedImportableTarget = DESCRIPTOR_RENDERER_FOR_COMPARISON.render(importableDescriptor)
val renderedTarget by lazy { DESCRIPTOR_RENDERER_FOR_COMPARISON.render(targetDescriptor) } val renderedTarget by lazy { DESCRIPTOR_RENDERER_FOR_COMPARISON.render(targetDescriptor) }
@@ -313,9 +330,9 @@ class MoveConflictChecker(
is PropertyAccessorDescriptor -> { is PropertyAccessorDescriptor -> {
(it as? PropertyDescriptor) (it as? PropertyDescriptor)
?.let { if (targetDescriptor is PropertyGetterDescriptor) it.getter else it.setter } ?.let { if (targetDescriptor is PropertyGetterDescriptor) it.getter else it.setter }
?.let { listOf(it) } ?.let { listOf(it) }
?: emptyList<DeclarationDescriptor>() ?: emptyList<DeclarationDescriptor>()
} }
else -> emptyList() else -> emptyList()
@@ -331,7 +348,8 @@ class MoveConflictChecker(
declaration.forEachDescendantOfType<KtReferenceExpression> { refExpr -> declaration.forEachDescendantOfType<KtReferenceExpression> { refExpr ->
// NB: for unknown reason, refExpr.resolveToCall() does not work here // NB: for unknown reason, refExpr.resolveToCall() does not work here
val targetDescriptor = refExpr.analyze(BodyResolveMode.PARTIAL)[BindingContext.REFERENCE_TARGET, refExpr] ?: return@forEachDescendantOfType val targetDescriptor =
refExpr.analyze(BodyResolveMode.PARTIAL)[BindingContext.REFERENCE_TARGET, refExpr] ?: return@forEachDescendantOfType
if (KotlinBuiltIns.isBuiltIn(targetDescriptor)) return@forEachDescendantOfType if (KotlinBuiltIns.isBuiltIn(targetDescriptor)) return@forEachDescendantOfType
@@ -348,10 +366,12 @@ class MoveConflictChecker(
val refContainer = refExpr.getStrictParentOfType<KtNamedDeclaration>() ?: return@forEachDescendantOfType val refContainer = refExpr.getStrictParentOfType<KtNamedDeclaration>() ?: return@forEachDescendantOfType
val scopeDescription = RefactoringUIUtil.getDescription(refContainer, true) val scopeDescription = RefactoringUIUtil.getDescription(refContainer, true)
val message = RefactoringBundle.message("0.referenced.in.1.will.not.be.accessible.in.module.2", val message = RefactoringBundle.message(
RefactoringUIUtil.getDescription(target, true), "0.referenced.in.1.will.not.be.accessible.in.module.2",
scopeDescription, RefactoringUIUtil.getDescription(target, true),
CommonRefactoringUtil.htmlEmphasize(targetModule.name)) scopeDescription,
CommonRefactoringUtil.htmlEmphasize(targetModule.name)
)
conflicts.putValue(target, CommonRefactoringUtil.capitalize(message)) conflicts.putValue(target, CommonRefactoringUtil.capitalize(message))
referencesToSkip += refExpr referencesToSkip += refExpr
} }
@@ -373,7 +393,8 @@ class MoveConflictChecker(
if (referencedDescriptor is DeclarationDescriptorWithVisibility if (referencedDescriptor is DeclarationDescriptorWithVisibility
&& referencedDescriptor.visibility == Visibilities.PUBLIC && referencedDescriptor.visibility == Visibilities.PUBLIC
&& moveTarget is KotlinMoveTargetForExistingElement && moveTarget is KotlinMoveTargetForExistingElement
&& moveTarget.targetElement.parentsWithSelf.filterIsInstance<KtClassOrObject>().all { it.isPublic }) continue && moveTarget.targetElement.parentsWithSelf.filterIsInstance<KtClassOrObject>().all { it.isPublic }
) continue
val container = element.getUsageContext() val container = element.getUsageContext()
if (!declarationToContainers.getOrPut(referencedElement) { HashSet<PsiElement>() }.add(container)) continue if (!declarationToContainers.getOrPut(referencedElement) { HashSet<PsiElement>() }.add(container)) continue
@@ -381,10 +402,10 @@ class MoveConflictChecker(
val targetContainer = moveTarget.getContainerDescriptor() ?: continue val targetContainer = moveTarget.getContainerDescriptor() ?: continue
val referencingDescriptor = when (container) { val referencingDescriptor = when (container) {
is KtDeclaration -> container.unsafeResolveToDescriptor() is KtDeclaration -> container.unsafeResolveToDescriptor()
is PsiMember -> container.getJavaMemberDescriptor() is PsiMember -> container.getJavaMemberDescriptor()
else -> null else -> null
} ?: continue } ?: continue
val actualVisibility = if (referencingDescriptor.isJavaDescriptor) referencedDescriptor.visibilityAsViewedFromJava() else null val actualVisibility = if (referencingDescriptor.isJavaDescriptor) referencedDescriptor.visibilityAsViewedFromJava() else null
val originalDescriptorToCheck = referencedDescriptor.wrap(newVisibility = actualVisibility) ?: referencedDescriptor val originalDescriptorToCheck = referencedDescriptor.wrap(newVisibility = actualVisibility) ?: referencedDescriptor
val newDescriptorToCheck = referencedDescriptor.asPredicted(targetContainer, actualVisibility) ?: continue val newDescriptorToCheck = referencedDescriptor.asPredicted(targetContainer, actualVisibility) ?: continue
@@ -448,26 +469,26 @@ class MoveConflictChecker(
for (declaration in elementsToMove - doNotGoIn) { for (declaration in elementsToMove - doNotGoIn) {
declaration.forEachDescendantOfType<KtReferenceExpression> { refExpr -> declaration.forEachDescendantOfType<KtReferenceExpression> { refExpr ->
refExpr.references refExpr.references
.forEach { ref -> .forEach { ref ->
val target = ref.resolve() ?: return@forEach val target = ref.resolve() ?: return@forEach
if (isToBeMoved(target)) return@forEach if (isToBeMoved(target)) return@forEach
val targetDescriptor = when (target) { val targetDescriptor = when (target) {
is KtDeclaration -> target.unsafeResolveToDescriptor() is KtDeclaration -> target.unsafeResolveToDescriptor()
is PsiMember -> target.getJavaMemberDescriptor() is PsiMember -> target.getJavaMemberDescriptor()
else -> null else -> null
} as? DeclarationDescriptorWithVisibility ?: return@forEach } as? DeclarationDescriptorWithVisibility ?: return@forEach
var isVisible = targetDescriptor.isVisibleFrom(ref) var isVisible = targetDescriptor.isVisibleFrom(ref)
if (isVisible && targetDescriptor is ConstructorDescriptor) { if (isVisible && targetDescriptor is ConstructorDescriptor) {
isVisible = targetDescriptor.containingDeclaration.isVisibleFrom(ref) isVisible = targetDescriptor.containingDeclaration.isVisibleFrom(ref)
}
if (!isVisible) {
val message = "${render(declaration)} uses ${render(target)} which will be inaccessible after move"
conflicts.putValue(refExpr, message.capitalize())
}
} }
if (!isVisible) {
val message = "${render(declaration)} uses ${render(target)} which will be inaccessible after move"
conflicts.putValue(refExpr, message.capitalize())
}
}
} }
} }
} }
@@ -511,8 +532,7 @@ class MoveConflictChecker(
if (elementToMove is KtClass && elementToMove.isSealed()) { if (elementToMove is KtClass && elementToMove.isSealed()) {
rootClass = elementToMove rootClass = elementToMove
rootClassDescriptor = rootClass.resolveToDescriptorIfAny() ?: return rootClassDescriptor = rootClass.resolveToDescriptorIfAny() ?: return
} } else {
else {
val classDescriptor = elementToMove.resolveToDescriptorIfAny() ?: return val classDescriptor = elementToMove.resolveToDescriptorIfAny() ?: return
val superClassDescriptor = classDescriptor.getSuperClassNotAny() ?: return val superClassDescriptor = classDescriptor.getSuperClassNotAny() ?: return
if (superClassDescriptor.modality != Modality.SEALED) return if (superClassDescriptor.modality != Modality.SEALED) return
@@ -530,8 +550,7 @@ class MoveConflictChecker(
val message = if (elementToMove == rootClass) { val message = if (elementToMove == rootClass) {
"Sealed class '${rootClass.name}' must be moved with all its subclasses" "Sealed class '${rootClass.name}' must be moved with all its subclasses"
} } else {
else {
val type = ElementDescriptionUtil.getElementDescription(elementToMove, UsageViewTypeLocation.INSTANCE).capitalize() val type = ElementDescriptionUtil.getElementDescription(elementToMove, UsageViewTypeLocation.INSTANCE).capitalize()
"$type '${rootClass.name}' must be moved with sealed parent class and all its subclasses" "$type '${rootClass.name}' must be moved with sealed parent class and all its subclasses"
} }
@@ -539,10 +558,92 @@ class MoveConflictChecker(
} }
} }
private fun checkNameClashes(conflicts: MultiMap<PsiElement, String>) {
fun <T> equivalent(a: T, b: T): Boolean = when (a) {
is DeclarationDescriptor -> when (a) {
is FunctionDescriptor -> b is FunctionDescriptor
&& equivalent(a.name, b.name) && a.valueParameters.zip(b.valueParameters).all { equivalent(it.first, it.second) }
else -> b is DeclarationDescriptor && equivalent(a.name, b.name)
}
is Name -> b is Name && a.asString() == b.asString()
is FqName -> b is Name && a.asString() == b.asString()
is ValueParameterDescriptor -> b is ValueParameterDescriptor
&& equivalent(a.type, b.type)
is KotlinType -> {
if (b !is KotlinType) false
else {
val aSupertypes = a.constructor.supertypesWithAny()
val bSupertypes = b.constructor.supertypesWithAny()
when {
a.isAnyOrNullableAny() && b.isAnyOrNullableAny() -> // a = T(?) | Any(?), b = T(?) | Any(?)
true // => 100% clash
aSupertypes.size == 1 && bSupertypes.size == 1 -> // a = T: T1, b = T: T2
equivalent(aSupertypes.first(), bSupertypes.first()) // equivalent(T1, T2) => clash
a.arguments.size != 0 && b.arguments.size != 0 ->
equivalent( // a = Something<....>, b = SomethingElse<....>
a.constructor.declarationDescriptor?.name, // equivalent(Something, SomethingElse) => clash
b.constructor.declarationDescriptor?.name
)
else -> a == b // common case. a == b => clash
}
}
}
else -> false
}
fun walkDeclarations(
currentScopeDeclaration: DeclarationDescriptor,
declaration: DeclarationDescriptor,
report: (String, String) -> Unit
) {
fun descrText(d: DeclarationDescriptor) = d.findPsi()?.text ?: "unknown"
when (currentScopeDeclaration) {
is PackageFragmentDescriptor -> {
val packageDescriptor = currentScopeDeclaration
.containingDeclaration
.getPackage(currentScopeDeclaration.fqName)
packageDescriptor
.memberScope
.getContributedDescriptors { it == declaration.name }
.filter { equivalent(it, declaration) }
.forEach { report(descrText(it), packageDescriptor.fqName.asString()) }
return
}
is ClassDescriptor -> currentScopeDeclaration
.unsubstitutedMemberScope
.getContributedDescriptors { it == declaration.name }
.filter { equivalent(it, declaration) }
.forEach { report(descrText(it), descrText(currentScopeDeclaration)) }
}
currentScopeDeclaration.containingDeclaration?.let { walkDeclarations(it, declaration, report) }
}
(elementsToMove - doNotGoIn)
.filter {it is PsiNamedElement}
.forEach { declaration ->
val declarationDescriptor =
declaration.analyze().get(BindingContext.DECLARATION_TO_DESCRIPTOR, declaration)
if (declarationDescriptor is DeclarationDescriptor) {
val baseDescriptor = moveTarget.getContainerDescriptor()
if (baseDescriptor != null) {
walkDeclarations(baseDescriptor, declarationDescriptor) { conflictingDecl, scopeName ->
conflicts.putValue(
declaration,
"Following declarations would clash: \"${declaration.getText()}\" (element to move) " +
"and \"\n$conflictingDecl\" declared in scope " +
"$scopeName (move destination)"
)
}
}
}
}
}
fun checkAllConflicts( fun checkAllConflicts(
externalUsages: MutableSet<UsageInfo>, externalUsages: MutableSet<UsageInfo>,
internalUsages: MutableSet<UsageInfo>, internalUsages: MutableSet<UsageInfo>,
conflicts: MultiMap<PsiElement, String> conflicts: MultiMap<PsiElement, String>
) { ) {
checkModuleConflictsInUsages(externalUsages, conflicts) checkModuleConflictsInUsages(externalUsages, conflicts)
checkModuleConflictsInDeclarations(internalUsages, conflicts) checkModuleConflictsInDeclarations(internalUsages, conflicts)
@@ -550,16 +651,17 @@ class MoveConflictChecker(
checkVisibilityInDeclarations(conflicts) checkVisibilityInDeclarations(conflicts)
checkInternalMemberUsages(conflicts) checkInternalMemberUsages(conflicts)
checkSealedClassMove(conflicts) checkSealedClassMove(conflicts)
checkNameClashes(conflicts)
} }
} }
fun analyzeConflictsInFile( fun analyzeConflictsInFile(
file: KtFile, file: KtFile,
usages: Collection<UsageInfo>, usages: Collection<UsageInfo>,
moveTarget: KotlinMoveTarget, moveTarget: KotlinMoveTarget,
allElementsToMove: Collection<PsiElement>, allElementsToMove: Collection<PsiElement>,
conflicts: MultiMap<PsiElement, String>, conflicts: MultiMap<PsiElement, String>,
onUsageUpdate: (List<UsageInfo>) -> Unit onUsageUpdate: (List<UsageInfo>) -> Unit
) { ) {
val elementsToMove = file.declarations val elementsToMove = file.declarations
if (elementsToMove.isEmpty()) return if (elementsToMove.isEmpty()) return
@@ -569,11 +671,11 @@ fun analyzeConflictsInFile(
val externalUsageSet = externalUsages.toMutableSet() val externalUsageSet = externalUsages.toMutableSet()
val conflictChecker = MoveConflictChecker( val conflictChecker = MoveConflictChecker(
file.project, file.project,
elementsToMove, elementsToMove,
moveTarget, moveTarget,
elementsToMove.first(), elementsToMove.first(),
allElementsToMove = allElementsToMove allElementsToMove = allElementsToMove
) )
conflictChecker.checkAllConflicts(externalUsageSet, internalUsageSet, conflicts) conflictChecker.checkAllConflicts(externalUsageSet, internalUsageSet, conflicts)
@@ -560,11 +560,15 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
"File '%s' already exists. Do you want to move selected declarations to this file?", "File '%s' already exists. Do you want to move selected declarations to this file?",
targetFile.getVirtualFile().getPath() targetFile.getVirtualFile().getPath()
); );
int ret = int ret=
Messages.showYesNoDialog(myProject, question, RefactoringBundle.message("move.title"), Messages.showYesNoDialog(myProject, question, RefactoringBundle.message("move.title"),
Messages.getQuestionIcon()); Messages.getQuestionIcon());
if (ret != Messages.YES) return null; if (ret != Messages.YES) return null;
} }
if (targetFile instanceof KtFile) {
return new KotlinMoveTargetForExistingElement((KtFile) targetFile);
}
} }
// All source files must be in the same directory // All source files must be in the same directory