[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:
Ilya Kirillov
2023-03-24 10:06:09 +01:00
committed by Space Team
parent d9ca77f716
commit 15f19f324e
21 changed files with 236 additions and 5 deletions
@@ -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.
*/
@@ -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 {
@@ -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 {
@@ -0,0 +1,7 @@
package m2.second
import a.b.c.readText
fun t() {
readText()
}
@@ -0,0 +1,7 @@
package m2.second
import a.b.c.*
fun t() {
readText()
}
@@ -0,0 +1,7 @@
package m2.second
import a.b.c.prop
fun t() {
prop
}
@@ -0,0 +1,7 @@
package m2.second
import a.b.c.X
fun t(): X {
}
@@ -0,0 +1,7 @@
package m2.second
import a.b.c.X
fun t(): List<X> {
}
@@ -0,0 +1,8 @@
package m2.second
import a.b.c.X
import a.b.c.C
fun t() {
X.C
}
@@ -0,0 +1,8 @@
package m2.second
import a.b.c.X
import a.b.c.C
fun t() {
X.C()
}
@@ -0,0 +1,7 @@
package m2.second
import a.b.c.prop as prp
fun t() {
prp
}
@@ -0,0 +1,9 @@
package one
import a.c.c.D
import a.a.Foo
import a.star.*
fun t() {
D()
}