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 48389158d83..b4977e84ba2 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 @@ -174,7 +174,7 @@ internal class KtFirImportOptimizer( } private fun processFunctionCall(functionCall: FirFunctionCall) { - if (functionCall.isFullyQualified) return + if (functionCall.dispatchedWithoutImport) return processErrorNameReference(functionCall) val referencesByName = functionCall.functionReferenceName ?: return @@ -192,7 +192,7 @@ internal class KtFirImportOptimizer( } private fun processPropertyAccessExpression(propertyAccessExpression: FirPropertyAccessExpression) { - if (propertyAccessExpression.isFullyQualified) return + if (propertyAccessExpression.dispatchedWithoutImport) return processErrorNameReference(propertyAccessExpression) val referencedByName = propertyAccessExpression.propertyReferenceName ?: return @@ -208,7 +208,7 @@ internal class KtFirImportOptimizer( } private fun processCallableReferenceAccess(callableReferenceAccess: FirCallableReferenceAccess) { - if (callableReferenceAccess.isFullyQualified) return + if (callableReferenceAccess.dispatchedWithoutImport) return processErrorNameReference(callableReferenceAccess) val referencedByName = callableReferenceAccess.callableReferenceName ?: return @@ -331,8 +331,30 @@ private val FirResolvedTypeRef.resolvedClassId: ClassId? return singleClassSymbol?.classId } -private val FirQualifiedAccessExpression.isFullyQualified: Boolean - get() = explicitReceiver is FirResolvedQualifier +private val FirQualifiedAccessExpression.dispatchedWithoutImport: Boolean + get() = when { + isQualifiedWithPackage -> true + dispatchReceiver is FirThisReceiverExpression -> true + dispatchReceiver == explicitReceiver -> true + else -> false + } + + +/** + * Returns `true` if [this] expression is fully-qualified with package name. + * Such expressions definitely do not need any kind of imports. + * + * Examples: + * - `pkg.foo()` - `true` + * - `foo()` - `false` + * - `Obj.foo()` - `false` + * - `pkg.Obj.foo()` - `false` + */ +private val FirQualifiedAccessExpression.isQualifiedWithPackage: Boolean + get() { + val receiver = explicitReceiver + return receiver is FirResolvedQualifier && receiver.relativeClassFqName == null + } private fun KtExpression.getPossiblyQualifiedSimpleNameExpression(): KtSimpleNameExpression? { return ((this as? KtQualifiedExpression)?.selectorExpression ?: this) as? KtSimpleNameExpression? 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 8929458b852..62e157ff47b 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 @@ -58,6 +58,12 @@ public class FirIdeNormalAnalysisSourceModuleAnalysisApiImportOptimizerTestGener runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedAliasedTypeImport.kt"); } + @Test + @TestMetadata("unusedExtensionFunctionFromObject_implicitReceiver.kt") + public void testUnusedExtensionFunctionFromObject_implicitReceiver() throws Exception { + runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedExtensionFunctionFromObject_implicitReceiver.kt"); + } + @Test @TestMetadata("unusedFunctionImports.kt") public void testUnusedFunctionImports() throws Exception { @@ -70,6 +76,12 @@ public class FirIdeNormalAnalysisSourceModuleAnalysisApiImportOptimizerTestGener runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedGenericTypeQualifier.kt"); } + @Test + @TestMetadata("unusedImportFromObject.kt") + public void testUnusedImportFromObject() throws Exception { + runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedImportFromObject.kt"); + } + @Test @TestMetadata("unusedInvokeOperatorImport.kt") public void testUnusedInvokeOperatorImport() throws Exception { @@ -88,6 +100,18 @@ public class FirIdeNormalAnalysisSourceModuleAnalysisApiImportOptimizerTestGener runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedAliasedTypeImport.kt"); } + @Test + @TestMetadata("usedExtensionFunctionFromObject_implicitReceiver.kt") + public void testUsedExtensionFunctionFromObject_implicitReceiver() throws Exception { + runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedExtensionFunctionFromObject_implicitReceiver.kt"); + } + + @Test + @TestMetadata("usedExtensionFunction_objectReceiver.kt") + public void testUsedExtensionFunction_objectReceiver() throws Exception { + runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedExtensionFunction_objectReceiver.kt"); + } + @Test @TestMetadata("usedFunctionImport.kt") public void testUsedFunctionImport() throws Exception { @@ -100,6 +124,12 @@ public class FirIdeNormalAnalysisSourceModuleAnalysisApiImportOptimizerTestGener runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedGenericTypeQualifier.kt"); } + @Test + @TestMetadata("usedImportFromObject.kt") + public void testUsedImportFromObject() throws Exception { + runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedImportFromObject.kt"); + } + @Test @TestMetadata("usedInvokeOperatorAliasedImport.kt") public void testUsedInvokeOperatorAliasedImport() 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 db03c56793c..e0b6dd5a5a8 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 @@ -58,6 +58,12 @@ public class FirStandaloneNormalAnalysisSourceModuleAnalysisApiImportOptimizerTe runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedAliasedTypeImport.kt"); } + @Test + @TestMetadata("unusedExtensionFunctionFromObject_implicitReceiver.kt") + public void testUnusedExtensionFunctionFromObject_implicitReceiver() throws Exception { + runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedExtensionFunctionFromObject_implicitReceiver.kt"); + } + @Test @TestMetadata("unusedFunctionImports.kt") public void testUnusedFunctionImports() throws Exception { @@ -70,6 +76,12 @@ public class FirStandaloneNormalAnalysisSourceModuleAnalysisApiImportOptimizerTe runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedGenericTypeQualifier.kt"); } + @Test + @TestMetadata("unusedImportFromObject.kt") + public void testUnusedImportFromObject() throws Exception { + runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedImportFromObject.kt"); + } + @Test @TestMetadata("unusedInvokeOperatorImport.kt") public void testUnusedInvokeOperatorImport() throws Exception { @@ -88,6 +100,18 @@ public class FirStandaloneNormalAnalysisSourceModuleAnalysisApiImportOptimizerTe runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedAliasedTypeImport.kt"); } + @Test + @TestMetadata("usedExtensionFunctionFromObject_implicitReceiver.kt") + public void testUsedExtensionFunctionFromObject_implicitReceiver() throws Exception { + runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedExtensionFunctionFromObject_implicitReceiver.kt"); + } + + @Test + @TestMetadata("usedExtensionFunction_objectReceiver.kt") + public void testUsedExtensionFunction_objectReceiver() throws Exception { + runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedExtensionFunction_objectReceiver.kt"); + } + @Test @TestMetadata("usedFunctionImport.kt") public void testUsedFunctionImport() throws Exception { @@ -100,6 +124,12 @@ public class FirStandaloneNormalAnalysisSourceModuleAnalysisApiImportOptimizerTe runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedGenericTypeQualifier.kt"); } + @Test + @TestMetadata("usedImportFromObject.kt") + public void testUsedImportFromObject() throws Exception { + runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedImportFromObject.kt"); + } + @Test @TestMetadata("usedInvokeOperatorAliasedImport.kt") public void testUsedInvokeOperatorAliasedImport() throws Exception { diff --git a/analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedExtensionFunctionFromObject_implicitReceiver.imports b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedExtensionFunctionFromObject_implicitReceiver.imports new file mode 100644 index 00000000000..119e6df9647 --- /dev/null +++ b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedExtensionFunctionFromObject_implicitReceiver.imports @@ -0,0 +1 @@ +dependency.Bar.extFun \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedExtensionFunctionFromObject_implicitReceiver.kt b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedExtensionFunctionFromObject_implicitReceiver.kt new file mode 100644 index 00000000000..9da2e446b6f --- /dev/null +++ b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedExtensionFunctionFromObject_implicitReceiver.kt @@ -0,0 +1,19 @@ +// FILE: main.kt +package test + +import dependency.Bar.extFun + +fun usage() { + with(dependency.Bar) { + with(10) { + extFun() + } + } +} + +// FILE: dependency.kt +package dependency + +object Bar { + fun Int.extFun() {} +} diff --git a/analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedImportFromObject.imports b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedImportFromObject.imports new file mode 100644 index 00000000000..899be668c27 --- /dev/null +++ b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedImportFromObject.imports @@ -0,0 +1,3 @@ +dependency.Bar.callable +dependency.Bar.function +dependency.Bar.property \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedImportFromObject.kt b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedImportFromObject.kt new file mode 100644 index 00000000000..f4a10aec4c2 --- /dev/null +++ b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedImportFromObject.kt @@ -0,0 +1,30 @@ +// FILE: main.kt +import dependency.Bar.property +import dependency.Bar.function +import dependency.Bar.callable + +fun test() { + dependency.Bar.property + dependency.Bar.function() + dependency.Bar::callable + + with(dependency.Bar) { + property + function() + ::callable + } + + val bar = dependency.Bar + bar.property + bar.function() + bar::callable +} + +// FILE: dependency.kt +package dependency + +object Bar { + val property: Int = 10 + fun function() {} + fun callable() {} +} diff --git a/analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedExtensionFunctionFromObject_implicitReceiver.imports b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedExtensionFunctionFromObject_implicitReceiver.imports new file mode 100644 index 00000000000..e69de29bb2d diff --git a/analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedExtensionFunctionFromObject_implicitReceiver.kt b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedExtensionFunctionFromObject_implicitReceiver.kt new file mode 100644 index 00000000000..f006185e228 --- /dev/null +++ b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedExtensionFunctionFromObject_implicitReceiver.kt @@ -0,0 +1,17 @@ +// FILE: main.kt +package test + +import dependency.Bar.extFun + +fun usage() { + with(10) { + extFun() + } +} + +// FILE: dependency.kt +package dependency + +object Bar { + fun Int.extFun() {} +} diff --git a/analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedExtensionFunction_objectReceiver.imports b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedExtensionFunction_objectReceiver.imports new file mode 100644 index 00000000000..e69de29bb2d diff --git a/analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedExtensionFunction_objectReceiver.kt b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedExtensionFunction_objectReceiver.kt new file mode 100644 index 00000000000..7176ad964de --- /dev/null +++ b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedExtensionFunction_objectReceiver.kt @@ -0,0 +1,26 @@ +// FILE: main.kt +package test + +import dependency.Bar +import dependency.extFun +import dependency.extVal +import dependency.extCallable + +fun usage() { + Bar.extFun() + + Bar.extVal + + val ref = Bar::extCallable +} + +// FILE: dependency.kt +package dependency + +object Bar + +fun Bar.extFun() {} + +val Bar.extVal: Int get() = 10 + +fun Bar.extCallable() {} \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedImportFromObject.imports b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedImportFromObject.imports new file mode 100644 index 00000000000..e69de29bb2d diff --git a/analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedImportFromObject.kt b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedImportFromObject.kt new file mode 100644 index 00000000000..ded31a2d258 --- /dev/null +++ b/analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedImportFromObject.kt @@ -0,0 +1,19 @@ +// FILE: main.kt +import dependency.Bar.property +import dependency.Bar.function +import dependency.Bar.callable + +fun test() { + property + function() + ::callable +} + +// FILE: dependency.kt +package dependency + +object Bar { + val property: Int = 10 + fun function() {} + fun callable() {} +}