From e34b64d6f2006ef901e86158f4839dc9ec180262 Mon Sep 17 00:00:00 2001 From: Roman Golyshev Date: Fri, 10 Sep 2021 21:47:10 +0300 Subject: [PATCH] [FIR IDE] Make `KtFirImportOptimizer` more robust to reference errors --- .../fir/components/KtFirImportOptimizer.kt | 104 ++++++++++++++++-- .../usedConstructor_invalidArguments.imports | 0 .../usedConstructor_invalidArguments.kt | 13 +++ .../usedConstructor_missingCall.imports | 0 .../usedConstructor_missingCall.kt | 13 +++ ...ExtensionFunction_invalidArguments.imports | 0 .../usedExtensionFunction_invalidArguments.kt | 16 +++ ...dExtensionProperty_invalidReceiver.imports | 0 .../usedExtensionProperty_invalidReceiver.kt | 16 +++ ...sedInvokeOperator_invalidArguments.imports | 0 .../usedInvokeOperator_invalidArguments.kt | 16 +++ .../usedTypeImport_missingGeneric.imports | 0 .../usedTypeImport_missingGeneric.kt | 11 ++ .../usedAliasedFunctionReference.imports | 0 .../usedAliasedFunctionReference.kt | 13 +++ .../HLImportOptimizerTestGenerated.java | 52 +++++++++ 16 files changed, 244 insertions(+), 10 deletions(-) create mode 100644 idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedConstructor_invalidArguments.imports create mode 100644 idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedConstructor_invalidArguments.kt create mode 100644 idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedConstructor_missingCall.imports create mode 100644 idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedConstructor_missingCall.kt create mode 100644 idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedExtensionFunction_invalidArguments.imports create mode 100644 idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedExtensionFunction_invalidArguments.kt create mode 100644 idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedExtensionProperty_invalidReceiver.imports create mode 100644 idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedExtensionProperty_invalidReceiver.kt create mode 100644 idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedInvokeOperator_invalidArguments.imports create mode 100644 idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedInvokeOperator_invalidArguments.kt create mode 100644 idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedTypeImport_missingGeneric.imports create mode 100644 idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedTypeImport_missingGeneric.kt create mode 100644 idea/idea-frontend-fir/testData/components/importOptimizer/usedAliasedFunctionReference.imports create mode 100644 idea/idea-frontend-fir/testData/components/importOptimizer/usedAliasedFunctionReference.kt diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirImportOptimizer.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirImportOptimizer.kt index 0bc49ea13b0..0d5f0705132 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirImportOptimizer.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirImportOptimizer.kt @@ -11,11 +11,16 @@ import org.jetbrains.kotlin.fir.declarations.FirFile 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.symbols.ensureResolved import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol +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.fir.visitors.FirVisitorVoid +import org.jetbrains.kotlin.idea.fir.getCandidateSymbols import org.jetbrains.kotlin.idea.fir.low.level.api.api.FirModuleResolveState import org.jetbrains.kotlin.idea.fir.low.level.api.api.getOrBuildFirFile import org.jetbrains.kotlin.idea.frontend.api.assertIsValidAndAccessible @@ -28,6 +33,8 @@ 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.psiUtil.getCallNameExpression +import org.jetbrains.kotlin.psi.psiUtil.getPossiblyQualifiedCallExpression import org.jetbrains.kotlin.psi.psiUtil.unwrapNullability import org.jetbrains.kotlin.resolve.ImportPath import org.jetbrains.kotlin.util.OperatorNameConventions @@ -115,6 +122,11 @@ internal class KtFirImportOptimizer( super.visitTypeRef(resolvedTypeRef) } + override fun visitErrorTypeRef(errorTypeRef: FirErrorTypeRef) { + processTypeRef(errorTypeRef) + super.visitErrorTypeRef(errorTypeRef) + } + override fun visitCallableReferenceAccess(callableReferenceAccess: FirCallableReferenceAccess) { processCallableReferenceAccess(callableReferenceAccess) super.visitCallableReferenceAccess(callableReferenceAccess) @@ -125,17 +137,22 @@ internal class KtFirImportOptimizer( super.visitResolvedQualifier(resolvedQualifier) } + override fun visitErrorResolvedQualifier(errorResolvedQualifier: FirErrorResolvedQualifier) { + processResolvedQualifier(errorResolvedQualifier) + super.visitErrorResolvedQualifier(errorResolvedQualifier) + } + private fun processFunctionCall(functionCall: FirFunctionCall) { if (functionCall.isFullyQualified) return - val functionReference = functionCall.toResolvedCallableReference() ?: return - val functionSymbol = functionReference.toResolvedCallableSymbol() ?: return + val referencesByName = functionCall.functionReferenceName ?: return + val functionSymbol = functionCall.referencedCallableSymbol ?: return - saveCallable(functionSymbol, functionReference.name) + saveCallable(functionSymbol, referencesByName) } private fun processImplicitFunctionCall(implicitInvokeCall: FirImplicitInvokeCall) { - val functionSymbol = implicitInvokeCall.toResolvedCallableSymbol() ?: return + val functionSymbol = implicitInvokeCall.referencedCallableSymbol ?: return saveCallable(functionSymbol, OperatorNameConventions.INVOKE) } @@ -143,10 +160,10 @@ internal class KtFirImportOptimizer( private fun processPropertyAccessExpression(propertyAccessExpression: FirPropertyAccessExpression) { if (propertyAccessExpression.isFullyQualified) return - val propertyReference = propertyAccessExpression.toResolvedCallableReference() ?: return - val propertySymbol = propertyReference.toResolvedCallableSymbol() ?: return + val referencedByName = propertyAccessExpression.propertyReferenceName ?: return + val propertySymbol = propertyAccessExpression.referencedCallableSymbol ?: return - saveCallable(propertySymbol, propertyReference.name) + saveCallable(propertySymbol, referencedByName) } private fun processTypeRef(resolvedTypeRef: FirResolvedTypeRef) { @@ -158,9 +175,10 @@ internal class KtFirImportOptimizer( private fun processCallableReferenceAccess(callableReferenceAccess: FirCallableReferenceAccess) { if (callableReferenceAccess.isFullyQualified) return - val resolvedSymbol = callableReferenceAccess.calleeReference.toResolvedCallableSymbol() ?: return + val referencedByName = callableReferenceAccess.callableReferenceName ?: return + val resolvedSymbol = callableReferenceAccess.referencedCallableSymbol ?: return - saveCallable(resolvedSymbol, resolvedSymbol.name) + saveCallable(resolvedSymbol, referencedByName) } private fun processResolvedQualifier(resolvedQualifier: FirResolvedQualifier) { @@ -198,9 +216,75 @@ internal class KtFirImportOptimizer( } } +/** + * An actual name by which this callable reference were used. + */ +private val FirCallableReferenceAccess.callableReferenceName: Name? + get() { + toResolvedCallableReference()?.let { return it.name } + + val wholeCallableReferenceExpression = realPsi as? KtCallableReferenceExpression + + return wholeCallableReferenceExpression?.callableReference?.getReferencedNameAsName() + } + +/** + * A name by which referenced functions was called. + */ +private val FirFunctionCall.functionReferenceName: Name? + get() { + toResolvedCallableReference()?.let { return it.name } + + // unresolved reference has incorrect name, so we have to retrieve it by PSI + val wholeCallExpression = realPsi as? KtExpression + val callExpression = wholeCallExpression?.getPossiblyQualifiedCallExpression() + + return callExpression?.getCallNameExpression()?.getReferencedNameAsName() + } + +/** + * A name by which referenced property is used. + */ +private val FirPropertyAccessExpression.propertyReferenceName: Name? + get() { + toResolvedCallableReference()?.let { return it.name } + + // unresolved reference has incorrect name, so we have to retrieve it by PSI + val wholePropertyAccessExpression = realPsi as? KtExpression + val propertyNameExpression = wholePropertyAccessExpression?.getPossiblyQualifiedSimpleNameExpression() + + return propertyNameExpression?.getReferencedNameAsName() + } + +/** + * Referenced callable symbol, even if it not completely correctly resolved. + */ +private val FirQualifiedAccessExpression.referencedCallableSymbol: FirCallableSymbol<*>? + get() { + return toResolvedCallableSymbol() + ?: (calleeReference as? FirNamedReference)?.candidateSymbol as? FirCallableSymbol<*> + } + +/** + * Referenced [ClassId], even if it is not completely correctly resolved. + */ +private val FirResolvedTypeRef.resolvedClassId: ClassId? + get() { + if (this !is FirErrorTypeRef) return type.classId + + val candidateSymbols = diagnostic.getCandidateSymbols() + val singleClassSymbol = candidateSymbols.singleOrNull() as? FirClassLikeSymbol + + return singleClassSymbol?.classId + } + private val FirQualifiedAccessExpression.isFullyQualified: Boolean get() = explicitReceiver is FirResolvedQualifier +private fun KtExpression.getPossiblyQualifiedSimpleNameExpression(): KtSimpleNameExpression? { + return ((this as? KtQualifiedExpression)?.selectorExpression ?: this) as? KtSimpleNameExpression? +} + /** * Helper abstraction to navigate through qualified FIR elements - we have to match [ClassId] and PSI qualifier pair * to correctly reason about long qualifiers. @@ -292,7 +376,7 @@ private sealed interface TypeQualifier { } fun createFor(typeRef: FirResolvedTypeRef): TypeQualifier? { - val wholeClassId = typeRef.type.classId ?: return null + val wholeClassId = typeRef.resolvedClassId ?: return null val psi = typeRef.psi as? KtTypeReference ?: return null val wholeUserType = psi.typeElement?.unwrapNullability() as? KtUserType ?: return null diff --git a/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedConstructor_invalidArguments.imports b/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedConstructor_invalidArguments.imports new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedConstructor_invalidArguments.kt b/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedConstructor_invalidArguments.kt new file mode 100644 index 00000000000..a3caee5ce8f --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedConstructor_invalidArguments.kt @@ -0,0 +1,13 @@ +// FILE: main.kt +package test + +import dependency.Bar + +fun usage() { + val result = Bar() +} + +// FILE: dependency.kt +package dependency + +class Bar(i: Int) \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedConstructor_missingCall.imports b/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedConstructor_missingCall.imports new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedConstructor_missingCall.kt b/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedConstructor_missingCall.kt new file mode 100644 index 00000000000..afeaca7dfc9 --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedConstructor_missingCall.kt @@ -0,0 +1,13 @@ +// FILE: main.kt +package test + +import dependency.Bar + +fun usage() { + val result = Bar +} + +// FILE: dependency.kt +package dependency + +class Bar \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedExtensionFunction_invalidArguments.imports b/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedExtensionFunction_invalidArguments.imports new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedExtensionFunction_invalidArguments.kt b/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedExtensionFunction_invalidArguments.kt new file mode 100644 index 00000000000..0dc647b914c --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedExtensionFunction_invalidArguments.kt @@ -0,0 +1,16 @@ +// FILE: main.kt +package test + +import dependency.Bar +import dependency.fooExt + +fun usage(b: Bar) { + b.fooExt() +} + +// FILE: dependency.kt +package dependency + +class Bar + +fun Bar.fooExt(i: Int) {} \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedExtensionProperty_invalidReceiver.imports b/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedExtensionProperty_invalidReceiver.imports new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedExtensionProperty_invalidReceiver.kt b/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedExtensionProperty_invalidReceiver.kt new file mode 100644 index 00000000000..225fe959f7b --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedExtensionProperty_invalidReceiver.kt @@ -0,0 +1,16 @@ +// FILE: main.kt +package test + +import dependency.fooExtProp + +fun usage(b: Any) { + b.fooExtProp +} + +// FILE: dependency.kt +package dependency + +class Bar + +val Bar.fooExtProp: Int + get() = 10 \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedInvokeOperator_invalidArguments.imports b/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedInvokeOperator_invalidArguments.imports new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedInvokeOperator_invalidArguments.kt b/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedInvokeOperator_invalidArguments.kt new file mode 100644 index 00000000000..5be2d958a5d --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedInvokeOperator_invalidArguments.kt @@ -0,0 +1,16 @@ +// FILE: main.kt +package test + +import dependency.Bar +import dependency.invoke + +fun usage(b: Bar) { + b() +} + +// FILE: dependency.kt +package dependency + +class Bar + +operator fun Bar.invoke(i: Int) {} \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedTypeImport_missingGeneric.imports b/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedTypeImport_missingGeneric.imports new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedTypeImport_missingGeneric.kt b/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedTypeImport_missingGeneric.kt new file mode 100644 index 00000000000..f34a2093009 --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedTypeImport_missingGeneric.kt @@ -0,0 +1,11 @@ +// FILE: main.kt +package test + +import dependency.Bar + +fun usage(p: Bar) {} + +// FILE: dependency.kt +package dependency + +class Bar \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/components/importOptimizer/usedAliasedFunctionReference.imports b/idea/idea-frontend-fir/testData/components/importOptimizer/usedAliasedFunctionReference.imports new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/idea-frontend-fir/testData/components/importOptimizer/usedAliasedFunctionReference.kt b/idea/idea-frontend-fir/testData/components/importOptimizer/usedAliasedFunctionReference.kt new file mode 100644 index 00000000000..c8d5ebb3fb8 --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/importOptimizer/usedAliasedFunctionReference.kt @@ -0,0 +1,13 @@ +// FILE: main.kt +package test + +import dependency.foo as aliasedFoo + +fun usage() { + val ref = ::aliasedFoo +} + +// FILE: dependency.kt +package dependency + +fun foo() {} \ No newline at end of file diff --git a/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/fir/frontend/api/components/HLImportOptimizerTestGenerated.java b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/fir/frontend/api/components/HLImportOptimizerTestGenerated.java index d124fa291e2..6bdce5f9ba5 100644 --- a/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/fir/frontend/api/components/HLImportOptimizerTestGenerated.java +++ b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/fir/frontend/api/components/HLImportOptimizerTestGenerated.java @@ -42,6 +42,12 @@ public class HLImportOptimizerTestGenerated extends AbstractHLImportOptimizerTes runTest("idea/idea-frontend-fir/testData/components/importOptimizer/unusedInvokeOperatorImport.kt"); } + @Test + @TestMetadata("usedAliasedFunctionReference.kt") + public void testUsedAliasedFunctionReference() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/importOptimizer/usedAliasedFunctionReference.kt"); + } + @Test @TestMetadata("usedAliasedTypeImport.kt") public void testUsedAliasedTypeImport() throws Exception { @@ -71,4 +77,50 @@ public class HLImportOptimizerTestGenerated extends AbstractHLImportOptimizerTes public void testUsedInvokeOperatorImport() throws Exception { runTest("idea/idea-frontend-fir/testData/components/importOptimizer/usedInvokeOperatorImport.kt"); } + + @Nested + @TestMetadata("idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors") + @TestDataPath("$PROJECT_ROOT") + public class ReferencesWithErrors { + @Test + public void testAllFilesPresentInReferencesWithErrors() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors"), Pattern.compile("^([^.]+)\\.kt$"), null, true); + } + + @Test + @TestMetadata("usedConstructor_invalidArguments.kt") + public void testUsedConstructor_invalidArguments() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedConstructor_invalidArguments.kt"); + } + + @Test + @TestMetadata("usedConstructor_missingCall.kt") + public void testUsedConstructor_missingCall() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedConstructor_missingCall.kt"); + } + + @Test + @TestMetadata("usedExtensionFunction_invalidArguments.kt") + public void testUsedExtensionFunction_invalidArguments() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedExtensionFunction_invalidArguments.kt"); + } + + @Test + @TestMetadata("usedExtensionProperty_invalidReceiver.kt") + public void testUsedExtensionProperty_invalidReceiver() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedExtensionProperty_invalidReceiver.kt"); + } + + @Test + @TestMetadata("usedInvokeOperator_invalidArguments.kt") + public void testUsedInvokeOperator_invalidArguments() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedInvokeOperator_invalidArguments.kt"); + } + + @Test + @TestMetadata("usedTypeImport_missingGeneric.kt") + public void testUsedTypeImport_missingGeneric() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/importOptimizer/referencesWithErrors/usedTypeImport_missingGeneric.kt"); + } + } }