[FIR IDE] Handle FirImplicitInvokeCalls in KtFirImportOptimizer
Hacks with PSI to detect implicit invoke calls are no more needed
This commit is contained in:
committed by
teamcityserver
parent
c7a6a77a04
commit
43156dc726
@@ -24,7 +24,7 @@ fun FirFunctionCall.isImplicitFunctionCall(): Boolean {
|
|||||||
return calleeReference.getCandidateSymbols().any(FirBasedSymbol<*>::isInvokeFunction)
|
return calleeReference.getCandidateSymbols().any(FirBasedSymbol<*>::isInvokeFunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun FirBasedSymbol<*>.isInvokeFunction() =
|
private 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<*>? =
|
||||||
|
|||||||
+12
-35
@@ -9,7 +9,6 @@ import org.jetbrains.kotlin.fir.FirElement
|
|||||||
import org.jetbrains.kotlin.fir.FirSession
|
import org.jetbrains.kotlin.fir.FirSession
|
||||||
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.symbols.ensureResolved
|
import org.jetbrains.kotlin.fir.symbols.ensureResolved
|
||||||
@@ -17,7 +16,6 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
|
|||||||
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.assertIsValidAndAccessible
|
import org.jetbrains.kotlin.idea.frontend.api.assertIsValidAndAccessible
|
||||||
@@ -102,6 +100,11 @@ internal class KtFirImportOptimizer(
|
|||||||
super.visitFunctionCall(functionCall)
|
super.visitFunctionCall(functionCall)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun visitImplicitInvokeCall(implicitInvokeCall: FirImplicitInvokeCall) {
|
||||||
|
processImplicitFunctionCall(implicitInvokeCall)
|
||||||
|
super.visitImplicitInvokeCall(implicitInvokeCall)
|
||||||
|
}
|
||||||
|
|
||||||
override fun visitPropertyAccessExpression(propertyAccessExpression: FirPropertyAccessExpression) {
|
override fun visitPropertyAccessExpression(propertyAccessExpression: FirPropertyAccessExpression) {
|
||||||
processPropertyAccessExpression(propertyAccessExpression)
|
processPropertyAccessExpression(propertyAccessExpression)
|
||||||
super.visitPropertyAccessExpression(propertyAccessExpression)
|
super.visitPropertyAccessExpression(propertyAccessExpression)
|
||||||
@@ -128,13 +131,13 @@ internal class KtFirImportOptimizer(
|
|||||||
val functionReference = functionCall.toResolvedCallableReference() ?: return
|
val functionReference = functionCall.toResolvedCallableReference() ?: return
|
||||||
val functionSymbol = functionReference.toResolvedCallableSymbol() ?: return
|
val functionSymbol = functionReference.toResolvedCallableSymbol() ?: return
|
||||||
|
|
||||||
val referencedByName = if (functionCall.isInvokeOperatorImplicitCall) {
|
saveCallable(functionSymbol, functionReference.name)
|
||||||
OperatorNameConventions.INVOKE
|
}
|
||||||
} else {
|
|
||||||
functionReference.name
|
|
||||||
}
|
|
||||||
|
|
||||||
saveCallable(functionSymbol, referencedByName)
|
private fun processImplicitFunctionCall(implicitInvokeCall: FirImplicitInvokeCall) {
|
||||||
|
val functionSymbol = implicitInvokeCall.toResolvedCallableSymbol() ?: return
|
||||||
|
|
||||||
|
saveCallable(functionSymbol, OperatorNameConventions.INVOKE)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processPropertyAccessExpression(propertyAccessExpression: FirPropertyAccessExpression) {
|
private fun processPropertyAccessExpression(propertyAccessExpression: FirPropertyAccessExpression) {
|
||||||
@@ -170,7 +173,7 @@ internal class KtFirImportOptimizer(
|
|||||||
val mostOuterTypeQualifier = generateSequence(qualifier) { it.outerTypeQualifier }.last()
|
val mostOuterTypeQualifier = generateSequence(qualifier) { it.outerTypeQualifier }.last()
|
||||||
if (mostOuterTypeQualifier.isQualified) return
|
if (mostOuterTypeQualifier.isQualified) return
|
||||||
|
|
||||||
saveType(mostOuterTypeQualifier)
|
saveType(mostOuterTypeQualifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveType(qualifier: TypeQualifier) {
|
private fun saveType(qualifier: TypeQualifier) {
|
||||||
@@ -198,32 +201,6 @@ 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.
|
||||||
|
|||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
dependency.invoke
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
// FILE: main.kt
|
||||||
|
package test
|
||||||
|
|
||||||
|
import dependency.invoke
|
||||||
|
|
||||||
|
fun usage(foo: Int) {
|
||||||
|
foo + foo
|
||||||
|
}
|
||||||
|
|
||||||
|
// FILE: dependency.kt
|
||||||
|
package dependency
|
||||||
|
|
||||||
|
operator fun Int.invoke() {}
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
dependency.invoke
|
||||||
Vendored
+16
@@ -0,0 +1,16 @@
|
|||||||
|
// FILE: dependency.kt
|
||||||
|
package dependency
|
||||||
|
|
||||||
|
operator fun String.invoke() {}
|
||||||
|
|
||||||
|
// FILE: main.kt
|
||||||
|
package test
|
||||||
|
|
||||||
|
import dependency.invoke
|
||||||
|
import dependency.invoke as str
|
||||||
|
|
||||||
|
class My(val str: String)
|
||||||
|
|
||||||
|
fun usage(m: My) {
|
||||||
|
m.str.str()
|
||||||
|
}
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
dependency.invoke as str
|
||||||
Vendored
+16
@@ -0,0 +1,16 @@
|
|||||||
|
// FILE: dependency.kt
|
||||||
|
package dependency
|
||||||
|
|
||||||
|
operator fun String.invoke() {}
|
||||||
|
|
||||||
|
// FILE: main.kt
|
||||||
|
package test
|
||||||
|
|
||||||
|
import dependency.invoke
|
||||||
|
import dependency.invoke as str
|
||||||
|
|
||||||
|
class My(val str: String)
|
||||||
|
|
||||||
|
fun usage(m: My) {
|
||||||
|
m.str()
|
||||||
|
}
|
||||||
Vendored
+13
@@ -0,0 +1,13 @@
|
|||||||
|
// FILE: main.kt
|
||||||
|
package test
|
||||||
|
|
||||||
|
import dependency.invoke
|
||||||
|
|
||||||
|
fun usage(foo: Int) {
|
||||||
|
foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FILE: dependency.kt
|
||||||
|
package dependency
|
||||||
|
|
||||||
|
operator fun Int.invoke() {}
|
||||||
+24
@@ -36,6 +36,12 @@ public class HLImportOptimizerTestGenerated extends AbstractHLImportOptimizerTes
|
|||||||
runTest("idea/idea-frontend-fir/testData/components/importOptimizer/unusedFunctionImports.kt");
|
runTest("idea/idea-frontend-fir/testData/components/importOptimizer/unusedFunctionImports.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("unusedInvokeOperatorImport.kt")
|
||||||
|
public void testUnusedInvokeOperatorImport() throws Exception {
|
||||||
|
runTest("idea/idea-frontend-fir/testData/components/importOptimizer/unusedInvokeOperatorImport.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("usedAliasedTypeImport.kt")
|
@TestMetadata("usedAliasedTypeImport.kt")
|
||||||
public void testUsedAliasedTypeImport() throws Exception {
|
public void testUsedAliasedTypeImport() throws Exception {
|
||||||
@@ -47,4 +53,22 @@ public class HLImportOptimizerTestGenerated extends AbstractHLImportOptimizerTes
|
|||||||
public void testUsedFunctionImport() throws Exception {
|
public void testUsedFunctionImport() throws Exception {
|
||||||
runTest("idea/idea-frontend-fir/testData/components/importOptimizer/usedFunctionImport.kt");
|
runTest("idea/idea-frontend-fir/testData/components/importOptimizer/usedFunctionImport.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("usedInvokeOperatorAliasedImport.kt")
|
||||||
|
public void testUsedInvokeOperatorAliasedImport() throws Exception {
|
||||||
|
runTest("idea/idea-frontend-fir/testData/components/importOptimizer/usedInvokeOperatorAliasedImport.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("usedInvokeOperatorExplicitImport.kt")
|
||||||
|
public void testUsedInvokeOperatorExplicitImport() throws Exception {
|
||||||
|
runTest("idea/idea-frontend-fir/testData/components/importOptimizer/usedInvokeOperatorExplicitImport.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("usedInvokeOperatorImport.kt")
|
||||||
|
public void testUsedInvokeOperatorImport() throws Exception {
|
||||||
|
runTest("idea/idea-frontend-fir/testData/components/importOptimizer/usedInvokeOperatorImport.kt");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user