[FIR IDE] Make KtFirImportOptimizer more robust to reference errors
This commit is contained in:
committed by
TeamCityServer
parent
cc5382b37e
commit
e34b64d6f2
+94
-10
@@ -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
|
||||
|
||||
+13
@@ -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)
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// FILE: main.kt
|
||||
package test
|
||||
|
||||
import dependency.Bar
|
||||
|
||||
fun usage() {
|
||||
val result = Bar
|
||||
}
|
||||
|
||||
// FILE: dependency.kt
|
||||
package dependency
|
||||
|
||||
class Bar
|
||||
+16
@@ -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) {}
|
||||
+16
@@ -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
|
||||
+16
@@ -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) {}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// FILE: main.kt
|
||||
package test
|
||||
|
||||
import dependency.Bar
|
||||
|
||||
fun usage(p: Bar) {}
|
||||
|
||||
// FILE: dependency.kt
|
||||
package dependency
|
||||
|
||||
class Bar<T>
|
||||
Vendored
+13
@@ -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() {}
|
||||
+52
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user