FIR IDE: Decouple ElementsToShortenCollector from KtFirReferenceShortener

This commit is contained in:
Roman Golyshev
2021-02-09 17:31:22 +03:00
committed by Space
parent c89380fc36
commit 09ba927680
@@ -11,6 +11,7 @@ import com.intellij.psi.SmartPsiElementPointer
import com.intellij.util.containers.addIfNotNull
import org.jetbrains.kotlin.descriptors.ClassKind
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.analysis.checkers.toRegularClass
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.impl.*
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.lowerBoundIfFlexible
import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid
@@ -60,11 +62,13 @@ internal class KtFirReferenceShortener(
override val token: ValidityToken,
override val firResolveState: FirModuleResolveState,
) : KtReferenceShortener(), KtFirAnalysisSessionComponent {
private val context = FirShorteningContext(firResolveState)
override fun collectShortenings(file: KtFile, selection: TextRange): ShortenCommand {
resolveFileToBodyResolve(file)
val firFile = file.getOrBuildFirOfType<FirFile>(firResolveState)
val collector = ElementsToShortenCollector()
val collector = ElementsToShortenCollector(context)
firFile.acceptChildren(collector)
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) {
val classifierSymbol = scope.findFirstClassifierByName(targetClassName) ?: continue
val classifierLookupTag = classifierSymbol.toLookupTag() as? ConeClassLikeLookupTag ?: continue
@@ -91,20 +107,14 @@ internal class KtFirReferenceShortener(
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) }
}
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()
}
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<*>? {
var element: FirClassifierSymbol<*>? = null
@@ -117,16 +127,12 @@ internal class KtFirReferenceShortener(
return element
}
@OptIn(ExperimentalStdlibApi::class)
private fun FirScope.getSingleFunctionByName(name: Name): FirNamedFunctionSymbol? =
buildList { processFunctionsByName(name, this::add) }.singleOrNull()
@OptIn(ExperimentalStdlibApi::class)
private fun FirScope.getSinglePropertyByName(name: Name): FirVariableSymbol<*>? =
buildList { processPropertiesByName(name, this::add) }.singleOrNull()
@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 result = buildList<FirScope> {
@@ -142,11 +148,11 @@ internal class KtFirReferenceShortener(
val resolvedNewImports = newImports.mapNotNull { createFakeResolvedImport(it) }
if (resolvedNewImports.isEmpty()) return null
return FirExplicitSimpleImportingScope(resolvedNewImports, firResolveState.rootModuleSession, ScopeSession())
return FirExplicitSimpleImportingScope(resolvedNewImports, firSession, ScopeSession())
}
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 {
importedFqName = fqNameToImport
@@ -159,252 +165,256 @@ internal class KtFirReferenceShortener(
}
}
private inner class ElementsToShortenCollector : 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 = 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 }
fun getRegularClass(typeRef: FirTypeRef): FirRegularClass? {
return typeRef.toRegularClass(firSession)
}
}
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(
val targetFile: KtFile,
val importsToAdd: List<FqName>,