[FIR IDE] Improve imports analysis for import aliases

Also make it work more correctly with invoke operator functions
This commit is contained in:
Roman Golyshev
2021-08-26 19:22:33 +03:00
committed by Space
parent 989cd487a6
commit 824acc2c4f
2 changed files with 54 additions and 14 deletions
@@ -24,7 +24,7 @@ fun FirFunctionCall.isImplicitFunctionCall(): Boolean {
return calleeReference.getCandidateSymbols().any(FirBasedSymbol<*>::isInvokeFunction) return calleeReference.getCandidateSymbols().any(FirBasedSymbol<*>::isInvokeFunction)
} }
private fun FirBasedSymbol<*>.isInvokeFunction() = fun FirBasedSymbol<*>.isInvokeFunction() =
(this as? FirNamedFunctionSymbol)?.fir?.name == OperatorNameConventions.INVOKE (this as? FirNamedFunctionSymbol)?.fir?.name == OperatorNameConventions.INVOKE
fun FirFunctionCall.getCalleeSymbol(): FirBasedSymbol<*>? = fun FirFunctionCall.getCalleeSymbol(): FirBasedSymbol<*>? =
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClassSymbol
import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin
import org.jetbrains.kotlin.fir.declarations.FirFile import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.declarations.utils.isOperator
import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.psi import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.fir.references.FirResolvedCallableReference import org.jetbrains.kotlin.fir.references.FirResolvedCallableReference
@@ -22,16 +23,19 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
import org.jetbrains.kotlin.fir.types.classId import org.jetbrains.kotlin.fir.types.classId
import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid
import org.jetbrains.kotlin.idea.fir.isInvokeFunction
import org.jetbrains.kotlin.idea.fir.low.level.api.api.FirModuleResolveState import org.jetbrains.kotlin.idea.fir.low.level.api.api.FirModuleResolveState
import org.jetbrains.kotlin.idea.fir.low.level.api.api.getOrBuildFirFile import org.jetbrains.kotlin.idea.fir.low.level.api.api.getOrBuildFirFile
import org.jetbrains.kotlin.idea.frontend.api.components.KtImportOptimizer import org.jetbrains.kotlin.idea.frontend.api.components.KtImportOptimizer
import org.jetbrains.kotlin.idea.frontend.api.components.KtImportOptimizerResult import org.jetbrains.kotlin.idea.frontend.api.components.KtImportOptimizerResult
import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.parentOrNull import org.jetbrains.kotlin.name.parentOrNull
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.unwrapNullability import org.jetbrains.kotlin.psi.psiUtil.unwrapNullability
import org.jetbrains.kotlin.resolve.ImportPath import org.jetbrains.kotlin.resolve.ImportPath
import org.jetbrains.kotlin.util.OperatorNameConventions
internal class KtFirImportOptimizer( internal class KtFirImportOptimizer(
private val firResolveState: FirModuleResolveState private val firResolveState: FirModuleResolveState
@@ -53,7 +57,7 @@ internal class KtFirImportOptimizer(
val referencesEntities = collectReferencedEntities(firFile) val referencesEntities = collectReferencedEntities(firFile)
val requiredStarImports = referencesEntities val requiredStarImports = referencesEntities.keys
.asSequence() .asSequence()
.filterNot { it in explicitlyImportedFqNames } .filterNot { it in explicitlyImportedFqNames }
.mapNotNull { it.parentOrNull() } .mapNotNull { it.parentOrNull() }
@@ -69,7 +73,7 @@ internal class KtFirImportOptimizer(
val isUsed = when { val isUsed = when {
!alreadySeenImports.add(importPath) -> false !alreadySeenImports.add(importPath) -> false
importPath.isAllUnder -> importPath.fqName in requiredStarImports importPath.isAllUnder -> importPath.fqName in requiredStarImports
importPath.fqName in referencesEntities -> true importPath.fqName in referencesEntities -> importPath.importedName in referencesEntities.getValue(importPath.fqName)
else -> false else -> false
} }
@@ -81,8 +85,8 @@ internal class KtFirImportOptimizer(
return KtImportOptimizerResult(unusedImports) return KtImportOptimizerResult(unusedImports)
} }
private fun collectReferencedEntities(firFile: FirFile): Set<FqName> { private fun collectReferencedEntities(firFile: FirFile): Map<FqName, Set<Name>> {
val usedImports: MutableSet<FqName> = mutableSetOf() val usedImports = mutableMapOf<FqName, MutableSet<Name>>()
firFile.accept(object : FirVisitorVoid() { firFile.accept(object : FirVisitorVoid() {
override fun visitElement(element: FirElement) { override fun visitElement(element: FirElement) {
@@ -117,15 +121,25 @@ internal class KtFirImportOptimizer(
private fun processFunctionCall(functionCall: FirFunctionCall) { private fun processFunctionCall(functionCall: FirFunctionCall) {
if (functionCall.isFullyQualified) return if (functionCall.isFullyQualified) return
val functionSymbol = functionCall.toResolvedCallableSymbol() ?: return val functionReference = functionCall.toResolvedCallableReference() ?: return
saveCallable(functionSymbol) val functionSymbol = functionReference.toResolvedCallableSymbol() ?: return
val referencedByName = if (functionCall.isInvokeOperatorImplicitCall) {
OperatorNameConventions.INVOKE
} else {
functionReference.name
}
saveCallable(functionSymbol, referencedByName)
} }
private fun processPropertyAccessExpression(propertyAccessExpression: FirPropertyAccessExpression) { private fun processPropertyAccessExpression(propertyAccessExpression: FirPropertyAccessExpression) {
if (propertyAccessExpression.isFullyQualified) return if (propertyAccessExpression.isFullyQualified) return
val propertySymbol = propertyAccessExpression.toResolvedCallableSymbol() ?: return val propertyReference = propertyAccessExpression.toResolvedCallableReference() ?: return
saveCallable(propertySymbol) val propertySymbol = propertyReference.toResolvedCallableSymbol() ?: return
saveCallable(propertySymbol, propertyReference.name)
} }
private fun processTypeRef(resolvedTypeRef: FirResolvedTypeRef) { private fun processTypeRef(resolvedTypeRef: FirResolvedTypeRef) {
@@ -137,7 +151,7 @@ internal class KtFirImportOptimizer(
private fun processResolvedCallableReference(resolvedCallableReference: FirResolvedCallableReference) { private fun processResolvedCallableReference(resolvedCallableReference: FirResolvedCallableReference) {
val referencedCallableSymbol = resolvedCallableReference.resolvedSymbol as? FirCallableSymbol<*> ?: return val referencedCallableSymbol = resolvedCallableReference.resolvedSymbol as? FirCallableSymbol<*> ?: return
saveCallable(referencedCallableSymbol) saveCallable(referencedCallableSymbol, resolvedCallableReference.name)
} }
private fun processResolvedQualifier(resolvedQualifier: FirResolvedQualifier) { private fun processResolvedQualifier(resolvedQualifier: FirResolvedQualifier) {
@@ -154,12 +168,12 @@ internal class KtFirImportOptimizer(
} }
private fun saveType(classId: ClassId) { private fun saveType(classId: ClassId) {
usedImports += classId.asSingleFqName() usedImports.getOrPut(classId.asSingleFqName()) { hashSetOf() } += classId.shortClassName
} }
private fun saveCallable(symbol: FirCallableSymbol<*>) { private fun saveCallable(resolvedSymbol: FirCallableSymbol<*>, referencedByName: Name) {
val importableName = symbol.computeImportableName() ?: return val importableName = resolvedSymbol.computeImportableName() ?: return
usedImports += importableName usedImports.getOrPut(importableName) { hashSetOf() } += referencedByName
} }
}) })
@@ -191,6 +205,32 @@ internal class KtFirImportOptimizer(
private val FirQualifiedAccessExpression.isFullyQualified: Boolean private val FirQualifiedAccessExpression.isFullyQualified: Boolean
get() = explicitReceiver is FirResolvedQualifier get() = explicitReceiver is FirResolvedQualifier
/**
* A hacky way to distinguish `foo()` call from `foo.invoke()` call.
*
* Need to be removed when the compiler is fixed to use `FirImplicitInvokeCall` in all appropriate situations.
*/
private val FirFunctionCall.isInvokeOperatorImplicitCall: Boolean
get() {
val functionSymbol = toResolvedCallableSymbol() ?: return false
if (!functionSymbol.isInvokeFunction()) return false
if (!functionSymbol.isOperator) return false
val receiver = explicitReceiver ?: return false
val callPsi = when (val psi = psi) {
is KtQualifiedExpression -> psi.selectorExpression as? KtCallExpression
is KtCallExpression -> psi
else -> null
} ?: return false
// with invoke operator, explicit receiver does not have a source for some reason
val receiverPsi = receiver.psi ?: return true
return callPsi.calleeExpression == receiverPsi
}
/** /**
* Helper abstraction to navigate through qualified FIR elements - we have to match [ClassId] and PSI qualifier pair * Helper abstraction to navigate through qualified FIR elements - we have to match [ClassId] and PSI qualifier pair
* to correctly reason about long qualifiers. * to correctly reason about long qualifiers.