Resolve references in background thread in PlainTextPasteImportResolver

#KT-16774 Fixed
This commit is contained in:
Vladimir Dolzhenko
2019-11-21 14:32:27 +01:00
parent bdb1c2a27f
commit 6a28d1da82
@@ -38,7 +38,6 @@ import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtImportDirective import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.psiUtil.referenceExpression import org.jetbrains.kotlin.psi.psiUtil.referenceExpression
import java.util.*
class PlainTextPasteImportResolver(private val dataForConversion: DataForConversion, val targetFile: KtFile) { class PlainTextPasteImportResolver(private val dataForConversion: DataForConversion, val targetFile: KtFile) {
private val file = dataForConversion.file private val file = dataForConversion.file
@@ -57,7 +56,7 @@ class PlainTextPasteImportResolver(private val dataForConversion: DataForConvers
private var ambiguityInResolution = false private var ambiguityInResolution = false
private var couldNotResolve = false private var couldNotResolve = false
val addedImports = ArrayList<PsiImportStatementBase>() val addedImports = mutableListOf<PsiImportStatementBase>()
private fun canBeImported(descriptor: DeclarationDescriptorWithVisibility?): Boolean { private fun canBeImported(descriptor: DeclarationDescriptorWithVisibility?): Boolean {
return descriptor != null return descriptor != null
@@ -65,17 +64,17 @@ class PlainTextPasteImportResolver(private val dataForConversion: DataForConvers
&& descriptor.isVisible(targetFile, null, bindingContext, resolutionFacade) && descriptor.isVisible(targetFile, null, bindingContext, resolutionFacade)
} }
private fun addImport(importStatement: PsiImportStatementBase, shouldAddToTarget: Boolean = false) { private fun addImport(addImportData: AddImportData) {
importList.add(importStatement) importList.add(addImportData.importStatement)
if (shouldAddToTarget) if (addImportData.shouldAddToTarget)
addedImports.add(importStatement) addedImports.add(addImportData.importStatement)
} }
fun addImportsFromTargetFile() { fun addImportsFromTargetFile() {
if (importList in dataForConversion.elementsAndTexts.toList()) return if (importList in dataForConversion.elementsAndTexts.toList()) return
val task = { val task = {
val addImportList = mutableListOf<PsiImportStatementBase>() val addImportList = mutableListOf<AddImportData>()
fun tryConvertKotlinImport(importDirective: KtImportDirective) { fun tryConvertKotlinImport(importDirective: KtImportDirective) {
val importPath = importDirective.importPath val importPath = importDirective.importPath
@@ -100,31 +99,26 @@ class PlainTextPasteImportResolver(private val dataForConversion: DataForConvers
when { when {
isClassReceiver -> isClassReceiver ->
addImportList.add( addImportList.add(
psiElementFactory.createImportStaticStatement( AddImportData(psiElementFactory.createImportStaticStatement(receiver as PsiClass, "*"))
receiver as PsiClass,
"*"
)
) )
isPackageReceiver -> isPackageReceiver ->
addImportList.add( addImportList.add(
psiElementFactory.createImportStatementOnDemand( AddImportData(psiElementFactory.createImportStatementOnDemand((receiver as PsiPackage).qualifiedName))
(receiver as PsiPackage).qualifiedName
)
) )
} }
} else { } else {
when { when {
isClassSelector -> isClassSelector ->
addImportList.add( addImportList.add(
psiElementFactory.createImportStatement( AddImportData(psiElementFactory.createImportStatement(selector as PsiClass))
selector as PsiClass
)
) )
isClassReceiver -> isClassReceiver ->
addImportList.add( addImportList.add(
psiElementFactory.createImportStaticStatement( AddImportData(
receiver as PsiClass, psiElementFactory.createImportStaticStatement(
importPath.importedName!!.asString() receiver as PsiClass,
importPath.importedName!!.asString()
)
) )
) )
} }
@@ -150,76 +144,101 @@ class PlainTextPasteImportResolver(private val dataForConversion: DataForConvers
) )
} }
private data class AddImportData(val importStatement: PsiImportStatementBase, val shouldAddToTarget: Boolean = false)
fun tryResolveReferences() { fun tryResolveReferences() {
val elementsWithUnresolvedRef = PsiTreeUtil.collectElements(file) { element -> val task = {
element.reference != null val addImportList = mutableListOf<AddImportData>()
&& element.reference is PsiQualifiedReference fun tryResolveReference(reference: PsiQualifiedReference): Boolean {
&& element.reference?.resolve() == null if (reference.resolve() != null) return true
} val referenceName = reference.referenceName ?: return false
if (referenceName in failedToResolveReferenceNames) return false
if (reference.qualifier != null) return false
val classes = shortNameCache.getClassesByName(referenceName, scope)
.mapNotNull { psiClass ->
val containingFile = psiClass.containingFile
if (ProjectRootsUtil.isInProjectOrLibraryContent(containingFile)) {
psiClass to psiClass.getJavaMemberDescriptor() as? ClassDescriptor
} else null
}.filter { canBeImported(it.second) }
fun tryResolveReference(reference: PsiQualifiedReference): Boolean { classes.find { (_, descriptor) ->
if (reference.resolve() != null) return true JavaToKotlinClassMap.mapPlatformClass(descriptor!!).isNotEmpty()
val referenceName = reference.referenceName ?: return false }?.let { (psiClass, _) ->
if (referenceName in failedToResolveReferenceNames) return false addImportList.add(AddImportData(psiElementFactory.createImportStatement(psiClass)))
if (reference.qualifier != null) return false }
val classes = shortNameCache.getClassesByName(referenceName, scope) if (reference.resolve() != null) return true
.mapNotNull { psiClass ->
val containingFile = psiClass.containingFile
if (ProjectRootsUtil.isInProjectOrLibraryContent(containingFile)) {
psiClass to psiClass.getJavaMemberDescriptor() as? ClassDescriptor
} else null
}.filter { canBeImported(it.second) }
classes.find { (_, descriptor) -> classes.singleOrNull()?.let { (psiClass, _) ->
JavaToKotlinClassMap.mapPlatformClass(descriptor!!).isNotEmpty() addImportList.add(AddImportData(psiElementFactory.createImportStatement(psiClass), true))
}?.let { (psiClass, _) -> }
addImport(psiElementFactory.createImportStatement(psiClass))
}
if (reference.resolve() != null) return true
classes.singleOrNull()?.let { (psiClass, _) -> when {
addImport(psiElementFactory.createImportStatement(psiClass), true) reference.resolve() != null -> return true
classes.isNotEmpty() -> {
ambiguityInResolution = true
return false
}
}
val members = (shortNameCache.getMethodsByName(referenceName, scope).asList() +
shortNameCache.getFieldsByName(referenceName, scope).asList())
.asSequence()
.map { it as PsiMember }
.filter { it.getNullableModuleInfo() != null }
.map { it to it.getJavaMemberDescriptor(resolutionFacade) as? DeclarationDescriptorWithVisibility }
.filter { canBeImported(it.second) }
.toList()
members.singleOrNull()?.let { (psiMember, _) ->
addImportList.add(
AddImportData(
psiElementFactory.createImportStaticStatement(psiMember.containingClass!!, psiMember.name!!),
true
)
)
}
when {
reference.resolve() != null -> return false
members.isNotEmpty() -> ambiguityInResolution = true
else -> couldNotResolve = true
}
return false
} }
when {
reference.resolve() != null -> return true val elementsWithUnresolvedRef = runReadAction {
classes.isNotEmpty() -> { PsiTreeUtil.collectElements(file) { element ->
ambiguityInResolution = true element.reference != null
return false && element.reference is PsiQualifiedReference
&& element.reference?.resolve() == null
} }
} }
val members = (shortNameCache.getMethodsByName(referenceName, scope).asList() + val reversed = elementsWithUnresolvedRef.reversedArray()
shortNameCache.getFieldsByName(referenceName, scope).asList()) reversed.forEachIndexed { index, value ->
.asSequence() ProgressManager.getInstance().progressIndicator?.fraction = 1.0 * index / reversed.size
.map { it as PsiMember } val reference = value.reference as PsiQualifiedReference
.filter { it.getNullableModuleInfo() != null } addImportList.clear()
.map { it to it.getJavaMemberDescriptor(resolutionFacade) as? DeclarationDescriptorWithVisibility } val tryResolveReference = runReadAction { tryResolveReference(reference) }
.filter { canBeImported(it.second) } if (!tryResolveReference) {
.toList() ApplicationManager.getApplication().invokeAndWait {
runWriteAction {
members.singleOrNull()?.let { (psiMember, _) -> addImportList.forEach { addImport(it) }
addImport(psiElementFactory.createImportStaticStatement(psiMember.containingClass!!, psiMember.name!!), true) val referenceName = reference.referenceName
} if (referenceName != null) {
failedToResolveReferenceNames += referenceName
when { }
reference.resolve() != null -> return false }
members.isNotEmpty() -> ambiguityInResolution = true
else -> couldNotResolve = true
}
return false
}
runWriteAction {
elementsWithUnresolvedRef.reversed().forEach {
val reference = it.reference as PsiQualifiedReference
if (!tryResolveReference(reference)) {
val referenceName = reference.referenceName
if (referenceName != null) {
failedToResolveReferenceNames += referenceName
} }
} }
} }
} }
ProgressManager.getInstance().runProcessWithProgressSynchronously(
task, "Resolving references ...", true, project
)
} }
} }