diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirImportOptimizer.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirImportOptimizer.kt index 20fe4e53657..972ce8acdb2 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirImportOptimizer.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirImportOptimizer.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.analysis.api.fir.components +import org.jetbrains.kotlin.KtFakeSourceElementKind import org.jetbrains.kotlin.KtRealSourceElementKind import org.jetbrains.kotlin.analysis.api.components.KtImportOptimizer 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.FirResolvedTypeRef import org.jetbrains.kotlin.fir.types.classId -import org.jetbrains.kotlin.name.ClassId -import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.name.parentOrNull +import org.jetbrains.kotlin.name.* import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getCallNameExpression import org.jetbrains.kotlin.psi.psiUtil.getPossiblyQualifiedCallExpression @@ -178,17 +176,13 @@ internal class KtFirImportOptimizer( processErrorNameReference(functionCall) val referencesByName = functionCall.functionReferenceName ?: return - val functionSymbol = functionCall.referencedCallableSymbol ?: return - - saveCallable(functionSymbol, referencesByName) + saveCallable(functionCall, referencesByName) } private fun processImplicitFunctionCall(implicitInvokeCall: FirImplicitInvokeCall) { processErrorNameReference(implicitInvokeCall) - val functionSymbol = implicitInvokeCall.referencedCallableSymbol ?: return - - saveCallable(functionSymbol, OperatorNameConventions.INVOKE) + saveCallable(implicitInvokeCall, OperatorNameConventions.INVOKE) } private fun processPropertyAccessExpression(propertyAccessExpression: FirPropertyAccessExpression) { @@ -196,9 +190,8 @@ internal class KtFirImportOptimizer( processErrorNameReference(propertyAccessExpression) val referencedByName = propertyAccessExpression.propertyReferenceName ?: return - val propertySymbol = propertyAccessExpression.referencedCallableSymbol ?: return - saveCallable(propertySymbol, referencedByName) + saveCallable(propertyAccessExpression, referencedByName) } private fun processTypeRef(resolvedTypeRef: FirResolvedTypeRef) { @@ -212,9 +205,7 @@ internal class KtFirImportOptimizer( processErrorNameReference(callableReferenceAccess) val referencedByName = callableReferenceAccess.callableReferenceName ?: return - val resolvedSymbol = callableReferenceAccess.referencedCallableSymbol ?: return - - saveCallable(resolvedSymbol, referencedByName) + saveCallable(callableReferenceAccess, referencedByName) } private fun processResolvedQualifier(resolvedQualifier: FirResolvedQualifier) { @@ -237,12 +228,58 @@ internal class KtFirImportOptimizer( saveReferencedItem(importableName, referencedByName) } - private fun saveCallable(resolvedSymbol: FirCallableSymbol<*>, referencedByName: Name) { - val importableName = resolvedSymbol.computeImportableName(firSession) ?: return + private fun saveCallable(qualifiedCall: FirQualifiedAccessExpression, referencedByName: Name) { + val importableName = importableNameForReferencedSymbol(qualifiedCall) ?: return 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) { 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? get() { diff --git a/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/importOptimizer/FirIdeNormalAnalysisSourceModuleAnalysisApiImportOptimizerTestGenerated.java b/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/importOptimizer/FirIdeNormalAnalysisSourceModuleAnalysisApiImportOptimizerTestGenerated.java index 62e157ff47b..094ee81de1c 100644 --- a/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/importOptimizer/FirIdeNormalAnalysisSourceModuleAnalysisApiImportOptimizerTestGenerated.java +++ b/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/importOptimizer/FirIdeNormalAnalysisSourceModuleAnalysisApiImportOptimizerTestGenerated.java @@ -64,6 +64,12 @@ public class FirIdeNormalAnalysisSourceModuleAnalysisApiImportOptimizerTestGener 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 @TestMetadata("unusedFunctionImports.kt") public void testUnusedFunctionImports() throws Exception { @@ -118,6 +124,12 @@ public class FirIdeNormalAnalysisSourceModuleAnalysisApiImportOptimizerTestGener 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 @TestMetadata("usedGenericTypeQualifier.kt") public void testUsedGenericTypeQualifier() throws Exception { diff --git a/analysis/analysis-api-standalone/tests-gen/org/jetbrains/kotlin/analysis/api/standalone/fir/test/cases/generated/cases/components/importOptimizer/FirStandaloneNormalAnalysisSourceModuleAnalysisApiImportOptimizerTestGenerated.java b/analysis/analysis-api-standalone/tests-gen/org/jetbrains/kotlin/analysis/api/standalone/fir/test/cases/generated/cases/components/importOptimizer/FirStandaloneNormalAnalysisSourceModuleAnalysisApiImportOptimizerTestGenerated.java index e0b6dd5a5a8..627177268b6 100644 --- a/analysis/analysis-api-standalone/tests-gen/org/jetbrains/kotlin/analysis/api/standalone/fir/test/cases/generated/cases/components/importOptimizer/FirStandaloneNormalAnalysisSourceModuleAnalysisApiImportOptimizerTestGenerated.java +++ b/analysis/analysis-api-standalone/tests-gen/org/jetbrains/kotlin/analysis/api/standalone/fir/test/cases/generated/cases/components/importOptimizer/FirStandaloneNormalAnalysisSourceModuleAnalysisApiImportOptimizerTestGenerated.java @@ -64,6 +64,12 @@ public class FirStandaloneNormalAnalysisSourceModuleAnalysisApiImportOptimizerTe 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 @TestMetadata("unusedFunctionImports.kt") public void testUnusedFunctionImports() throws Exception { @@ -118,6 +124,12 @@ public class FirStandaloneNormalAnalysisSourceModuleAnalysisApiImportOptimizerTe 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 @TestMetadata("usedGenericTypeQualifier.kt") public void testUsedGenericTypeQualifier() throws Exception { diff --git a/analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedFunctionImportedFromObjectSuperClass.imports b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedFunctionImportedFromObjectSuperClass.imports new file mode 100644 index 00000000000..27196954129 --- /dev/null +++ b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedFunctionImportedFromObjectSuperClass.imports @@ -0,0 +1,6 @@ +test.Foo.callableReference +test.Foo.extFuncFromBase +test.Foo.extPropFromBase +test.Foo.funcFromBase +test.Foo.invoke +test.Foo.propFromBase diff --git a/analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedFunctionImportedFromObjectSuperClass.kt b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedFunctionImportedFromObjectSuperClass.kt new file mode 100644 index 00000000000..6522399ebcc --- /dev/null +++ b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedFunctionImportedFromObjectSuperClass.kt @@ -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 + } +} \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedFunctionImportedFromObjectSuperClass.imports b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedFunctionImportedFromObjectSuperClass.imports new file mode 100644 index 00000000000..e69de29bb2d diff --git a/analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedFunctionImportedFromObjectSuperClass.kt b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedFunctionImportedFromObjectSuperClass.kt new file mode 100644 index 00000000000..5dcd012b59a --- /dev/null +++ b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedFunctionImportedFromObjectSuperClass.kt @@ -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 +} \ No newline at end of file