From bda500c5658cd2a990470d4b1a17c2d1fe8ca30b Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Tue, 29 Jul 2025 17:39:52 +0200 Subject: [PATCH] [kotlin] k2 matcher: support deep types match ^KTIJ-35069 fixed GitOrigin-RevId: e74c76b487e051f9a476a9e95b2f3c34b3c5d1f1 --- .../unifier/equivalence/types/function1.kt | 1 - .../unifier/equivalence/types/function2.kt | 1 - .../introduce/K2SemanticMatcher.kt | 93 +++++++++++++++++-- 3 files changed, 83 insertions(+), 12 deletions(-) diff --git a/plugins/kotlin/idea/tests/testData/unifier/equivalence/types/function1.kt b/plugins/kotlin/idea/tests/testData/unifier/equivalence/types/function1.kt index 9f8e29ebffa9..80c3db320b57 100644 --- a/plugins/kotlin/idea/tests/testData/unifier/equivalence/types/function1.kt +++ b/plugins/kotlin/idea/tests/testData/unifier/equivalence/types/function1.kt @@ -1,4 +1,3 @@ -// IGNORE_K2 val a: Function1 = { "" } val b: kotlin.Function1 = { "" } val c: (Int) -> String = { "" } diff --git a/plugins/kotlin/idea/tests/testData/unifier/equivalence/types/function2.kt b/plugins/kotlin/idea/tests/testData/unifier/equivalence/types/function2.kt index bf0c7bef603f..2aaf150181bc 100644 --- a/plugins/kotlin/idea/tests/testData/unifier/equivalence/types/function2.kt +++ b/plugins/kotlin/idea/tests/testData/unifier/equivalence/types/function2.kt @@ -1,4 +1,3 @@ -// IGNORE_K2 val a: Function2 = { a, b -> "" } val b: kotlin.Function2 = { a, b -> "" } val c: (Any, Int) -> String = { a, b -> "" } diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/K2SemanticMatcher.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/K2SemanticMatcher.kt index cfdbbe52f45a..07078c5153f8 100644 --- a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/K2SemanticMatcher.kt +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/K2SemanticMatcher.kt @@ -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` and `List`, 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