FIR IDE: Decouple ElementsToShortenCollector from KtFirReferenceShortener
This commit is contained in:
+271
-261
@@ -11,6 +11,7 @@ import com.intellij.psi.SmartPsiElementPointer
|
|||||||
import com.intellij.util.containers.addIfNotNull
|
import com.intellij.util.containers.addIfNotNull
|
||||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||||
import org.jetbrains.kotlin.fir.FirElement
|
import org.jetbrains.kotlin.fir.FirElement
|
||||||
|
import org.jetbrains.kotlin.fir.FirSession
|
||||||
import org.jetbrains.kotlin.fir.ROOT_PREFIX_FOR_IDE_RESOLUTION_MODE
|
import org.jetbrains.kotlin.fir.ROOT_PREFIX_FOR_IDE_RESOLUTION_MODE
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClass
|
import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClass
|
||||||
import org.jetbrains.kotlin.fir.declarations.*
|
import org.jetbrains.kotlin.fir.declarations.*
|
||||||
@@ -37,6 +38,7 @@ import org.jetbrains.kotlin.fir.symbols.CallableId
|
|||||||
import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag
|
import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.*
|
import org.jetbrains.kotlin.fir.symbols.impl.*
|
||||||
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
|
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
|
||||||
|
import org.jetbrains.kotlin.fir.types.FirTypeRef
|
||||||
import org.jetbrains.kotlin.fir.types.classId
|
import org.jetbrains.kotlin.fir.types.classId
|
||||||
import org.jetbrains.kotlin.fir.types.lowerBoundIfFlexible
|
import org.jetbrains.kotlin.fir.types.lowerBoundIfFlexible
|
||||||
import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid
|
import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid
|
||||||
@@ -60,11 +62,13 @@ internal class KtFirReferenceShortener(
|
|||||||
override val token: ValidityToken,
|
override val token: ValidityToken,
|
||||||
override val firResolveState: FirModuleResolveState,
|
override val firResolveState: FirModuleResolveState,
|
||||||
) : KtReferenceShortener(), KtFirAnalysisSessionComponent {
|
) : KtReferenceShortener(), KtFirAnalysisSessionComponent {
|
||||||
|
private val context = FirShorteningContext(firResolveState)
|
||||||
|
|
||||||
override fun collectShortenings(file: KtFile, selection: TextRange): ShortenCommand {
|
override fun collectShortenings(file: KtFile, selection: TextRange): ShortenCommand {
|
||||||
resolveFileToBodyResolve(file)
|
resolveFileToBodyResolve(file)
|
||||||
val firFile = file.getOrBuildFirOfType<FirFile>(firResolveState)
|
val firFile = file.getOrBuildFirOfType<FirFile>(firResolveState)
|
||||||
|
|
||||||
val collector = ElementsToShortenCollector()
|
val collector = ElementsToShortenCollector(context)
|
||||||
firFile.acceptChildren(collector)
|
firFile.acceptChildren(collector)
|
||||||
|
|
||||||
return ShortenCommandImpl(
|
return ShortenCommandImpl(
|
||||||
@@ -75,9 +79,21 @@ internal class KtFirReferenceShortener(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class AvailableClassifier(val classId: ClassId, val isFromStarOrPackageImport: Boolean)
|
private fun resolveFileToBodyResolve(file: KtFile) {
|
||||||
|
for (declaration in file.declarations) {
|
||||||
|
declaration.getOrBuildFir(firResolveState) // temporary hack, resolves declaration to BODY_RESOLVE stage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun findFirstClassifierInScopesByName(positionScopes: List<FirScope>, targetClassName: Name): AvailableClassifier? {
|
private data class AvailableClassifier(val classId: ClassId, val isFromStarOrPackageImport: Boolean)
|
||||||
|
|
||||||
|
private class FirShorteningContext(val firResolveState: FirModuleResolveState) {
|
||||||
|
|
||||||
|
private val firSession: FirSession
|
||||||
|
get() = firResolveState.rootModuleSession
|
||||||
|
|
||||||
|
fun findFirstClassifierInScopesByName(positionScopes: List<FirScope>, targetClassName: Name): AvailableClassifier? {
|
||||||
for (scope in positionScopes) {
|
for (scope in positionScopes) {
|
||||||
val classifierSymbol = scope.findFirstClassifierByName(targetClassName) ?: continue
|
val classifierSymbol = scope.findFirstClassifierByName(targetClassName) ?: continue
|
||||||
val classifierLookupTag = classifierSymbol.toLookupTag() as? ConeClassLikeLookupTag ?: continue
|
val classifierLookupTag = classifierSymbol.toLookupTag() as? ConeClassLikeLookupTag ?: continue
|
||||||
@@ -91,20 +107,14 @@ internal class KtFirReferenceShortener(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findFunctionsInScopes(scopes: List<FirScope>, name: Name): List<FirNamedFunctionSymbol> {
|
fun findFunctionsInScopes(scopes: List<FirScope>, name: Name): List<FirNamedFunctionSymbol> {
|
||||||
return scopes.flatMap { it.getFunctions(name) }
|
return scopes.flatMap { it.getFunctions(name) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findSinglePropertyInScopesByName(scopes: List<FirScope>, name: Name): FirVariableSymbol<*>? {
|
fun findSinglePropertyInScopesByName(scopes: List<FirScope>, name: Name): FirVariableSymbol<*>? {
|
||||||
return scopes.asSequence().mapNotNull { it.getSinglePropertyByName(name) }.singleOrNull()
|
return scopes.asSequence().mapNotNull { it.getSinglePropertyByName(name) }.singleOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveFileToBodyResolve(file: KtFile) {
|
|
||||||
for (declaration in file.declarations) {
|
|
||||||
declaration.getOrBuildFir(firResolveState) // temporary hack, resolves declaration to BODY_RESOLVE stage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun FirScope.findFirstClassifierByName(name: Name): FirClassifierSymbol<*>? {
|
private fun FirScope.findFirstClassifierByName(name: Name): FirClassifierSymbol<*>? {
|
||||||
var element: FirClassifierSymbol<*>? = null
|
var element: FirClassifierSymbol<*>? = null
|
||||||
|
|
||||||
@@ -117,16 +127,12 @@ internal class KtFirReferenceShortener(
|
|||||||
return element
|
return element
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
|
||||||
private fun FirScope.getSingleFunctionByName(name: Name): FirNamedFunctionSymbol? =
|
|
||||||
buildList { processFunctionsByName(name, this::add) }.singleOrNull()
|
|
||||||
|
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
private fun FirScope.getSinglePropertyByName(name: Name): FirVariableSymbol<*>? =
|
private fun FirScope.getSinglePropertyByName(name: Name): FirVariableSymbol<*>? =
|
||||||
buildList { processPropertiesByName(name, this::add) }.singleOrNull()
|
buildList { processPropertiesByName(name, this::add) }.singleOrNull()
|
||||||
|
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
private fun findScopesAtPosition(position: KtElement, newImports: List<FqName>): List<FirScope>? {
|
fun findScopesAtPosition(position: KtElement, newImports: List<FqName>): List<FirScope>? {
|
||||||
val towerDataContext = firResolveState.getTowerDataContextForElement(position) ?: return null
|
val towerDataContext = firResolveState.getTowerDataContextForElement(position) ?: return null
|
||||||
|
|
||||||
val result = buildList<FirScope> {
|
val result = buildList<FirScope> {
|
||||||
@@ -142,11 +148,11 @@ internal class KtFirReferenceShortener(
|
|||||||
val resolvedNewImports = newImports.mapNotNull { createFakeResolvedImport(it) }
|
val resolvedNewImports = newImports.mapNotNull { createFakeResolvedImport(it) }
|
||||||
if (resolvedNewImports.isEmpty()) return null
|
if (resolvedNewImports.isEmpty()) return null
|
||||||
|
|
||||||
return FirExplicitSimpleImportingScope(resolvedNewImports, firResolveState.rootModuleSession, ScopeSession())
|
return FirExplicitSimpleImportingScope(resolvedNewImports, firSession, ScopeSession())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createFakeResolvedImport(fqNameToImport: FqName): FirResolvedImport? {
|
private fun createFakeResolvedImport(fqNameToImport: FqName): FirResolvedImport? {
|
||||||
val packageOrClass = resolveToPackageOrClass(firResolveState.rootModuleSession.firSymbolProvider, fqNameToImport) ?: return null
|
val packageOrClass = resolveToPackageOrClass(firSession.firSymbolProvider, fqNameToImport) ?: return null
|
||||||
|
|
||||||
val delegateImport = buildImport {
|
val delegateImport = buildImport {
|
||||||
importedFqName = fqNameToImport
|
importedFqName = fqNameToImport
|
||||||
@@ -159,252 +165,256 @@ internal class KtFirReferenceShortener(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class ElementsToShortenCollector : FirVisitorVoid() {
|
fun getRegularClass(typeRef: FirTypeRef): FirRegularClass? {
|
||||||
val namesToImport: MutableList<FqName> = mutableListOf()
|
return typeRef.toRegularClass(firSession)
|
||||||
val typesToShorten: MutableList<KtUserType> = mutableListOf()
|
|
||||||
val qualifiersToShorten: MutableList<KtDotQualifiedExpression> = mutableListOf()
|
|
||||||
|
|
||||||
override fun visitElement(element: FirElement) {
|
|
||||||
element.acceptChildren(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitResolvedTypeRef(resolvedTypeRef: FirResolvedTypeRef) {
|
|
||||||
processTypeRef(resolvedTypeRef)
|
|
||||||
|
|
||||||
resolvedTypeRef.acceptChildren(this)
|
|
||||||
resolvedTypeRef.delegatedTypeRef?.accept(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitResolvedQualifier(resolvedQualifier: FirResolvedQualifier) {
|
|
||||||
super.visitResolvedQualifier(resolvedQualifier)
|
|
||||||
|
|
||||||
processTypeQualifier(resolvedQualifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitResolvedNamedReference(resolvedNamedReference: FirResolvedNamedReference) {
|
|
||||||
super.visitResolvedNamedReference(resolvedNamedReference)
|
|
||||||
|
|
||||||
processPropertyReference(resolvedNamedReference)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitFunctionCall(functionCall: FirFunctionCall) {
|
|
||||||
super.visitFunctionCall(functionCall)
|
|
||||||
|
|
||||||
processFunctionCall(functionCall)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun processTypeRef(resolvedTypeRef: FirResolvedTypeRef) {
|
|
||||||
val wholeTypeReference = resolvedTypeRef.psi as? KtTypeReference ?: return
|
|
||||||
|
|
||||||
val wholeClassifierId = resolvedTypeRef.type.lowerBoundIfFlexible().classId ?: return
|
|
||||||
val wholeTypeElement = wholeTypeReference.typeElement.unwrapNullable() as? KtUserType ?: return
|
|
||||||
|
|
||||||
if (wholeTypeElement.qualifier == null) return
|
|
||||||
|
|
||||||
collectTypeIfNeedsToBeShortened(wholeClassifierId, wholeTypeElement)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun collectTypeIfNeedsToBeShortened(wholeClassifierId: ClassId, wholeTypeElement: KtUserType) {
|
|
||||||
val allClassIds = wholeClassifierId.outerClassesWithSelf
|
|
||||||
val allTypeElements = wholeTypeElement.qualifiersWithSelf
|
|
||||||
|
|
||||||
val positionScopes = findScopesAtPosition(wholeTypeElement, namesToImport) ?: return
|
|
||||||
|
|
||||||
for ((classId, typeElement) in allClassIds.zip(allTypeElements)) {
|
|
||||||
// if qualifier is null, then this type have no package and thus cannot be shortened
|
|
||||||
if (typeElement.qualifier == null) return
|
|
||||||
|
|
||||||
val firstFoundClass = findFirstClassifierInScopesByName(positionScopes, classId.shortClassName)?.classId
|
|
||||||
|
|
||||||
if (firstFoundClass == classId) {
|
|
||||||
addTypeToShorten(typeElement)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// none class matched
|
|
||||||
val (mostTopLevelClassId, mostTopLevelTypeElement) = allClassIds.zip(allTypeElements).last()
|
|
||||||
val availableClassifier = findFirstClassifierInScopesByName(positionScopes, mostTopLevelClassId.shortClassName)
|
|
||||||
|
|
||||||
check(availableClassifier?.classId != mostTopLevelClassId) {
|
|
||||||
"If this condition were true, we would have exited from the loop on the last iteration. ClassId = $mostTopLevelClassId"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (availableClassifier == null || availableClassifier.isFromStarOrPackageImport) {
|
|
||||||
addTypeToImportAndShorten(mostTopLevelClassId.asSingleFqName(), mostTopLevelTypeElement)
|
|
||||||
} else {
|
|
||||||
addFakePackagePrefixToShortenIfPresent(mostTopLevelTypeElement)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addFakePackagePrefixToShortenIfPresent(typeElement: KtUserType) {
|
|
||||||
val deepestTypeWithQualifier = typeElement.qualifiersWithSelf.last().parent as? KtUserType
|
|
||||||
?: error("Type element should have at least one qualifier, instead it was ${typeElement.text}")
|
|
||||||
|
|
||||||
if (deepestTypeWithQualifier.hasFakeRootPrefix()) {
|
|
||||||
addTypeToShorten(deepestTypeWithQualifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addTypeToShorten(typeElement: KtUserType) {
|
|
||||||
typesToShorten.add(typeElement)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addTypeToImportAndShorten(classFqName: FqName, mostTopLevelTypeElement: KtUserType) {
|
|
||||||
namesToImport.add(classFqName)
|
|
||||||
typesToShorten.add(mostTopLevelTypeElement)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun processTypeQualifier(resolvedQualifier: FirResolvedQualifier) {
|
|
||||||
val wholeClassQualifier = resolvedQualifier.classId ?: return
|
|
||||||
val wholeQualifierElement = when (val qualifierPsi = resolvedQualifier.psi) {
|
|
||||||
is KtDotQualifiedExpression -> qualifierPsi
|
|
||||||
is KtNameReferenceExpression -> qualifierPsi.getDotQualifiedExpressionForSelector() ?: return
|
|
||||||
else -> return
|
|
||||||
}
|
|
||||||
|
|
||||||
collectQualifierIfNeedsToBeShortened(wholeClassQualifier, wholeQualifierElement)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun collectQualifierIfNeedsToBeShortened(wholeClassQualifier: ClassId, wholeQualifierElement: KtDotQualifiedExpression) {
|
|
||||||
val positionScopes = findScopesAtPosition(wholeQualifierElement, namesToImport) ?: return
|
|
||||||
|
|
||||||
val allClassIds = wholeClassQualifier.outerClassesWithSelf
|
|
||||||
val allQualifiers = wholeQualifierElement.qualifiersWithSelf
|
|
||||||
|
|
||||||
for ((classId, qualifier) in allClassIds.zip(allQualifiers)) {
|
|
||||||
val firstFoundClass = findFirstClassifierInScopesByName(positionScopes, classId.shortClassName)?.classId
|
|
||||||
|
|
||||||
if (firstFoundClass == classId) {
|
|
||||||
addElementToShorten(qualifier)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val (mostTopLevelClassId, mostTopLevelQualifier) = allClassIds.zip(allQualifiers).last()
|
|
||||||
val availableClassifier = findFirstClassifierInScopesByName(positionScopes, mostTopLevelClassId.shortClassName)
|
|
||||||
|
|
||||||
check(availableClassifier?.classId != mostTopLevelClassId) {
|
|
||||||
"If this condition were true, we would have exited from the loop on the last iteration. ClassId = $mostTopLevelClassId"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (availableClassifier == null || availableClassifier.isFromStarOrPackageImport) {
|
|
||||||
addElementToImportAndShorten(mostTopLevelClassId.asSingleFqName(), mostTopLevelQualifier)
|
|
||||||
} else {
|
|
||||||
addFakePackagePrefixToShortenIfPresent(mostTopLevelQualifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun processPropertyReference(resolvedNamedReference: FirResolvedNamedReference) {
|
|
||||||
val referenceExpression = resolvedNamedReference.psi as? KtNameReferenceExpression
|
|
||||||
val qualifiedProperty = referenceExpression?.getDotQualifiedExpressionForSelector() ?: return
|
|
||||||
|
|
||||||
val propertyId = (resolvedNamedReference.resolvedSymbol as? FirCallableSymbol<*>)?.callableId ?: return
|
|
||||||
|
|
||||||
val scopes = findScopesAtPosition(qualifiedProperty, namesToImport) ?: return
|
|
||||||
val singleAvailableProperty = findSinglePropertyInScopesByName(scopes, propertyId.callableName)
|
|
||||||
|
|
||||||
if (singleAvailableProperty?.callableId == propertyId) {
|
|
||||||
addElementToShorten(qualifiedProperty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun processFunctionCall(functionCall: FirFunctionCall) {
|
|
||||||
if (!canBePossibleToDropReceiver(functionCall)) return
|
|
||||||
|
|
||||||
val callExpression = functionCall.psi as? KtCallExpression ?: return
|
|
||||||
val qualifiedCallExpression = callExpression.getDotQualifiedExpressionForSelector() ?: return
|
|
||||||
|
|
||||||
val calleeReference = functionCall.calleeReference
|
|
||||||
val callableId = findUnambiguousReferencedCallableId(calleeReference) ?: return
|
|
||||||
|
|
||||||
val scopes = findScopesAtPosition(callExpression, namesToImport) ?: return
|
|
||||||
val availableCallables = findFunctionsInScopes(scopes, callableId.callableName)
|
|
||||||
|
|
||||||
when {
|
|
||||||
availableCallables.isEmpty() -> {
|
|
||||||
val additionalImport = callableId.asImportableFqName() ?: return
|
|
||||||
addElementToImportAndShorten(additionalImport, qualifiedCallExpression)
|
|
||||||
}
|
|
||||||
availableCallables.all { it.callableId == callableId } -> {
|
|
||||||
addElementToShorten(qualifiedCallExpression)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
addFakePackagePrefixToShortenIfPresent(qualifiedCallExpression)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun canBePossibleToDropReceiver(functionCall: FirFunctionCall): Boolean {
|
|
||||||
// we can remove receiver only if it is a qualifier
|
|
||||||
val explicitReceiver = functionCall.explicitReceiver as? FirResolvedQualifier ?: return false
|
|
||||||
|
|
||||||
// if there is no extension receiver necessary, then it can be removed
|
|
||||||
if (functionCall.extensionReceiver is FirNoReceiverExpression) return true
|
|
||||||
|
|
||||||
val receiverType = explicitReceiver.typeRef.toRegularClass(firResolveState.rootModuleSession) ?: return true
|
|
||||||
return receiverType.classKind != ClassKind.OBJECT
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findUnambiguousReferencedCallableId(namedReference: FirNamedReference): CallableId? {
|
|
||||||
val unambiguousSymbol = when (namedReference) {
|
|
||||||
is FirResolvedNamedReference -> namedReference.resolvedSymbol
|
|
||||||
is FirErrorNamedReference -> {
|
|
||||||
val candidateSymbol = namedReference.candidateSymbol
|
|
||||||
if (candidateSymbol !is FirErrorFunctionSymbol) {
|
|
||||||
candidateSymbol
|
|
||||||
} else {
|
|
||||||
getSingleUnambiguousCandidate(namedReference)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (unambiguousSymbol as? FirCallableSymbol<*>)?.callableId
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If [namedReference] is ambiguous and all candidates point to the callables with same callableId,
|
|
||||||
* returns the first candidate; otherwise returns null.
|
|
||||||
*/
|
|
||||||
private fun getSingleUnambiguousCandidate(namedReference: FirErrorNamedReference): FirCallableSymbol<*>? {
|
|
||||||
val coneAmbiguityError = namedReference.diagnostic as? ConeAmbiguityError ?: return null
|
|
||||||
|
|
||||||
val candidates = coneAmbiguityError.candidates.map { it as FirCallableSymbol<*> }
|
|
||||||
require(candidates.isNotEmpty()) { "Cannot have zero candidates" }
|
|
||||||
|
|
||||||
val distinctCandidates = candidates.distinctBy { it.callableId }
|
|
||||||
return distinctCandidates.singleOrNull()
|
|
||||||
?: error("Expected all candidates to have same callableId, but got: ${distinctCandidates.map { it.callableId }}")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addFakePackagePrefixToShortenIfPresent(wholeQualifiedExpression: KtDotQualifiedExpression) {
|
|
||||||
val deepestQualifier = wholeQualifiedExpression.qualifiersWithSelf.last()
|
|
||||||
if (deepestQualifier.hasFakeRootPrefix()) {
|
|
||||||
addElementToShorten(deepestQualifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addElementToShorten(element: KtDotQualifiedExpression) {
|
|
||||||
qualifiersToShorten.add(element)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addElementToImportAndShorten(nameToImport: FqName, element: KtDotQualifiedExpression) {
|
|
||||||
namesToImport.add(nameToImport)
|
|
||||||
qualifiersToShorten.add(element)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val ClassId.outerClassesWithSelf: Sequence<ClassId>
|
|
||||||
get() = generateSequence(this) { it.outerClassId }
|
|
||||||
|
|
||||||
private val KtUserType.qualifiersWithSelf: Sequence<KtUserType>
|
|
||||||
get() = generateSequence(this) { it.qualifier }
|
|
||||||
|
|
||||||
private val KtDotQualifiedExpression.qualifiersWithSelf: Sequence<KtDotQualifiedExpression>
|
|
||||||
get() = generateSequence(this) { it.receiverExpression as? KtDotQualifiedExpression }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ElementsToShortenCollector(private val shorteningContext: FirShorteningContext) : FirVisitorVoid() {
|
||||||
|
val namesToImport: MutableList<FqName> = mutableListOf()
|
||||||
|
val typesToShorten: MutableList<KtUserType> = mutableListOf()
|
||||||
|
val qualifiersToShorten: MutableList<KtDotQualifiedExpression> = mutableListOf()
|
||||||
|
|
||||||
|
override fun visitElement(element: FirElement) {
|
||||||
|
element.acceptChildren(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitResolvedTypeRef(resolvedTypeRef: FirResolvedTypeRef) {
|
||||||
|
processTypeRef(resolvedTypeRef)
|
||||||
|
|
||||||
|
resolvedTypeRef.acceptChildren(this)
|
||||||
|
resolvedTypeRef.delegatedTypeRef?.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitResolvedQualifier(resolvedQualifier: FirResolvedQualifier) {
|
||||||
|
super.visitResolvedQualifier(resolvedQualifier)
|
||||||
|
|
||||||
|
processTypeQualifier(resolvedQualifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitResolvedNamedReference(resolvedNamedReference: FirResolvedNamedReference) {
|
||||||
|
super.visitResolvedNamedReference(resolvedNamedReference)
|
||||||
|
|
||||||
|
processPropertyReference(resolvedNamedReference)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitFunctionCall(functionCall: FirFunctionCall) {
|
||||||
|
super.visitFunctionCall(functionCall)
|
||||||
|
|
||||||
|
processFunctionCall(functionCall)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processTypeRef(resolvedTypeRef: FirResolvedTypeRef) {
|
||||||
|
val wholeTypeReference = resolvedTypeRef.psi as? KtTypeReference ?: return
|
||||||
|
|
||||||
|
val wholeClassifierId = resolvedTypeRef.type.lowerBoundIfFlexible().classId ?: return
|
||||||
|
val wholeTypeElement = wholeTypeReference.typeElement.unwrapNullable() as? KtUserType ?: return
|
||||||
|
|
||||||
|
if (wholeTypeElement.qualifier == null) return
|
||||||
|
|
||||||
|
collectTypeIfNeedsToBeShortened(wholeClassifierId, wholeTypeElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun collectTypeIfNeedsToBeShortened(wholeClassifierId: ClassId, wholeTypeElement: KtUserType) {
|
||||||
|
val allClassIds = wholeClassifierId.outerClassesWithSelf
|
||||||
|
val allTypeElements = wholeTypeElement.qualifiersWithSelf
|
||||||
|
|
||||||
|
val positionScopes = shorteningContext.findScopesAtPosition(wholeTypeElement, namesToImport) ?: return
|
||||||
|
|
||||||
|
for ((classId, typeElement) in allClassIds.zip(allTypeElements)) {
|
||||||
|
// if qualifier is null, then this type have no package and thus cannot be shortened
|
||||||
|
if (typeElement.qualifier == null) return
|
||||||
|
|
||||||
|
val firstFoundClass = shorteningContext.findFirstClassifierInScopesByName(positionScopes, classId.shortClassName)?.classId
|
||||||
|
|
||||||
|
if (firstFoundClass == classId) {
|
||||||
|
addTypeToShorten(typeElement)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// none class matched
|
||||||
|
val (mostTopLevelClassId, mostTopLevelTypeElement) = allClassIds.zip(allTypeElements).last()
|
||||||
|
val availableClassifier = shorteningContext.findFirstClassifierInScopesByName(positionScopes, mostTopLevelClassId.shortClassName)
|
||||||
|
|
||||||
|
check(availableClassifier?.classId != mostTopLevelClassId) {
|
||||||
|
"If this condition were true, we would have exited from the loop on the last iteration. ClassId = $mostTopLevelClassId"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (availableClassifier == null || availableClassifier.isFromStarOrPackageImport) {
|
||||||
|
addTypeToImportAndShorten(mostTopLevelClassId.asSingleFqName(), mostTopLevelTypeElement)
|
||||||
|
} else {
|
||||||
|
addFakePackagePrefixToShortenIfPresent(mostTopLevelTypeElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addFakePackagePrefixToShortenIfPresent(typeElement: KtUserType) {
|
||||||
|
val deepestTypeWithQualifier = typeElement.qualifiersWithSelf.last().parent as? KtUserType
|
||||||
|
?: error("Type element should have at least one qualifier, instead it was ${typeElement.text}")
|
||||||
|
|
||||||
|
if (deepestTypeWithQualifier.hasFakeRootPrefix()) {
|
||||||
|
addTypeToShorten(deepestTypeWithQualifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addTypeToShorten(typeElement: KtUserType) {
|
||||||
|
typesToShorten.add(typeElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addTypeToImportAndShorten(classFqName: FqName, mostTopLevelTypeElement: KtUserType) {
|
||||||
|
namesToImport.add(classFqName)
|
||||||
|
typesToShorten.add(mostTopLevelTypeElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processTypeQualifier(resolvedQualifier: FirResolvedQualifier) {
|
||||||
|
val wholeClassQualifier = resolvedQualifier.classId ?: return
|
||||||
|
val wholeQualifierElement = when (val qualifierPsi = resolvedQualifier.psi) {
|
||||||
|
is KtDotQualifiedExpression -> qualifierPsi
|
||||||
|
is KtNameReferenceExpression -> qualifierPsi.getDotQualifiedExpressionForSelector() ?: return
|
||||||
|
else -> return
|
||||||
|
}
|
||||||
|
|
||||||
|
collectQualifierIfNeedsToBeShortened(wholeClassQualifier, wholeQualifierElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun collectQualifierIfNeedsToBeShortened(wholeClassQualifier: ClassId, wholeQualifierElement: KtDotQualifiedExpression) {
|
||||||
|
val positionScopes = shorteningContext.findScopesAtPosition(wholeQualifierElement, namesToImport) ?: return
|
||||||
|
|
||||||
|
val allClassIds = wholeClassQualifier.outerClassesWithSelf
|
||||||
|
val allQualifiers = wholeQualifierElement.qualifiersWithSelf
|
||||||
|
|
||||||
|
for ((classId, qualifier) in allClassIds.zip(allQualifiers)) {
|
||||||
|
val firstFoundClass = shorteningContext.findFirstClassifierInScopesByName(positionScopes, classId.shortClassName)?.classId
|
||||||
|
|
||||||
|
if (firstFoundClass == classId) {
|
||||||
|
addElementToShorten(qualifier)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val (mostTopLevelClassId, mostTopLevelQualifier) = allClassIds.zip(allQualifiers).last()
|
||||||
|
val availableClassifier = shorteningContext.findFirstClassifierInScopesByName(positionScopes, mostTopLevelClassId.shortClassName)
|
||||||
|
|
||||||
|
check(availableClassifier?.classId != mostTopLevelClassId) {
|
||||||
|
"If this condition were true, we would have exited from the loop on the last iteration. ClassId = $mostTopLevelClassId"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (availableClassifier == null || availableClassifier.isFromStarOrPackageImport) {
|
||||||
|
addElementToImportAndShorten(mostTopLevelClassId.asSingleFqName(), mostTopLevelQualifier)
|
||||||
|
} else {
|
||||||
|
addFakePackagePrefixToShortenIfPresent(mostTopLevelQualifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processPropertyReference(resolvedNamedReference: FirResolvedNamedReference) {
|
||||||
|
val referenceExpression = resolvedNamedReference.psi as? KtNameReferenceExpression
|
||||||
|
val qualifiedProperty = referenceExpression?.getDotQualifiedExpressionForSelector() ?: return
|
||||||
|
|
||||||
|
val propertyId = (resolvedNamedReference.resolvedSymbol as? FirCallableSymbol<*>)?.callableId ?: return
|
||||||
|
|
||||||
|
val scopes = shorteningContext.findScopesAtPosition(qualifiedProperty, namesToImport) ?: return
|
||||||
|
val singleAvailableProperty = shorteningContext.findSinglePropertyInScopesByName(scopes, propertyId.callableName)
|
||||||
|
|
||||||
|
if (singleAvailableProperty?.callableId == propertyId) {
|
||||||
|
addElementToShorten(qualifiedProperty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processFunctionCall(functionCall: FirFunctionCall) {
|
||||||
|
if (!canBePossibleToDropReceiver(functionCall)) return
|
||||||
|
|
||||||
|
val callExpression = functionCall.psi as? KtCallExpression ?: return
|
||||||
|
val qualifiedCallExpression = callExpression.getDotQualifiedExpressionForSelector() ?: return
|
||||||
|
|
||||||
|
val calleeReference = functionCall.calleeReference
|
||||||
|
val callableId = findUnambiguousReferencedCallableId(calleeReference) ?: return
|
||||||
|
|
||||||
|
val scopes = shorteningContext.findScopesAtPosition(callExpression, namesToImport) ?: return
|
||||||
|
val availableCallables = shorteningContext.findFunctionsInScopes(scopes, callableId.callableName)
|
||||||
|
|
||||||
|
when {
|
||||||
|
availableCallables.isEmpty() -> {
|
||||||
|
val additionalImport = callableId.asImportableFqName() ?: return
|
||||||
|
addElementToImportAndShorten(additionalImport, qualifiedCallExpression)
|
||||||
|
}
|
||||||
|
availableCallables.all { it.callableId == callableId } -> {
|
||||||
|
addElementToShorten(qualifiedCallExpression)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
addFakePackagePrefixToShortenIfPresent(qualifiedCallExpression)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun canBePossibleToDropReceiver(functionCall: FirFunctionCall): Boolean {
|
||||||
|
// we can remove receiver only if it is a qualifier
|
||||||
|
val explicitReceiver = functionCall.explicitReceiver as? FirResolvedQualifier ?: return false
|
||||||
|
|
||||||
|
// if there is no extension receiver necessary, then it can be removed
|
||||||
|
if (functionCall.extensionReceiver is FirNoReceiverExpression) return true
|
||||||
|
|
||||||
|
val receiverType = shorteningContext.getRegularClass(explicitReceiver.typeRef) ?: return true
|
||||||
|
return receiverType.classKind != ClassKind.OBJECT
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findUnambiguousReferencedCallableId(namedReference: FirNamedReference): CallableId? {
|
||||||
|
val unambiguousSymbol = when (namedReference) {
|
||||||
|
is FirResolvedNamedReference -> namedReference.resolvedSymbol
|
||||||
|
is FirErrorNamedReference -> {
|
||||||
|
val candidateSymbol = namedReference.candidateSymbol
|
||||||
|
if (candidateSymbol !is FirErrorFunctionSymbol) {
|
||||||
|
candidateSymbol
|
||||||
|
} else {
|
||||||
|
getSingleUnambiguousCandidate(namedReference)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (unambiguousSymbol as? FirCallableSymbol<*>)?.callableId
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If [namedReference] is ambiguous and all candidates point to the callables with same callableId,
|
||||||
|
* returns the first candidate; otherwise returns null.
|
||||||
|
*/
|
||||||
|
private fun getSingleUnambiguousCandidate(namedReference: FirErrorNamedReference): FirCallableSymbol<*>? {
|
||||||
|
val coneAmbiguityError = namedReference.diagnostic as? ConeAmbiguityError ?: return null
|
||||||
|
|
||||||
|
val candidates = coneAmbiguityError.candidates.map { it as FirCallableSymbol<*> }
|
||||||
|
require(candidates.isNotEmpty()) { "Cannot have zero candidates" }
|
||||||
|
|
||||||
|
val distinctCandidates = candidates.distinctBy { it.callableId }
|
||||||
|
return distinctCandidates.singleOrNull()
|
||||||
|
?: error("Expected all candidates to have same callableId, but got: ${distinctCandidates.map { it.callableId }}")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addFakePackagePrefixToShortenIfPresent(wholeQualifiedExpression: KtDotQualifiedExpression) {
|
||||||
|
val deepestQualifier = wholeQualifiedExpression.qualifiersWithSelf.last()
|
||||||
|
if (deepestQualifier.hasFakeRootPrefix()) {
|
||||||
|
addElementToShorten(deepestQualifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addElementToShorten(element: KtDotQualifiedExpression) {
|
||||||
|
qualifiersToShorten.add(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addElementToImportAndShorten(nameToImport: FqName, element: KtDotQualifiedExpression) {
|
||||||
|
namesToImport.add(nameToImport)
|
||||||
|
qualifiersToShorten.add(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val ClassId.outerClassesWithSelf: Sequence<ClassId>
|
||||||
|
get() = generateSequence(this) { it.outerClassId }
|
||||||
|
|
||||||
|
private val KtUserType.qualifiersWithSelf: Sequence<KtUserType>
|
||||||
|
get() = generateSequence(this) { it.qualifier }
|
||||||
|
|
||||||
|
private val KtDotQualifiedExpression.qualifiersWithSelf: Sequence<KtDotQualifiedExpression>
|
||||||
|
get() = generateSequence(this) { it.receiverExpression as? KtDotQualifiedExpression }
|
||||||
|
}
|
||||||
|
|
||||||
private class ShortenCommandImpl(
|
private class ShortenCommandImpl(
|
||||||
val targetFile: KtFile,
|
val targetFile: KtFile,
|
||||||
val importsToAdd: List<FqName>,
|
val importsToAdd: List<FqName>,
|
||||||
|
|||||||
Reference in New Issue
Block a user