[Analysis API FIR] do not consider unresolved imports as unused in import optimizer
Otherwise, the import optimizer breaks incomplete code ^KTIJ-25034 fixed
This commit is contained in:
committed by
Space Team
parent
d9ca77f716
commit
15f19f324e
+55
-5
@@ -21,7 +21,12 @@ import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
|
||||
import org.jetbrains.kotlin.fir.expressions.*
|
||||
import org.jetbrains.kotlin.fir.psi
|
||||
import org.jetbrains.kotlin.fir.realPsi
|
||||
import org.jetbrains.kotlin.fir.references.FirNamedReference
|
||||
import org.jetbrains.kotlin.fir.references.FirErrorNamedReference
|
||||
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeUnresolvedError
|
||||
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeUnresolvedNameError
|
||||
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeUnresolvedReferenceError
|
||||
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeUnresolvedSymbolError
|
||||
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeUnresolvedTypeQualifierError
|
||||
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
|
||||
@@ -62,7 +67,9 @@ internal class KtFirImportOptimizer(
|
||||
.map { it.fqName }
|
||||
.toSet()
|
||||
|
||||
val referencesEntities = collectReferencedEntities(firFile)
|
||||
val (usedImports, unresolvedNames) = collectReferencedEntities(firFile)
|
||||
|
||||
val referencesEntities = usedImports
|
||||
.filterNot { (fqName, referencedByNames) ->
|
||||
// when referenced by more than one name, we need to keep the imports with same package
|
||||
fqName.parentOrNull() == file.packageFqName && referencedByNames.size == 1
|
||||
@@ -82,8 +89,9 @@ internal class KtFirImportOptimizer(
|
||||
val importPath = import.importPath ?: continue
|
||||
|
||||
val isUsed = when {
|
||||
importPath.importedName in unresolvedNames -> true
|
||||
!alreadySeenImports.add(importPath) -> false
|
||||
importPath.isAllUnder -> importPath.fqName in requiredStarImports
|
||||
importPath.isAllUnder -> unresolvedNames.isNotEmpty() || importPath.fqName in requiredStarImports
|
||||
importPath.fqName in referencesEntities -> importPath.importedName in referencesEntities.getValue(importPath.fqName)
|
||||
else -> false
|
||||
}
|
||||
@@ -96,8 +104,14 @@ internal class KtFirImportOptimizer(
|
||||
return KtImportOptimizerResult(unusedImports)
|
||||
}
|
||||
|
||||
private fun collectReferencedEntities(firFile: FirFile): Map<FqName, Set<Name>> {
|
||||
private data class ReferencedEntitiesResult(
|
||||
val usedImports: Map<FqName, Set<Name>>,
|
||||
val unresolvedNames: Set<Name>,
|
||||
)
|
||||
|
||||
private fun collectReferencedEntities(firFile: FirFile): ReferencedEntitiesResult {
|
||||
val usedImports = mutableMapOf<FqName, MutableSet<Name>>()
|
||||
val unresolvedNames = mutableSetOf<Name>()
|
||||
|
||||
firFile.accept(object : FirVisitorVoid() {
|
||||
override fun visitElement(element: FirElement) {
|
||||
@@ -128,6 +142,7 @@ internal class KtFirImportOptimizer(
|
||||
|
||||
override fun visitErrorTypeRef(errorTypeRef: FirErrorTypeRef) {
|
||||
processTypeRef(errorTypeRef)
|
||||
processErrorTypeRef(errorTypeRef)
|
||||
|
||||
errorTypeRef.delegatedTypeRef?.accept(this)
|
||||
super.visitErrorTypeRef(errorTypeRef)
|
||||
@@ -148,8 +163,21 @@ internal class KtFirImportOptimizer(
|
||||
super.visitErrorResolvedQualifier(errorResolvedQualifier)
|
||||
}
|
||||
|
||||
private fun processErrorNameReference(resolvable: FirResolvable) {
|
||||
val nameReference = resolvable.calleeReference as? FirErrorNamedReference ?: return
|
||||
val name = nameReference.unresolvedName ?: return
|
||||
unresolvedNames += name
|
||||
}
|
||||
|
||||
private fun processErrorTypeRef(typeRef: FirErrorTypeRef) {
|
||||
val diagnostic = typeRef.diagnostic as? ConeUnresolvedError ?: return
|
||||
val name = diagnostic.unresolvedName ?: return
|
||||
unresolvedNames += name
|
||||
}
|
||||
|
||||
private fun processFunctionCall(functionCall: FirFunctionCall) {
|
||||
if (functionCall.isFullyQualified) return
|
||||
processErrorNameReference(functionCall)
|
||||
|
||||
val referencesByName = functionCall.functionReferenceName ?: return
|
||||
val functionSymbol = functionCall.referencedCallableSymbol ?: return
|
||||
@@ -158,6 +186,8 @@ internal class KtFirImportOptimizer(
|
||||
}
|
||||
|
||||
private fun processImplicitFunctionCall(implicitInvokeCall: FirImplicitInvokeCall) {
|
||||
processErrorNameReference(implicitInvokeCall)
|
||||
|
||||
val functionSymbol = implicitInvokeCall.referencedCallableSymbol ?: return
|
||||
|
||||
saveCallable(functionSymbol, OperatorNameConventions.INVOKE)
|
||||
@@ -165,6 +195,7 @@ internal class KtFirImportOptimizer(
|
||||
|
||||
private fun processPropertyAccessExpression(propertyAccessExpression: FirPropertyAccessExpression) {
|
||||
if (propertyAccessExpression.isFullyQualified) return
|
||||
processErrorNameReference(propertyAccessExpression)
|
||||
|
||||
val referencedByName = propertyAccessExpression.propertyReferenceName ?: return
|
||||
val propertySymbol = propertyAccessExpression.referencedCallableSymbol ?: return
|
||||
@@ -180,6 +211,7 @@ internal class KtFirImportOptimizer(
|
||||
|
||||
private fun processCallableReferenceAccess(callableReferenceAccess: FirCallableReferenceAccess) {
|
||||
if (callableReferenceAccess.isFullyQualified) return
|
||||
processErrorNameReference(callableReferenceAccess)
|
||||
|
||||
val referencedByName = callableReferenceAccess.callableReferenceName ?: return
|
||||
val resolvedSymbol = callableReferenceAccess.referencedCallableSymbol ?: return
|
||||
@@ -218,10 +250,28 @@ internal class KtFirImportOptimizer(
|
||||
}
|
||||
})
|
||||
|
||||
return usedImports
|
||||
return ReferencedEntitiesResult(usedImports, unresolvedNames)
|
||||
}
|
||||
}
|
||||
|
||||
private val FirErrorNamedReference.unresolvedName: Name?
|
||||
get() {
|
||||
val diagnostic = diagnostic as? ConeUnresolvedError ?: return null
|
||||
return diagnostic.unresolvedName
|
||||
}
|
||||
|
||||
private val ConeUnresolvedError.unresolvedName: Name?
|
||||
get() = when (this) {
|
||||
is ConeUnresolvedNameError -> name
|
||||
is ConeUnresolvedReferenceError -> name
|
||||
is ConeUnresolvedSymbolError -> classId.shortClassName
|
||||
is ConeUnresolvedTypeQualifierError -> {
|
||||
// we take the first qualifier part because only the first one can be imported
|
||||
qualifiers.firstOrNull()?.name
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* An actual name by which this callable reference were used.
|
||||
*/
|
||||
|
||||
+54
@@ -133,12 +133,66 @@ public class FirIdeNormalAnalysisSourceModuleAnalysisApiImportOptimizerTestGener
|
||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("unresolvedFunction.kt")
|
||||
public void testUnresolvedFunction() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/unresolvedFunction.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("unresolvedFunctionStarImport.kt")
|
||||
public void testUnresolvedFunctionStarImport() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/unresolvedFunctionStarImport.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("unresolvedProperty.kt")
|
||||
public void testUnresolvedProperty() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/unresolvedProperty.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("unresolvedType.kt")
|
||||
public void testUnresolvedType() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/unresolvedType.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("unresolvedTypeArgument.kt")
|
||||
public void testUnresolvedTypeArgument() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/unresolvedTypeArgument.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("unresolvedTypeQualifier.kt")
|
||||
public void testUnresolvedTypeQualifier() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/unresolvedTypeQualifier.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("unresolvedTypeQualifierConstructor.kt")
|
||||
public void testUnresolvedTypeQualifierConstructor() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/unresolvedTypeQualifierConstructor.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("unresolvedViaImportAlias.kt")
|
||||
public void testUnresolvedViaImportAlias() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/unresolvedViaImportAlias.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("unusedTypeHiddenByTypeParameter_invalidAsArgument.kt")
|
||||
public void testUnusedTypeHiddenByTypeParameter_invalidAsArgument() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/unusedTypeHiddenByTypeParameter_invalidAsArgument.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("unusedUnresolvedImport.kt")
|
||||
public void testUnusedUnresolvedImport() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/unusedUnresolvedImport.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("usedConstructor_invalidArguments.kt")
|
||||
public void testUsedConstructor_invalidArguments() throws Exception {
|
||||
|
||||
+54
@@ -133,12 +133,66 @@ public class FirStandaloneNormalAnalysisSourceModuleAnalysisApiImportOptimizerTe
|
||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("unresolvedFunction.kt")
|
||||
public void testUnresolvedFunction() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/unresolvedFunction.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("unresolvedFunctionStarImport.kt")
|
||||
public void testUnresolvedFunctionStarImport() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/unresolvedFunctionStarImport.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("unresolvedProperty.kt")
|
||||
public void testUnresolvedProperty() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/unresolvedProperty.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("unresolvedType.kt")
|
||||
public void testUnresolvedType() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/unresolvedType.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("unresolvedTypeArgument.kt")
|
||||
public void testUnresolvedTypeArgument() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/unresolvedTypeArgument.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("unresolvedTypeQualifier.kt")
|
||||
public void testUnresolvedTypeQualifier() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/unresolvedTypeQualifier.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("unresolvedTypeQualifierConstructor.kt")
|
||||
public void testUnresolvedTypeQualifierConstructor() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/unresolvedTypeQualifierConstructor.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("unresolvedViaImportAlias.kt")
|
||||
public void testUnresolvedViaImportAlias() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/unresolvedViaImportAlias.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("unusedTypeHiddenByTypeParameter_invalidAsArgument.kt")
|
||||
public void testUnusedTypeHiddenByTypeParameter_invalidAsArgument() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/unusedTypeHiddenByTypeParameter_invalidAsArgument.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("unusedUnresolvedImport.kt")
|
||||
public void testUnusedUnresolvedImport() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/unusedUnresolvedImport.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("usedConstructor_invalidArguments.kt")
|
||||
public void testUsedConstructor_invalidArguments() throws Exception {
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package m2.second
|
||||
|
||||
import a.b.c.readText
|
||||
|
||||
fun t() {
|
||||
readText()
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package m2.second
|
||||
|
||||
import a.b.c.*
|
||||
|
||||
fun t() {
|
||||
readText()
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package m2.second
|
||||
|
||||
import a.b.c.prop
|
||||
|
||||
fun t() {
|
||||
prop
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package m2.second
|
||||
|
||||
import a.b.c.X
|
||||
|
||||
fun t(): X {
|
||||
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package m2.second
|
||||
|
||||
import a.b.c.X
|
||||
|
||||
fun t(): List<X> {
|
||||
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package m2.second
|
||||
|
||||
import a.b.c.X
|
||||
import a.b.c.C
|
||||
|
||||
fun t() {
|
||||
X.C
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package m2.second
|
||||
|
||||
import a.b.c.X
|
||||
import a.b.c.C
|
||||
|
||||
fun t() {
|
||||
X.C()
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package m2.second
|
||||
|
||||
import a.b.c.prop as prp
|
||||
|
||||
fun t() {
|
||||
prp
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
a.a.Foo
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package one
|
||||
|
||||
import a.c.c.D
|
||||
import a.a.Foo
|
||||
import a.star.*
|
||||
|
||||
fun t() {
|
||||
D()
|
||||
}
|
||||
Reference in New Issue
Block a user