KTIJ-25530 [Analysis API] Correctly collect references to implicitly dispatched callables in Import Optimizer
N.B. There is a case which is not covered ATM due to the bug in the compiler, see KT-58980 ^KTIJ-25530 Fixed
This commit is contained in:
+55
-18
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.analysis.api.fir.components
|
package org.jetbrains.kotlin.analysis.api.fir.components
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.KtFakeSourceElementKind
|
||||||
import org.jetbrains.kotlin.KtRealSourceElementKind
|
import org.jetbrains.kotlin.KtRealSourceElementKind
|
||||||
import org.jetbrains.kotlin.analysis.api.components.KtImportOptimizer
|
import org.jetbrains.kotlin.analysis.api.components.KtImportOptimizer
|
||||||
import org.jetbrains.kotlin.analysis.api.components.KtImportOptimizerResult
|
import org.jetbrains.kotlin.analysis.api.components.KtImportOptimizerResult
|
||||||
@@ -33,10 +34,7 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
|
|||||||
import org.jetbrains.kotlin.fir.types.FirErrorTypeRef
|
import org.jetbrains.kotlin.fir.types.FirErrorTypeRef
|
||||||
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.name.ClassId
|
import org.jetbrains.kotlin.name.*
|
||||||
import org.jetbrains.kotlin.name.FqName
|
|
||||||
import org.jetbrains.kotlin.name.Name
|
|
||||||
import org.jetbrains.kotlin.name.parentOrNull
|
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getCallNameExpression
|
import org.jetbrains.kotlin.psi.psiUtil.getCallNameExpression
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getPossiblyQualifiedCallExpression
|
import org.jetbrains.kotlin.psi.psiUtil.getPossiblyQualifiedCallExpression
|
||||||
@@ -178,17 +176,13 @@ internal class KtFirImportOptimizer(
|
|||||||
processErrorNameReference(functionCall)
|
processErrorNameReference(functionCall)
|
||||||
|
|
||||||
val referencesByName = functionCall.functionReferenceName ?: return
|
val referencesByName = functionCall.functionReferenceName ?: return
|
||||||
val functionSymbol = functionCall.referencedCallableSymbol ?: return
|
saveCallable(functionCall, referencesByName)
|
||||||
|
|
||||||
saveCallable(functionSymbol, referencesByName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processImplicitFunctionCall(implicitInvokeCall: FirImplicitInvokeCall) {
|
private fun processImplicitFunctionCall(implicitInvokeCall: FirImplicitInvokeCall) {
|
||||||
processErrorNameReference(implicitInvokeCall)
|
processErrorNameReference(implicitInvokeCall)
|
||||||
|
|
||||||
val functionSymbol = implicitInvokeCall.referencedCallableSymbol ?: return
|
saveCallable(implicitInvokeCall, OperatorNameConventions.INVOKE)
|
||||||
|
|
||||||
saveCallable(functionSymbol, OperatorNameConventions.INVOKE)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processPropertyAccessExpression(propertyAccessExpression: FirPropertyAccessExpression) {
|
private fun processPropertyAccessExpression(propertyAccessExpression: FirPropertyAccessExpression) {
|
||||||
@@ -196,9 +190,8 @@ internal class KtFirImportOptimizer(
|
|||||||
processErrorNameReference(propertyAccessExpression)
|
processErrorNameReference(propertyAccessExpression)
|
||||||
|
|
||||||
val referencedByName = propertyAccessExpression.propertyReferenceName ?: return
|
val referencedByName = propertyAccessExpression.propertyReferenceName ?: return
|
||||||
val propertySymbol = propertyAccessExpression.referencedCallableSymbol ?: return
|
|
||||||
|
|
||||||
saveCallable(propertySymbol, referencedByName)
|
saveCallable(propertyAccessExpression, referencedByName)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processTypeRef(resolvedTypeRef: FirResolvedTypeRef) {
|
private fun processTypeRef(resolvedTypeRef: FirResolvedTypeRef) {
|
||||||
@@ -212,9 +205,7 @@ internal class KtFirImportOptimizer(
|
|||||||
processErrorNameReference(callableReferenceAccess)
|
processErrorNameReference(callableReferenceAccess)
|
||||||
|
|
||||||
val referencedByName = callableReferenceAccess.callableReferenceName ?: return
|
val referencedByName = callableReferenceAccess.callableReferenceName ?: return
|
||||||
val resolvedSymbol = callableReferenceAccess.referencedCallableSymbol ?: return
|
saveCallable(callableReferenceAccess, referencedByName)
|
||||||
|
|
||||||
saveCallable(resolvedSymbol, referencedByName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processResolvedQualifier(resolvedQualifier: FirResolvedQualifier) {
|
private fun processResolvedQualifier(resolvedQualifier: FirResolvedQualifier) {
|
||||||
@@ -237,12 +228,58 @@ internal class KtFirImportOptimizer(
|
|||||||
saveReferencedItem(importableName, referencedByName)
|
saveReferencedItem(importableName, referencedByName)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveCallable(resolvedSymbol: FirCallableSymbol<*>, referencedByName: Name) {
|
private fun saveCallable(qualifiedCall: FirQualifiedAccessExpression, referencedByName: Name) {
|
||||||
val importableName = resolvedSymbol.computeImportableName(firSession) ?: return
|
val importableName = importableNameForReferencedSymbol(qualifiedCall) ?: return
|
||||||
|
|
||||||
saveReferencedItem(importableName, referencedByName)
|
saveReferencedItem(importableName, referencedByName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun importableNameForReferencedSymbol(qualifiedCall: FirQualifiedAccessExpression): FqName? {
|
||||||
|
return qualifiedCall.importableNameForImplicitlyDispatchedCallable()
|
||||||
|
?: qualifiedCall.referencedCallableSymbol?.computeImportableName(firSession)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns correct importable name for implicitly dispatched callable - that is, a callable
|
||||||
|
* which has a dispatch receiver, but whose dispatch receiver is present implicitly. The most
|
||||||
|
* important case for that is the following:
|
||||||
|
*
|
||||||
|
* ```kt
|
||||||
|
* import MyObject.bar
|
||||||
|
*
|
||||||
|
* open class Base { fun bar() {} }
|
||||||
|
*
|
||||||
|
* object MyObject : Base()
|
||||||
|
*
|
||||||
|
* fun test() {
|
||||||
|
* bar()
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* For the `bar()` call, `MyObject` instance is an implicit dispatch receiver.
|
||||||
|
*
|
||||||
|
* In such case, [FirQualifiedAccessExpression] representing the call references
|
||||||
|
* the original `Base.bar` callable symbol instead of `MyObject.bar`, because there are
|
||||||
|
* no separate symbol for that case.
|
||||||
|
*
|
||||||
|
* Java statics present a similar case - they can be imported not only from the declaring class,
|
||||||
|
* but also from any subclass.
|
||||||
|
*/
|
||||||
|
private fun FirQualifiedAccessExpression.importableNameForImplicitlyDispatchedCallable(): FqName? {
|
||||||
|
val dispatchReceiver = dispatchReceiver
|
||||||
|
if (
|
||||||
|
dispatchReceiver !is FirResolvedQualifier ||
|
||||||
|
dispatchReceiver.source?.kind != KtFakeSourceElementKind.ImplicitReceiver
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val dispatcherClass = dispatchReceiver.classId ?: return null
|
||||||
|
val referencedSymbolName = referencedCallableSymbol?.name ?: return null
|
||||||
|
|
||||||
|
return CallableId(dispatcherClass, referencedSymbolName).asSingleFqName()
|
||||||
|
}
|
||||||
|
|
||||||
private fun saveReferencedItem(importableName: FqName, referencedByName: Name) {
|
private fun saveReferencedItem(importableName: FqName, referencedByName: Name) {
|
||||||
usedImports.getOrPut(importableName) { hashSetOf() } += referencedByName
|
usedImports.getOrPut(importableName) { hashSetOf() } += referencedByName
|
||||||
}
|
}
|
||||||
@@ -271,7 +308,7 @@ private val ConeUnresolvedError.unresolvedName: Name?
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An actual name by which this callable reference were used.
|
* An actual name by which this callable reference was used.
|
||||||
*/
|
*/
|
||||||
private val FirCallableReferenceAccess.callableReferenceName: Name?
|
private val FirCallableReferenceAccess.callableReferenceName: Name?
|
||||||
get() {
|
get() {
|
||||||
|
|||||||
+12
@@ -64,6 +64,12 @@ public class FirIdeNormalAnalysisSourceModuleAnalysisApiImportOptimizerTestGener
|
|||||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedExtensionFunctionFromObject_implicitReceiver.kt");
|
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedExtensionFunctionFromObject_implicitReceiver.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("unusedFunctionImportedFromObjectSuperClass.kt")
|
||||||
|
public void testUnusedFunctionImportedFromObjectSuperClass() throws Exception {
|
||||||
|
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedFunctionImportedFromObjectSuperClass.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("unusedFunctionImports.kt")
|
@TestMetadata("unusedFunctionImports.kt")
|
||||||
public void testUnusedFunctionImports() throws Exception {
|
public void testUnusedFunctionImports() throws Exception {
|
||||||
@@ -118,6 +124,12 @@ public class FirIdeNormalAnalysisSourceModuleAnalysisApiImportOptimizerTestGener
|
|||||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedFunctionImport.kt");
|
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedFunctionImport.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("usedFunctionImportedFromObjectSuperClass.kt")
|
||||||
|
public void testUsedFunctionImportedFromObjectSuperClass() throws Exception {
|
||||||
|
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedFunctionImportedFromObjectSuperClass.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("usedGenericTypeQualifier.kt")
|
@TestMetadata("usedGenericTypeQualifier.kt")
|
||||||
public void testUsedGenericTypeQualifier() throws Exception {
|
public void testUsedGenericTypeQualifier() throws Exception {
|
||||||
|
|||||||
+12
@@ -64,6 +64,12 @@ public class FirStandaloneNormalAnalysisSourceModuleAnalysisApiImportOptimizerTe
|
|||||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedExtensionFunctionFromObject_implicitReceiver.kt");
|
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedExtensionFunctionFromObject_implicitReceiver.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("unusedFunctionImportedFromObjectSuperClass.kt")
|
||||||
|
public void testUnusedFunctionImportedFromObjectSuperClass() throws Exception {
|
||||||
|
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedFunctionImportedFromObjectSuperClass.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("unusedFunctionImports.kt")
|
@TestMetadata("unusedFunctionImports.kt")
|
||||||
public void testUnusedFunctionImports() throws Exception {
|
public void testUnusedFunctionImports() throws Exception {
|
||||||
@@ -118,6 +124,12 @@ public class FirStandaloneNormalAnalysisSourceModuleAnalysisApiImportOptimizerTe
|
|||||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedFunctionImport.kt");
|
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedFunctionImport.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("usedFunctionImportedFromObjectSuperClass.kt")
|
||||||
|
public void testUsedFunctionImportedFromObjectSuperClass() throws Exception {
|
||||||
|
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedFunctionImportedFromObjectSuperClass.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("usedGenericTypeQualifier.kt")
|
@TestMetadata("usedGenericTypeQualifier.kt")
|
||||||
public void testUsedGenericTypeQualifier() throws Exception {
|
public void testUsedGenericTypeQualifier() throws Exception {
|
||||||
|
|||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
test.Foo.callableReference
|
||||||
|
test.Foo.extFuncFromBase
|
||||||
|
test.Foo.extPropFromBase
|
||||||
|
test.Foo.funcFromBase
|
||||||
|
test.Foo.invoke
|
||||||
|
test.Foo.propFromBase
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import test.Foo.funcFromBase
|
||||||
|
import test.Foo.extFuncFromBase
|
||||||
|
import test.Foo.propFromBase
|
||||||
|
import test.Foo.extPropFromBase
|
||||||
|
import test.Foo.invoke
|
||||||
|
import test.Foo.callableReference
|
||||||
|
|
||||||
|
open class Base {
|
||||||
|
fun funcFromBase() {}
|
||||||
|
fun Int.extFuncFromBase() {}
|
||||||
|
|
||||||
|
val propFromBase: Int = 0
|
||||||
|
val Int.extPropFromBase: Int get() = 0
|
||||||
|
|
||||||
|
operator fun String.invoke() {}
|
||||||
|
|
||||||
|
fun callableReference() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Foo : Base()
|
||||||
|
|
||||||
|
fun usage() {
|
||||||
|
with(Foo) {
|
||||||
|
funcFromBase()
|
||||||
|
10.extFuncFromBase()
|
||||||
|
|
||||||
|
propFromBase
|
||||||
|
10.extPropFromBase
|
||||||
|
|
||||||
|
"hello"()
|
||||||
|
|
||||||
|
::callableReference
|
||||||
|
}
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import test.Foo.funcFromBase
|
||||||
|
import test.Foo.extFuncFromBase
|
||||||
|
import test.Foo.propFromBase
|
||||||
|
import test.Foo.extPropFromBase
|
||||||
|
import test.Foo.invoke
|
||||||
|
import test.Foo.callableReference
|
||||||
|
|
||||||
|
open class Base {
|
||||||
|
fun funcFromBase() {}
|
||||||
|
fun Int.extFuncFromBase() {}
|
||||||
|
|
||||||
|
val propFromBase: Int = 0
|
||||||
|
val Int.extPropFromBase: Int get() = 0
|
||||||
|
|
||||||
|
operator fun String.invoke() {}
|
||||||
|
|
||||||
|
fun callableReference() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Foo : Base()
|
||||||
|
|
||||||
|
fun usage() {
|
||||||
|
funcFromBase()
|
||||||
|
10.extFuncFromBase()
|
||||||
|
|
||||||
|
propFromBase
|
||||||
|
10.extPropFromBase
|
||||||
|
|
||||||
|
"hello"()
|
||||||
|
|
||||||
|
::callableReference
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user