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.FirTypeScope
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.impl.FirImplicitNullableAnyTypeRef
import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.utils.addIfNotNull
// TODO replace with structural type comparison?
object KtDeclarationAndFirDeclarationEqualityChecker {
@@ -40,15 +42,28 @@ object KtDeclarationAndFirDeclarationEqualityChecker {
return false
}
if (fir is FirFunction) {
if (fir.valueParameters.size != psi.valueParameters.size) return false
fir.valueParameters.zip(psi.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 (!typeParametersMatch(fir, psi) || !valueParametersMatch(fir, psi)) return false
}
return true
}
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(
candidateParameterType,
expectedParameter.returnTypeRef,
isVararg = expectedParameter.isVararg
candidateBound,
expectedBound,
isVararg = false
)
) {
return false
@@ -58,6 +73,24 @@ object KtDeclarationAndFirDeclarationEqualityChecker {
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)
private fun FirTypeRef.renderTypeAsKotlinType(isVararg: Boolean = false): String {
val rendered = when (this) {
@@ -89,6 +122,9 @@ object KtDeclarationAndFirDeclarationEqualityChecker {
if (parameters.isNotEmpty()) {
append(parameters.joinToString(prefix = "<", postfix = ">") { it.renderTypeAsKotlinType() })
}
if (isMarkedNullable) {
append("?")
}
}
}
else -> error("Invalid type reference $this")