FIR LL: fix KtDeclarationAndFirDeclarationEqualityChecker

1. Nullable function type is not rendered correctly.
2. Bounds of type parameters are not considered during comparison, this
   would incorrectly treat different declarations as the same, for
   example
   ```
   fun <C: List<String>> C.foo() {}
   fun <C: Set<String>> C.foo() {}
   ```
This commit is contained in:
Tianyu Geng
2021-10-18 14:53:35 -07:00
committed by Ilya Kirillov
parent f23256bf49
commit 8bec5cec61
@@ -20,11 +20,13 @@ import org.jetbrains.kotlin.fir.scopes.FirContainingNamesAwareScope
import org.jetbrains.kotlin.fir.scopes.FirScopeProvider import org.jetbrains.kotlin.fir.scopes.FirScopeProvider
import org.jetbrains.kotlin.fir.scopes.FirTypeScope import org.jetbrains.kotlin.fir.scopes.FirTypeScope
import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.impl.FirImplicitNullableAnyTypeRef
import org.jetbrains.kotlin.name.StandardClassIds import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.psi.KtCallableDeclaration import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtFunction import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtTypeReference import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.types.Variance import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.utils.addIfNotNull
// TODO replace with structural type comparison? // TODO replace with structural type comparison?
object KtDeclarationAndFirDeclarationEqualityChecker { object KtDeclarationAndFirDeclarationEqualityChecker {
@@ -40,15 +42,28 @@ object KtDeclarationAndFirDeclarationEqualityChecker {
return false return false
} }
if (fir is FirFunction) { if (fir is FirFunction) {
if (fir.valueParameters.size != psi.valueParameters.size) return false if (!typeParametersMatch(fir, psi) || !valueParametersMatch(fir, psi)) return false
fir.valueParameters.zip(psi.valueParameters) { expectedParameter, candidateParameter -> }
if (expectedParameter.name.toString() != candidateParameter.name) return false return true
if (expectedParameter.isVararg != candidateParameter.isVarArg) return false }
val candidateParameterType = candidateParameter.typeReference ?: return false
private fun typeParametersMatch(firFunction: FirCallableDeclaration, psiFunction: KtCallableDeclaration): Boolean {
if (firFunction.typeParameters.size != psiFunction.typeParameters.size) return false
val boundsByName = psiFunction.typeConstraints.groupBy { it.subjectTypeParameterName?.getReferencedName() }
firFunction.typeParameters.zip(psiFunction.typeParameters) { expectedTypeParameter, candidateTypeParameter ->
if (expectedTypeParameter.symbol.name.toString() != candidateTypeParameter.name) return false
val candidateBounds = mutableListOf<KtTypeReference>()
candidateBounds.addIfNotNull(candidateTypeParameter.extendsBound)
boundsByName[candidateTypeParameter.name]?.forEach {
candidateBounds.addIfNotNull(it.boundTypeReference)
}
val expectedBounds = expectedTypeParameter.symbol.resolvedBounds.filter { it !is FirImplicitNullableAnyTypeRef }
if (candidateBounds.size != expectedBounds.size) return false
expectedBounds.zip(candidateBounds) { expectedBound, candidateBound ->
if (!isTheSameTypes( if (!isTheSameTypes(
candidateParameterType, candidateBound,
expectedParameter.returnTypeRef, expectedBound,
isVararg = expectedParameter.isVararg isVararg = false
) )
) { ) {
return false return false
@@ -58,6 +73,24 @@ object KtDeclarationAndFirDeclarationEqualityChecker {
return true return true
} }
private fun valueParametersMatch(firFunction: FirFunction, psiFunction: KtCallableDeclaration): Boolean {
if (firFunction.valueParameters.size != psiFunction.valueParameters.size) return false
firFunction.valueParameters.zip(psiFunction.valueParameters) { expectedParameter, candidateParameter ->
if (expectedParameter.name.toString() != candidateParameter.name) return false
if (expectedParameter.isVararg != candidateParameter.isVarArg) return false
val candidateParameterType = candidateParameter.typeReference ?: return false
if (!isTheSameTypes(
candidateParameterType,
expectedParameter.returnTypeRef,
isVararg = expectedParameter.isVararg
)
) {
return false
}
}
return true
}
@OptIn(ExperimentalStdlibApi::class) @OptIn(ExperimentalStdlibApi::class)
private fun FirTypeRef.renderTypeAsKotlinType(isVararg: Boolean = false): String { private fun FirTypeRef.renderTypeAsKotlinType(isVararg: Boolean = false): String {
val rendered = when (this) { val rendered = when (this) {
@@ -89,6 +122,9 @@ object KtDeclarationAndFirDeclarationEqualityChecker {
if (parameters.isNotEmpty()) { if (parameters.isNotEmpty()) {
append(parameters.joinToString(prefix = "<", postfix = ">") { it.renderTypeAsKotlinType() }) append(parameters.joinToString(prefix = "<", postfix = ">") { it.renderTypeAsKotlinType() })
} }
if (isMarkedNullable) {
append("?")
}
} }
} }
else -> error("Invalid type reference $this") else -> error("Invalid type reference $this")