diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/substitution/Substitutors.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/substitution/Substitutors.kt index e0198d1087f..5aeb125c6f4 100644 --- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/substitution/Substitutors.kt +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/substitution/Substitutors.kt @@ -94,11 +94,23 @@ abstract class AbstractConeSubstitutor(protected val typeContext: ConeTypeContex private fun ConeCapturedType.substituteCapturedType(): ConeCapturedType? { val innerType = this.lowerType ?: this.constructor.projection.type + // TODO(KT-64024): This early return looks suspicious. + // In fact, if the inner type wasn't substituted we will ignore potential substitution in + // super types val substitutedInnerType = substituteOrNull(innerType) ?: return null if (substitutedInnerType is ConeCapturedType) return substitutedInnerType val substitutedSuperTypes = this.constructor.supertypes?.map { substituteOrSelf(it) } + // TODO(KT-64027): Creation of new captured types creates unexpected behavior by breaking substitution consistency. + // E.g: + // ``` + // substitution = { A => B } + // substituteOrSelf(C) -> C + // substituteOrSelf(C) -> C + // C C + // ``` + return ConeCapturedType( captureStatus, constructor = ConeCapturedTypeConstructor( diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/FirBuilderInferenceSession.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/FirBuilderInferenceSession.kt index acd8ece6195..25080336122 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/FirBuilderInferenceSession.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/FirBuilderInferenceSession.kt @@ -11,8 +11,6 @@ import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction import org.jetbrains.kotlin.fir.declarations.FirDeclaration import org.jetbrains.kotlin.fir.declarations.hasAnnotation -import org.jetbrains.kotlin.fir.expressions.FirResolvable -import org.jetbrains.kotlin.fir.expressions.FirStatement import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.languageVersionSettings import org.jetbrains.kotlin.fir.resolve.calls.Candidate @@ -20,6 +18,7 @@ import org.jetbrains.kotlin.fir.resolve.calls.ImplicitExtensionReceiverValue import org.jetbrains.kotlin.fir.resolve.calls.ResolutionContext import org.jetbrains.kotlin.fir.resolve.calls.candidate import org.jetbrains.kotlin.fir.resolve.inference.model.ConeBuilderInferenceSubstitutionConstraintPosition +import org.jetbrains.kotlin.fir.resolve.substitution.AbstractConeSubstitutor import org.jetbrains.kotlin.fir.resolve.substitution.ChainedSubstitutor import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor import org.jetbrains.kotlin.fir.resolve.substitution.replaceStubsAndTypeVariablesToErrors @@ -230,6 +229,23 @@ class FirBuilderInferenceSession( return introducedConstraint } + private fun extractCommonCapturedTypes(lower: ConeKotlinType, upper: ConeKotlinType): List { + val extractedCapturedTypes = mutableSetOf().also { extractCapturedTypesTo(lower, it) } + return extractedCapturedTypes.filter { capturedType -> + upper.contains { it is ConeCapturedType && it.constructor === capturedType.constructor } + } + } + + private fun extractCapturedTypesTo(type: ConeKotlinType, to: MutableSet) { + if (type is ConeCapturedType) { + to.add(type) + } + for (typeArgument in type.typeArguments) { + if (typeArgument !is ConeKotlinTypeProjection) continue + extractCapturedTypesTo(typeArgument.type, to) + } + } + private fun getResultingSubstitutor(commonSystem: NewConstraintSystemImpl): ConeSubstitutor { val nonFixedToVariablesSubstitutor = createNonFixedTypeToVariableSubstitutor() val commonSystemSubstitutor = commonSystem.buildCurrentSubstitutor() as ConeSubstitutor @@ -297,12 +313,44 @@ class FirBuilderInferenceSession( } private fun InitialConstraint.substitute( - substitutor: TypeSubstitutorMarker, + substitutor: ConeSubstitutor, fixedTypeVariables: Map ): InitialConstraint { + require(constraintKind != ConstraintKind.LOWER) + + // TODO(KT-63996, KT-64027): This function assumes types passed in lower, upper order which isn't true for equality constraints + val commonCapTypes = extractCommonCapturedTypes(lower = a as ConeKotlinType, upper = b as ConeKotlinType) + + // TODO(KT-64027): This logic tries to work-around the problem with type substitution consistency in captured types + // In order to preserve consistency we collect captured types from both a and b and substitute them collectively + // E.g: + // substitutor = { B => W } + // a = C, b = D + // commonCapTypes = [CapturedType(out B)_0] + // capTypesSubstitutor = { CapturedTypeConstructor_0 => CapturedType(out W)_1 } + // substitutedLowerType = C + // substitutedUpperType = D + val substitutedCommonCapType = commonCapTypes.associate { + it.constructor to substitutor.substituteOrSelf(it) + } + + val capTypesSubstitutor = object : AbstractConeSubstitutor(resolutionContext.typeContext) { + override fun substituteType(type: ConeKotlinType): ConeKotlinType? { + if (type !is ConeCapturedType) return null + return substitutedCommonCapType[type.constructor] + } + } + // TODO: invalid naming. It doesn't make sense case of equality constraints - val substitutedLowerType = substitutor.safeSubstitute(resolutionContext.typeContext, this.a) - val substitutedUpperType = substitutor.safeSubstitute(resolutionContext.typeContext, this.b) + val substitutedLowerType = substitutor.safeSubstitute( + resolutionContext.typeContext, + capTypesSubstitutor.safeSubstitute(resolutionContext.typeContext, this.a) + ) + val substitutedUpperType = substitutor.safeSubstitute( + resolutionContext.typeContext, + capTypesSubstitutor.safeSubstitute(resolutionContext.typeContext, this.b) + ) + val a = a // In situation when some type variable _T is fixed to Stub(_T)?, diff --git a/compiler/testData/diagnostics/tests/inference/builderInference/stubTypes/capturedTypes.fir.kt b/compiler/testData/diagnostics/tests/inference/builderInference/stubTypes/capturedTypes.fir.kt index 8c5bef933d3..2f277f0f465 100644 --- a/compiler/testData/diagnostics/tests/inference/builderInference/stubTypes/capturedTypes.fir.kt +++ b/compiler/testData/diagnostics/tests/inference/builderInference/stubTypes/capturedTypes.fir.kt @@ -26,7 +26,7 @@ fun captureIn(x: Inv): K = null as K fun capture(x: Inv): K = null as K fun main() { - build { + build { emit("") getInv() captureOut(getInv()) @@ -35,7 +35,7 @@ fun main() { // K is fixed into CapturedType(out NotFixed: TypeVariable(R)) capture(getOut()) "" - } + } build { emit("") // K is fixed into CapturedType(in NotFixed: TypeVariable(R))