[kotlin] k2 matcher: support deep types match

^KTIJ-35069 fixed

GitOrigin-RevId: e74c76b487e051f9a476a9e95b2f3c34b3c5d1f1
This commit is contained in:
Anna Kozlova
2025-07-29 20:33:41 +00:00
committed by intellij-monorepo-bot
parent 7908519bf6
commit bda500c565
3 changed files with 83 additions and 12 deletions
@@ -1,4 +1,3 @@
// IGNORE_K2
val a: Function1<Int, String> = { "" }
val b: kotlin.Function1<kotlin.Int, kotlin.String> = { "" }
val c: <selection>(Int) -> String</selection> = { "" }
@@ -1,4 +1,3 @@
// IGNORE_K2
val a: Function2<Any, Int, String> = { a, b -> "" }
val b: kotlin.Function2<kotlin.Any, kotlin.Int, kotlin.String> = { a, b -> "" }
val c: <selection>(Any, Int) -> String</selection> = { a, b -> "" }
@@ -10,12 +10,15 @@ import org.jetbrains.kotlin.analysis.api.KaSession
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.base.KaConstantValue
import org.jetbrains.kotlin.analysis.api.fir.diagnostics.KaFirDiagnostic
import org.jetbrains.kotlin.analysis.api.impl.base.components.KaBaseIllegalPsiException
import org.jetbrains.kotlin.analysis.api.resolution.*
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.analysis.api.symbols.markers.KaNamedSymbol
import org.jetbrains.kotlin.analysis.api.types.KaClassType
import org.jetbrains.kotlin.analysis.api.types.KaFunctionType
import org.jetbrains.kotlin.analysis.api.types.KaType
import org.jetbrains.kotlin.analysis.api.types.KaTypeArgumentWithVariance
import org.jetbrains.kotlin.analysis.api.types.KaTypeParameterType
import org.jetbrains.kotlin.analysis.api.types.symbol
import org.jetbrains.kotlin.idea.base.analysis.api.utils.CallParameterInfoProvider.getArgumentOrIndexExpressions
import org.jetbrains.kotlin.idea.base.analysis.api.utils.CallParameterInfoProvider.mapArgumentsToParameterIndices
import org.jetbrains.kotlin.idea.base.psi.isInsideKtTypeReference
@@ -257,15 +260,73 @@ object K2SemanticMatcher {
fun areBlockBodyOwnersEqualOrAssociated(targetFunction: KaFunctionSymbol, patternFunction: KaFunctionSymbol): Boolean =
targetFunction == patternFunction || blockBodyOwners[targetFunction] == patternFunction
// TODO: current approach doesn't work on pairs of types such as `List<U>` and `List<T>`, where `U` and `T` are associated
context(KaSession)
fun areTypesEqualOrAssociated(targetType: KaType?, patternType: KaType?): Boolean {
fun areTypesEqualOrAssociated(
targetType: KaType?,
patternType: KaType?,
targetTypeReference: KtTypeReference? = null,
patternTypeReference: KtTypeReference? = null
): Boolean {
if (targetType == null || patternType == null) return targetType == null && patternType == null
return targetType.semanticallyEquals(patternType) ||
targetType is KaTypeParameterType &&
patternType is KaTypeParameterType &&
symbols[targetType.symbol] == patternType.symbol
if (targetType.isMarkedNullable != patternType.isMarkedNullable) return false
if ((targetType is KaFunctionType && targetType.hasReceiver) != (patternType is KaFunctionType && patternType.hasReceiver)) return false
if ((targetType.abbreviation != null) != (patternType.abbreviation != null)) return false
if (targetType.semanticallyEquals(patternType)) return true
if (targetType is KaTypeParameterType && patternType is KaTypeParameterType)
return symbols[targetType.symbol] == patternType.symbol
fun typeParameterSubstitution(targetType: KaTypeParameterType, patternType: KaType, typeReference: KtTypeReference?): Boolean {
val typeParameter = targetType.symbol.psi as KtNamedDeclaration
val subst = parameterSubstitution[typeParameter]
return when {
subst != null -> {
subst is KtTypeReference && subst.type.semanticallyEquals(patternType)
}
parameterSubstitution.containsKey(typeParameter) -> {
parameterSubstitution[typeParameter] = typeReference
true
}
else -> false
}
}
if (targetType is KaTypeParameterType) {
return typeParameterSubstitution(targetType, patternType, patternTypeReference)
}
if (patternType is KaTypeParameterType) {
return typeParameterSubstitution(patternType, targetType, targetTypeReference)
}
if (targetType.symbol != patternType.symbol) return false
val targetClassType = targetType as? KaClassType ?: return false
val patternClassType = patternType as? KaClassType ?: return false
val targetTypeArguments = targetClassType.typeArguments
val patternTypeArguments = patternClassType.typeArguments
if (targetTypeArguments.size != patternTypeArguments.size) return false
targetTypeArguments.zip(patternTypeArguments).forEachIndexed { argIndex, pair ->
val (targetTypeArgument, patternTypeArgument) = pair
if (targetTypeArgument::class != patternTypeArgument::class) return false
if (targetTypeArgument is KaTypeArgumentWithVariance &&
patternTypeArgument is KaTypeArgumentWithVariance &&
targetTypeArgument.variance != patternTypeArgument.variance) {
return false
}
val targetArgumentReference = targetTypeReference?.typeElement?.typeArgumentsAsTypes?.getOrNull(argIndex)
val patternArgumentReference = patternTypeReference?.typeElement?.typeArgumentsAsTypes?.getOrNull(argIndex)
if (!areTypesEqualOrAssociated(targetTypeArgument.type, patternTypeArgument.type, targetArgumentReference, patternArgumentReference)) {
return false
}
}
return true
}
context(KaSession)
@@ -877,7 +938,12 @@ object K2SemanticMatcher {
targetDeclaration: KtCallableDeclaration,
patternDeclaration: KtCallableDeclaration,
context: MatchingContext,
): Boolean = context.areTypesEqualOrAssociated(targetDeclaration.returnType, patternDeclaration.returnType)
): Boolean = context.areTypesEqualOrAssociated(
targetDeclaration.returnType,
patternDeclaration.returnType,
targetDeclaration.typeReference,
patternDeclaration.typeReference
)
context(KaSession)
private fun areReceiverParametersMatchingByResolve(
@@ -887,6 +953,8 @@ object K2SemanticMatcher {
): Boolean = context.areTypesEqualOrAssociated(
targetDeclaration.getCallableSymbol().receiverType,
patternDeclaration.getCallableSymbol().receiverType,
targetDeclaration.receiverTypeReference,
patternDeclaration.receiverTypeReference,
)
context(KaSession)
@@ -899,7 +967,12 @@ object K2SemanticMatcher {
val patternParameters = patternFunction.getFunctionLikeSymbol().valueParameters
if (targetParameters.size > 1 || patternParameters.size > 1) return false
return context.areTypesEqualOrAssociated(targetParameters.singleOrNull()?.returnType, patternParameters.singleOrNull()?.returnType)
return context.areTypesEqualOrAssociated(
targetParameters.singleOrNull()?.returnType,
patternParameters.singleOrNull()?.returnType,
targetFunction.valueParameters.singleOrNull()?.typeReference,
patternFunction.valueParameters.singleOrNull()?.typeReference
)
}
context(KaSession)
@@ -924,7 +997,7 @@ object K2SemanticMatcher {
targetTypeReference: KtTypeReference,
patternTypeReference: KtTypeReference,
context: MatchingContext
): Boolean = context.areTypesEqualOrAssociated(targetTypeReference.type, patternTypeReference.type)
): Boolean = context.areTypesEqualOrAssociated(targetTypeReference.type, patternTypeReference.type, targetTypeReference, patternTypeReference)
context(KaSession)
private fun KtFunction.getFunctionLikeSymbol(): KaFunctionSymbol = symbol as KaFunctionSymbol