K2: Fix false-positive error with captured types in builder inference

Error arises from the fact that type substitution operation isn't
consistent when applied to captured types.

E.g.:
```
substitution = { A => B }
substituteOrSelf(C<CapturedType(out A)_0>) -> C<CapturedType(out B)_1>
substituteOrSelf(C<CapturedType(out A)_0>) -> C<CapturedType(out B)_2>
C<CapturedType(out B)_1> <!:> C<CapturedType(out B)_2>
```

Relates to KT-53749
This commit is contained in:
Simon Ogorodnik
2023-12-01 15:58:44 +01:00
committed by Space Team
parent d46075a9db
commit c2ddbfc93b
3 changed files with 67 additions and 7 deletions
@@ -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<CapturedType(out A)_0>) -> C<CapturedType(out B)_1>
// substituteOrSelf(C<CapturedType(out A)_0>) -> C<CapturedType(out B)_2>
// C<CapturedType(out B)_1> <!:> C<CapturedType(out B)_2>
// ```
return ConeCapturedType(
captureStatus,
constructor = ConeCapturedTypeConstructor(
@@ -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<ConeCapturedType> {
val extractedCapturedTypes = mutableSetOf<ConeCapturedType>().also { extractCapturedTypesTo(lower, it) }
return extractedCapturedTypes.filter { capturedType ->
upper.contains { it is ConeCapturedType && it.constructor === capturedType.constructor }
}
}
private fun extractCapturedTypesTo(type: ConeKotlinType, to: MutableSet<ConeCapturedType>) {
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<TypeConstructorMarker, KotlinTypeMarker>
): 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<CapturedType(out B)_0>, b = D<CapturedType(out B)_0>
// commonCapTypes = [CapturedType(out B)_0]
// capTypesSubstitutor = { CapturedTypeConstructor_0 => CapturedType(out W)_1 }
// substitutedLowerType = C<CapturedType(out W)_1>
// substitutedUpperType = D<CapturedType(out W)_1>
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)?,
@@ -26,7 +26,7 @@ fun <K> captureIn(x: Inv<out K>): K = null as K
fun <K> capture(x: Inv<K>): K = null as K
fun main() {
<!NEW_INFERENCE_ERROR!>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))