Move: Optimize conflict analysis
- Do not process conflicts twice wgen moving a directory - Check move target before resolving usage context declaration - Skip visibility check for usages referring to public declarations - Skip module conflict check for declarations with unchanged module - Do not report lambdas as usage container (use enclosing declaration)
This commit is contained in:
@@ -149,7 +149,8 @@ fun PsiElement.getUsageContext(): PsiElement {
|
|||||||
this,
|
this,
|
||||||
KtPropertyAccessor::class.java,
|
KtPropertyAccessor::class.java,
|
||||||
KtProperty::class.java,
|
KtProperty::class.java,
|
||||||
KtFunction::class.java,
|
KtNamedFunction::class.java,
|
||||||
|
KtConstructor::class.java,
|
||||||
KtClassOrObject::class.java
|
KtClassOrObject::class.java
|
||||||
) ?: containingFile
|
) ?: containingFile
|
||||||
else -> ConflictsUtil.getContainer(this)
|
else -> ConflictsUtil.getContainer(this)
|
||||||
|
|||||||
+6
-3
@@ -96,7 +96,8 @@ class MoveDeclarationsDescriptor @JvmOverloads constructor(
|
|||||||
val deleteSourceFiles: Boolean = false,
|
val deleteSourceFiles: Boolean = false,
|
||||||
val moveCallback: MoveCallback? = null,
|
val moveCallback: MoveCallback? = null,
|
||||||
val openInEditor: Boolean = false,
|
val openInEditor: Boolean = false,
|
||||||
val allElementsToMove: List<PsiElement>? = null
|
val allElementsToMove: List<PsiElement>? = null,
|
||||||
|
val analyzeConflicts: Boolean = true
|
||||||
)
|
)
|
||||||
|
|
||||||
class ConflictUsageInfo(element: PsiElement, val messages: Collection<String>) : UsageInfo(element)
|
class ConflictUsageInfo(element: PsiElement, val messages: Collection<String>) : UsageInfo(element)
|
||||||
@@ -226,8 +227,10 @@ class MoveKotlinDeclarationsProcessor(
|
|||||||
|
|
||||||
internalUsages += descriptor.delegate.findInternalUsages(descriptor)
|
internalUsages += descriptor.delegate.findInternalUsages(descriptor)
|
||||||
collectUsages(kotlinToLightElements, externalUsages)
|
collectUsages(kotlinToLightElements, externalUsages)
|
||||||
conflictChecker.checkAllConflicts(externalUsages, internalUsages, conflicts)
|
if (descriptor.analyzeConflicts) {
|
||||||
descriptor.delegate.collectConflicts(descriptor, internalUsages, conflicts)
|
conflictChecker.checkAllConflicts(externalUsages, internalUsages, conflicts)
|
||||||
|
descriptor.delegate.collectConflicts(descriptor, internalUsages, conflicts)
|
||||||
|
}
|
||||||
|
|
||||||
usages += internalUsages
|
usages += internalUsages
|
||||||
usages += externalUsages
|
usages += externalUsages
|
||||||
|
|||||||
+18
-12
@@ -48,6 +48,8 @@ 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.module
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.contains
|
import org.jetbrains.kotlin.psi.psiUtil.contains
|
||||||
@@ -164,11 +166,11 @@ class MoveConflictChecker(
|
|||||||
// Based on RefactoringConflictsUtil.analyzeModuleConflicts
|
// Based on RefactoringConflictsUtil.analyzeModuleConflicts
|
||||||
fun analyzeModuleConflictsInUsages(project: Project,
|
fun analyzeModuleConflictsInUsages(project: Project,
|
||||||
usages: Collection<UsageInfo>,
|
usages: Collection<UsageInfo>,
|
||||||
sourceRoot: VirtualFile,
|
targetFile: VirtualFile,
|
||||||
conflicts: MultiMap<PsiElement, String>) {
|
conflicts: MultiMap<PsiElement, String>) {
|
||||||
val targetModule = ModuleUtilCore.findModuleForFile(sourceRoot, project) ?: return
|
val targetModule = targetFile.getModule(project) ?: return
|
||||||
|
|
||||||
val isInTestSources = ModuleRootManager.getInstance(targetModule).fileIndex.isInTestSourceContent(sourceRoot)
|
val isInTestSources = ModuleRootManager.getInstance(targetModule).fileIndex.isInTestSourceContent(targetFile)
|
||||||
NextUsage@ for (usage in usages) {
|
NextUsage@ for (usage in usages) {
|
||||||
val element = usage.element ?: continue
|
val element = usage.element ?: continue
|
||||||
if (PsiTreeUtil.getParentOfType(element, PsiImportStatement::class.java, false) != null) continue
|
if (PsiTreeUtil.getParentOfType(element, PsiImportStatement::class.java, false) != null) continue
|
||||||
@@ -177,9 +179,7 @@ class MoveConflictChecker(
|
|||||||
val resolveScope = element.resolveScope
|
val resolveScope = element.resolveScope
|
||||||
if (resolveScope.isSearchInModuleContent(targetModule, isInTestSources)) continue
|
if (resolveScope.isSearchInModuleContent(targetModule, isInTestSources)) continue
|
||||||
|
|
||||||
val usageFile = element.containingFile
|
val usageModule = element.module ?: continue
|
||||||
val usageVFile = usageFile.virtualFile ?: continue
|
|
||||||
val usageModule = ModuleUtilCore.findModuleForFile(usageVFile, project) ?: continue
|
|
||||||
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) {
|
||||||
@@ -200,9 +200,9 @@ class MoveConflictChecker(
|
|||||||
|
|
||||||
fun checkModuleConflictsInUsages(externalUsages: MutableSet<UsageInfo>, conflicts: MultiMap<PsiElement, String>) {
|
fun checkModuleConflictsInUsages(externalUsages: MutableSet<UsageInfo>, conflicts: MultiMap<PsiElement, String>) {
|
||||||
val newConflicts = MultiMap<PsiElement, String>()
|
val newConflicts = MultiMap<PsiElement, String>()
|
||||||
val sourceRoot = moveTarget.targetFile ?: return
|
val targetFile = moveTarget.targetFile ?: return
|
||||||
|
|
||||||
analyzeModuleConflictsInUsages(project, externalUsages, sourceRoot, newConflicts)
|
analyzeModuleConflictsInUsages(project, externalUsages, targetFile, newConflicts)
|
||||||
if (!newConflicts.isEmpty) {
|
if (!newConflicts.isEmpty) {
|
||||||
val referencedElementsToSkip = newConflicts.keySet().mapNotNullTo(HashSet()) { it.namedUnwrappedElement }
|
val referencedElementsToSkip = newConflicts.keySet().mapNotNullTo(HashSet()) { it.namedUnwrappedElement }
|
||||||
externalUsages.removeIf {
|
externalUsages.removeIf {
|
||||||
@@ -242,8 +242,8 @@ class MoveConflictChecker(
|
|||||||
internalUsages: MutableSet<UsageInfo>,
|
internalUsages: MutableSet<UsageInfo>,
|
||||||
conflicts: MultiMap<PsiElement, String>
|
conflicts: MultiMap<PsiElement, String>
|
||||||
) {
|
) {
|
||||||
val sourceRoot = moveTarget.targetFile ?: return
|
val targetFile = moveTarget.targetFile ?: return
|
||||||
val targetModule = ModuleUtilCore.findModuleForFile(sourceRoot, project) ?: return
|
val targetModule = targetFile.getModule(project) ?: return
|
||||||
val resolveScope = targetModule.getScopeWithPlatformAwareDependencies()
|
val resolveScope = targetModule.getScopeWithPlatformAwareDependencies()
|
||||||
|
|
||||||
fun isInScope(targetElement: PsiElement, targetDescriptor: DeclarationDescriptor): Boolean {
|
fun isInScope(targetElement: PsiElement, targetDescriptor: DeclarationDescriptor): Boolean {
|
||||||
@@ -255,7 +255,7 @@ class MoveConflictChecker(
|
|||||||
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) }
|
||||||
|
|
||||||
val targetModuleInfo = getModuleInfoByVirtualFile(project, sourceRoot)
|
val targetModuleInfo = getModuleInfoByVirtualFile(project, targetFile)
|
||||||
val dummyFile = KtPsiFactory(targetElement.project).createFile("dummy.kt", "").apply {
|
val dummyFile = KtPsiFactory(targetElement.project).createFile("dummy.kt", "").apply {
|
||||||
moduleInfo = targetModuleInfo
|
moduleInfo = targetModuleInfo
|
||||||
targetPlatform = TargetPlatformDetector.getPlatform(targetModule)
|
targetPlatform = TargetPlatformDetector.getPlatform(targetModule)
|
||||||
@@ -288,6 +288,8 @@ class MoveConflictChecker(
|
|||||||
|
|
||||||
val referencesToSkip = HashSet<KtReferenceExpression>()
|
val referencesToSkip = HashSet<KtReferenceExpression>()
|
||||||
for (declaration in elementsToMove - doNotGoIn) {
|
for (declaration in elementsToMove - doNotGoIn) {
|
||||||
|
if (declaration.module == targetModule) continue
|
||||||
|
|
||||||
declaration.forEachDescendantOfType<KtReferenceExpression> { refExpr ->
|
declaration.forEachDescendantOfType<KtReferenceExpression> { refExpr ->
|
||||||
val targetDescriptor = refExpr.analyze(BodyResolveMode.PARTIAL)[BindingContext.REFERENCE_TARGET, refExpr] ?: return@forEachDescendantOfType
|
val targetDescriptor = refExpr.analyze(BodyResolveMode.PARTIAL)[BindingContext.REFERENCE_TARGET, refExpr] ?: return@forEachDescendantOfType
|
||||||
|
|
||||||
@@ -328,15 +330,19 @@ class MoveConflictChecker(
|
|||||||
val referencedElement = usage.referencedElement?.namedUnwrappedElement as? KtNamedDeclaration ?: continue
|
val referencedElement = usage.referencedElement?.namedUnwrappedElement as? KtNamedDeclaration ?: continue
|
||||||
val referencedDescriptor = resolutionFacade.resolveToDescriptor(referencedElement)
|
val referencedDescriptor = resolutionFacade.resolveToDescriptor(referencedElement)
|
||||||
|
|
||||||
|
if (referencedDescriptor is DeclarationDescriptorWithVisibility
|
||||||
|
&& referencedDescriptor.visibility == Visibilities.PUBLIC) 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
|
||||||
|
|
||||||
|
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 targetContainer = moveTarget.getContainerDescriptor() ?: continue
|
|
||||||
val descriptorToCheck = referencedDescriptor.asPredicted(targetContainer) ?: continue
|
val descriptorToCheck = referencedDescriptor.asPredicted(targetContainer) ?: continue
|
||||||
|
|
||||||
if (referencedDescriptor.isVisibleIn(referencingDescriptor) && !descriptorToCheck.isVisibleIn(referencingDescriptor)) {
|
if (referencedDescriptor.isVisibleIn(referencingDescriptor) && !descriptorToCheck.isVisibleIn(referencingDescriptor)) {
|
||||||
|
|||||||
+10
-4
@@ -32,6 +32,8 @@ import org.jetbrains.kotlin.idea.refactoring.invokeOnceOnCommandFinish
|
|||||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.KotlinDirectoryMoveTarget
|
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.KotlinDirectoryMoveTarget
|
||||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.MoveKotlinDeclarationsProcessor
|
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.MoveKotlinDeclarationsProcessor
|
||||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.analyzeConflictsInFile
|
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.analyzeConflictsInFile
|
||||||
|
import org.jetbrains.kotlin.idea.runSynchronouslyWithProgress
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -68,7 +70,7 @@ class KotlinMoveDirectoryWithClassesHelper : MoveDirectoryWithClassesHelper() {
|
|||||||
project: Project) {
|
project: Project) {
|
||||||
filesToMove
|
filesToMove
|
||||||
.filterIsInstance<KtFile>()
|
.filterIsInstance<KtFile>()
|
||||||
.mapTo(result) { FileUsagesWrapper(it, fileHandler.findUsages(it, null, searchInComments, searchInNonJavaFiles), null) }
|
.mapTo(result) { FileUsagesWrapper(it, fileHandler.findUsages(it, null, false), null) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun preprocessUsages(
|
override fun preprocessUsages(
|
||||||
@@ -83,8 +85,12 @@ class KotlinMoveDirectoryWithClassesHelper : MoveDirectoryWithClassesHelper() {
|
|||||||
for ((index, usageInfo) in infos.withIndex()) {
|
for ((index, usageInfo) in infos.withIndex()) {
|
||||||
if (usageInfo !is FileUsagesWrapper) continue
|
if (usageInfo !is FileUsagesWrapper) continue
|
||||||
|
|
||||||
analyzeConflictsInFile(usageInfo.psiFile, usageInfo.usages, moveTarget, files, conflicts) {
|
project.runSynchronouslyWithProgress("Analyzing conflicts in ${usageInfo.psiFile.name}", false) {
|
||||||
infos[index] = usageInfo.copy(usages = it)
|
runReadAction {
|
||||||
|
analyzeConflictsInFile(usageInfo.psiFile, usageInfo.usages, moveTarget, files, conflicts) {
|
||||||
|
infos[index] = usageInfo.copy(usages = it)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,7 +109,7 @@ class KotlinMoveDirectoryWithClassesHelper : MoveDirectoryWithClassesHelper() {
|
|||||||
): Boolean {
|
): Boolean {
|
||||||
if (file !is KtFile) return false
|
if (file !is KtFile) return false
|
||||||
|
|
||||||
val moveDeclarationsProcessor = fileHandler.initMoveProcessor(file, moveDestination)
|
val moveDeclarationsProcessor = fileHandler.initMoveProcessor(file, moveDestination, false)
|
||||||
val moveContextMap = getOrCreateMoveContextMap()
|
val moveContextMap = getOrCreateMoveContextMap()
|
||||||
moveContextMap[file] = MoveContext(moveDestination, moveDeclarationsProcessor)
|
moveContextMap[file] = MoveContext(moveDestination, moveDeclarationsProcessor)
|
||||||
if (moveDeclarationsProcessor != null) {
|
if (moveDeclarationsProcessor != null) {
|
||||||
|
|||||||
+13
-4
@@ -73,7 +73,7 @@ class MoveKotlinFileHandler : MoveFileHandler() {
|
|||||||
return ContainerChangeInfo(ContainerInfo.Package(oldPackageName), ContainerInfo.Package(newPackageName.toSafe()))
|
return ContainerChangeInfo(ContainerInfo.Package(oldPackageName), ContainerInfo.Package(newPackageName.toSafe()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun initMoveProcessor(psiFile: PsiFile, newParent: PsiDirectory?): MoveKotlinDeclarationsProcessor? {
|
fun initMoveProcessor(psiFile: PsiFile, newParent: PsiDirectory?, withConflicts: Boolean): MoveKotlinDeclarationsProcessor? {
|
||||||
if (psiFile !is KtFile) return null
|
if (psiFile !is KtFile) return null
|
||||||
val packageNameInfo = psiFile.getPackageNameInfo(newParent, false) ?: return null
|
val packageNameInfo = psiFile.getPackageNameInfo(newParent, false) ?: return null
|
||||||
|
|
||||||
@@ -98,7 +98,8 @@ class MoveKotlinFileHandler : MoveFileHandler() {
|
|||||||
moveTarget = moveTarget,
|
moveTarget = moveTarget,
|
||||||
delegate = MoveDeclarationsDelegate.TopLevel,
|
delegate = MoveDeclarationsDelegate.TopLevel,
|
||||||
scanEntireFile = true,
|
scanEntireFile = true,
|
||||||
allElementsToMove = psiFile.allElementsToMove
|
allElementsToMove = psiFile.allElementsToMove,
|
||||||
|
analyzeConflicts = withConflicts
|
||||||
),
|
),
|
||||||
Mover.Idle
|
Mover.Idle
|
||||||
)
|
)
|
||||||
@@ -114,11 +115,19 @@ class MoveKotlinFileHandler : MoveFileHandler() {
|
|||||||
newParent: PsiDirectory?,
|
newParent: PsiDirectory?,
|
||||||
searchInComments: Boolean,
|
searchInComments: Boolean,
|
||||||
searchInNonJavaFiles: Boolean
|
searchInNonJavaFiles: Boolean
|
||||||
|
): List<UsageInfo> {
|
||||||
|
return findUsages(psiFile, newParent, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findUsages(
|
||||||
|
psiFile: PsiFile,
|
||||||
|
newParent: PsiDirectory?,
|
||||||
|
withConflicts: Boolean
|
||||||
): List<UsageInfo> {
|
): List<UsageInfo> {
|
||||||
if (psiFile !is KtFile) return emptyList()
|
if (psiFile !is KtFile) return emptyList()
|
||||||
|
|
||||||
val usages = arrayListOf<UsageInfo>(FileInfo(psiFile))
|
val usages = arrayListOf<UsageInfo>(FileInfo(psiFile))
|
||||||
initMoveProcessor(psiFile, newParent)?.let {
|
initMoveProcessor(psiFile, newParent, withConflicts)?.let {
|
||||||
usages += it.findUsages()
|
usages += it.findUsages()
|
||||||
usages += it.getConflictsAsUsages()
|
usages += it.getConflictsAsUsages()
|
||||||
}
|
}
|
||||||
@@ -127,7 +136,7 @@ class MoveKotlinFileHandler : MoveFileHandler() {
|
|||||||
|
|
||||||
override fun prepareMovedFile(file: PsiFile, moveDestination: PsiDirectory, oldToNewMap: MutableMap<PsiElement, PsiElement>) {
|
override fun prepareMovedFile(file: PsiFile, moveDestination: PsiDirectory, oldToNewMap: MutableMap<PsiElement, PsiElement>) {
|
||||||
if (file !is KtFile) return
|
if (file !is KtFile) return
|
||||||
val moveProcessor = initMoveProcessor(file, moveDestination) ?: return
|
val moveProcessor = initMoveProcessor(file, moveDestination, false) ?: return
|
||||||
val moveContext = MoveContext(file, moveProcessor)
|
val moveContext = MoveContext(file, moveProcessor)
|
||||||
oldToNewMap[moveContext] = moveContext
|
oldToNewMap[moveContext] = moveContext
|
||||||
val packageNameInfo = file.getPackageNameInfo(moveDestination, true) ?: return
|
val packageNameInfo = file.getPackageNameInfo(moveDestination, true) ?: return
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
// SHOULD_FAIL_WITH: 'foo' in class B will require class instance, 'foo' in function test() will require class instance, 'foo' in function test() will require class instance, 'foo' in lambda <anonymous>() will require class instance
|
// SHOULD_FAIL_WITH: 'foo' in class B will require class instance, 'foo' in function test() will require class instance, 'foo' in function test() will require class instance, 'foo' in function test() will require class instance
|
||||||
class A {
|
class A {
|
||||||
companion object {
|
companion object {
|
||||||
class B {
|
class B {
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
// SHOULD_FAIL_WITH: 'foo' in class B will require class instance, 'foo' in function test() will require class instance, 'foo' in function test() will require class instance, 'foo' in lambda <anonymous>() will require class instance
|
// SHOULD_FAIL_WITH: 'foo' in class B will require class instance, 'foo' in function test() will require class instance, 'foo' in function test() will require class instance, 'foo' in function test() will require class instance
|
||||||
class A {
|
class A {
|
||||||
companion object {
|
companion object {
|
||||||
class B {
|
class B {
|
||||||
|
|||||||
Reference in New Issue
Block a user