From b6d7f35ebf7b25f54243a90a094e8864165df789 Mon Sep 17 00:00:00 2001 From: Kirill Rakhman Date: Wed, 20 Dec 2023 11:03:39 +0100 Subject: [PATCH] [FIR] Implement capturing of captured types This is in preparation of a future commit, where captured types won't be approximated anymore after completion. Consider a case like class Box(val value: T) interface Foo { fun bar() } fun test(x: Box>) { x.value.bar() } The type of `x.value` will be `CapturedType(out Foo<*>)`. Note that capturing only applies to the top level, i.e., nested projections are not captured. That's why it becomes necessary to support capturing of captured types, otherwise the star projection in `CapturedType(out Foo<*>)` is not properly captured in the receiver of the call `x.value.bar()`. #KT-62959 --- .../checkers/FirUpperBoundViolatedHelpers.kt | 1 + .../fir/resolve/substitution/Substitutors.kt | 84 +++++++++---------- .../jetbrains/kotlin/fir/types/TypeUtils.kt | 9 ++ 3 files changed, 52 insertions(+), 42 deletions(-) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirUpperBoundViolatedHelpers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirUpperBoundViolatedHelpers.kt index 723c041954e..ac70324fb00 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirUpperBoundViolatedHelpers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirUpperBoundViolatedHelpers.kt @@ -17,6 +17,7 @@ import org.jetbrains.kotlin.fir.resolve.fullyExpandedType import org.jetbrains.kotlin.fir.resolve.substitution.AbstractConeSubstitutor import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor import org.jetbrains.kotlin.fir.resolve.substitution.substitutorByMap +import org.jetbrains.kotlin.fir.resolve.substitution.wrapProjection import org.jetbrains.kotlin.fir.resolve.toSymbol import org.jetbrains.kotlin.fir.resolve.withCombinedAttributesFrom import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol 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 4be45cbabc7..de720db1cd7 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 @@ -24,18 +24,48 @@ import org.jetbrains.kotlin.types.model.typeConstructor import org.jetbrains.kotlin.utils.addToStdlib.runIf import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment -abstract class AbstractConeSubstitutor(protected val typeContext: ConeTypeContext) : ConeSubstitutor() { - protected fun wrapProjection(old: ConeTypeProjection, newType: ConeKotlinType): ConeTypeProjection { - return when (old) { - is ConeStarProjection -> old - is ConeKotlinTypeProjectionIn -> ConeKotlinTypeProjectionIn(newType) - is ConeKotlinTypeProjectionOut -> ConeKotlinTypeProjectionOut(newType) - is ConeKotlinTypeConflictingProjection -> ConeKotlinTypeConflictingProjection(newType) - is ConeKotlinType -> newType - else -> old - } - } +inline fun ConeCapturedType.substitute(f: (ConeKotlinType) -> ConeKotlinType?): 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 = innerType?.let(f) ?: return null + if (substitutedInnerType is ConeCapturedType) return substitutedInnerType + val substitutedSuperTypes = + this.constructor.supertypes?.map { f(it) ?: 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( + wrapProjection(constructor.projection, substitutedInnerType), + substitutedSuperTypes, + typeParameterMarker = constructor.typeParameterMarker + ), + lowerType = if (lowerType != null) substitutedInnerType else null + ) +} + +fun wrapProjection(old: ConeTypeProjection, newType: ConeKotlinType): ConeTypeProjection { + return when (old) { + is ConeStarProjection -> old + is ConeKotlinTypeProjectionIn -> ConeKotlinTypeProjectionIn(newType) + is ConeKotlinTypeProjectionOut -> ConeKotlinTypeProjectionOut(newType) + is ConeKotlinTypeConflictingProjection -> ConeKotlinTypeConflictingProjection(newType) + is ConeKotlinType -> newType + else -> old + } +} + +abstract class AbstractConeSubstitutor(protected val typeContext: ConeTypeContext) : ConeSubstitutor() { abstract fun substituteType(type: ConeKotlinType): ConeKotlinType? open fun substituteArgument(projection: ConeTypeProjection, index: Int): ConeTypeProjection? { val type = (projection as? ConeKotlinTypeProjection)?.type ?: return null @@ -84,7 +114,7 @@ abstract class AbstractConeSubstitutor(protected val typeContext: ConeTypeContex if (it.lowerBound == it.upperBound) it.lowerBound else it } - is ConeCapturedType -> return substituteCapturedType() + is ConeCapturedType -> return substitute(::substituteOrNull) is ConeDefinitelyNotNullType -> this.substituteOriginal() is ConeIntersectionType -> this.substituteIntersectedTypes() is ConeStubType -> return null @@ -92,36 +122,6 @@ 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( - wrapProjection(constructor.projection, substitutedInnerType), - substitutedSuperTypes, - typeParameterMarker = constructor.typeParameterMarker - ), - lowerType = if (lowerType != null) substitutedInnerType else null - ) - } - private fun ConeIntersectionType.substituteIntersectedTypes(): ConeIntersectionType? { val substitutedTypes = ArrayList(intersectedTypes.size) var somethingIsSubstituted = false diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/TypeUtils.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/TypeUtils.kt index 34f4bfe12b5..f145dc613d9 100644 --- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/TypeUtils.kt +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/TypeUtils.kt @@ -19,6 +19,7 @@ import org.jetbrains.kotlin.fir.declarations.utils.modality import org.jetbrains.kotlin.fir.declarations.utils.visibility import org.jetbrains.kotlin.fir.diagnostics.ConeRecursiveTypeParameterDuringErasureError import org.jetbrains.kotlin.fir.resolve.fullyExpandedType +import org.jetbrains.kotlin.fir.resolve.substitution.substitute import org.jetbrains.kotlin.fir.resolve.substitution.substitutorByMap import org.jetbrains.kotlin.fir.resolve.toSymbol import org.jetbrains.kotlin.fir.resolvedTypeFromPrototype @@ -431,6 +432,14 @@ fun ConeTypeContext.captureArguments(type: ConeKotlinType, status: CaptureStatus } internal fun ConeTypeContext.captureFromExpressionInternal(type: ConeKotlinType): ConeKotlinType? { + if (type is ConeCapturedType) { + return type.substitute { captureFromExpressionInternal(it) } + } + + if (type is ConeDefinitelyNotNullType) { + return captureFromExpressionInternal(type.original)?.makeConeTypeDefinitelyNotNullOrNotNull(this) + } + if (type !is ConeIntersectionType && type !is ConeFlexibleType) { return captureFromArgumentsInternal(type, CaptureStatus.FROM_EXPRESSION) }