FIR2IR: Repeat the K1 behavior: SAM conversion with 'in' projection

It would be more consistently to prohibit the behavior from the unmuted
test (see KT-52428), but it was decided to postpone the breaking change.

Unfortunately, it didn't work to make a test where for computing
star projections we would need to substitute other type parameters
because effectively, it's not allowed to have SAM conversion when
star projections/wildcard is based on a type parameter which bounds
use other type parameters.

^KT-53552 In progress
This commit is contained in:
Denis.Zharkov
2023-02-15 11:54:17 +01:00
committed by Space Team
parent 45eefab811
commit 05ca001310
22 changed files with 158 additions and 53 deletions
@@ -101,27 +101,17 @@ internal enum class ConversionTypeOrigin {
SETTER
}
class ConversionTypeContext internal constructor(
internal val invariantProjection: Boolean = false,
internal val origin: ConversionTypeOrigin = ConversionTypeOrigin.DEFAULT,
) {
class ConversionTypeContext internal constructor(internal val origin: ConversionTypeOrigin) {
fun inSetter() = ConversionTypeContext(
invariantProjection = invariantProjection,
origin = ConversionTypeOrigin.SETTER
)
fun withInvariantProjections() = ConversionTypeContext(
invariantProjection = true,
origin = origin
)
companion object {
internal val DEFAULT = ConversionTypeContext(
invariantProjection = false, origin = ConversionTypeOrigin.DEFAULT
origin = ConversionTypeOrigin.DEFAULT
)
internal val WITH_INVARIANT = DEFAULT.withInvariantProjections()
internal val IN_SETTER = ConversionTypeContext(
invariantProjection = false, origin = ConversionTypeOrigin.SETTER
origin = ConversionTypeOrigin.SETTER
)
}
}
@@ -229,7 +229,7 @@ class Fir2IrTypeConverter(
private fun ConeTypeProjection.toIrTypeArgument(typeContext: ConversionTypeContext): IrTypeArgument {
fun toIrTypeArgument(type: ConeKotlinType, variance: Variance): IrTypeProjection {
val irType = type.toIrType(typeContext)
return makeTypeProjection(irType, if (typeContext.invariantProjection) Variance.INVARIANT else variance)
return makeTypeProjection(irType, variance)
}
return when (this) {
@@ -26,7 +26,9 @@ import org.jetbrains.kotlin.fir.resolve.substitution.AbstractConeSubstitutor
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.resolve.transformers.ReturnTypeCalculatorForFullBodyResolve
import org.jetbrains.kotlin.fir.symbols.ConeTypeParameterLookupTag
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.ir.builders.declarations.UNDEFINED_PARAMETER_INDEX
import org.jetbrains.kotlin.ir.declarations.*
@@ -44,6 +46,7 @@ import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.types.model.TypeVariance
/**
* A generator that converts callable references or arguments that needs an adapter in between. This covers:
@@ -428,17 +431,81 @@ internal class AdapterGenerator(
return this
}
val parameterType = parameter.returnTypeRef.coneType
val substitutedParameterType = starProjectionApproximator.substituteOrSelf(substitutor.substituteOrSelf(parameterType))
val samFirType = if (substitutedParameterType is ConeRawType) substitutedParameterType.lowerBound else substitutedParameterType
var samType = samFirType.toIrType(ConversionTypeContext.WITH_INVARIANT)
if (shouldUnwrapVarargType) {
samType = samType.getArrayElementType(irBuiltIns)
}
val substitutedParameterType =
starProjectionApproximator.substituteOrSelf(substitutor.substituteOrSelf(parameterType)).let {
if (shouldUnwrapVarargType)
it.arrayElementType() ?: it
else
it
}
val samFirType = substitutedParameterType.removeExternalProjections() ?: substitutedParameterType
val samType = samFirType.toIrType(ConversionTypeContext.DEFAULT)
// Make sure the converted IrType owner indeed has a single abstract method, since FunctionReferenceLowering relies on it.
if (!samType.isSamType) return this
return IrTypeOperatorCallImpl(this.startOffset, this.endOffset, samType, IrTypeOperator.SAM_CONVERSION, samType, this)
}
// This function is mostly a mirror of org.jetbrains.kotlin.backend.common.SamTypeApproximator.removeExternalProjections
// First attempts, to share the code between K1 and K2 via type contexts stumbled upon the absence of star-projection-type in K2
// and the possibility of incorrectly mapped details that might break some code when using K1.
private fun ConeKotlinType.removeExternalProjections(): ConeKotlinType? =
when (this) {
is ConeSimpleKotlinType -> removeExternalProjections()
is ConeFlexibleType -> ConeFlexibleType(
lowerBound.removeExternalProjections() ?: lowerBound,
upperBound.removeExternalProjections() ?: upperBound,
)
}
private fun ConeSimpleKotlinType.removeExternalProjections(): ConeSimpleKotlinType? =
with(session.typeContext) {
val typeConstructor = typeConstructor()
val parameters = typeConstructor.getParameters()
val parameterSet = parameters.toSet()
@Suppress("UNCHECKED_CAST")
val newArguments = getArguments().mapIndexed { i, argument ->
val parameter = parameters.getOrNull(i) ?: return null
when {
argument.getVariance() == TypeVariance.IN -> {
// Just erasing `in` from the type projection would lead to an incorrect type for the SAM adapter,
// and error at runtime on JVM if invokedynamic + LambdaMetafactory is used, see KT-51868.
// So we do it "carefully". If we have a class `A<T>` and a method that takes e.g. `A<in String>`, we check
// if `T` has a non-trivial upper bound. If it has one, we don't attempt to perform a SAM conversion at all.
// Otherwise we erase the type to `Any?`, so `A<in String>` becomes `A<Any?>`, which is the computed SAM type.
val upperBound = parameter.getUpperBounds().singleOrNull()?.upperBoundIfFlexible() ?: return null
if (!upperBound.isNullableAny()) return null
upperBound
}
!argument.isStarProjection() -> argument.getType()
else -> parameter.typeParameterSymbol.starProjectionTypeRepresentation(parameterSet)
}
} as List<ConeTypeProjection>
withArguments(newArguments.toTypedArray())
}
// See the definition from K1 at org.jetbrains.kotlin.types.StarProjectionImpl.get_type
// In K1, it's used more frequently because of not-nullable TypeProjection::getType, but in K2 we almost got rid of it
// But here, we still need it to more-or-less fully reproduce the semantics of K1 when generating SAM conversions
private fun FirTypeParameterSymbol.starProjectionTypeRepresentation(containingParameterSet: Set<ConeTypeParameterLookupTag>): ConeKotlinType {
val substitutor = object : AbstractConeSubstitutor(session.typeContext) {
// We don't substitute types
override fun substituteType(type: ConeKotlinType): ConeKotlinType? = null
override fun substituteArgument(projection: ConeTypeProjection, index: Int): ConeTypeProjection? {
// But we substitute type parameters from the class-owner of this@FirTypeParameterSymbol as it's done in K1
if (projection is ConeTypeParameterType && projection.lookupTag in containingParameterSet) return ConeStarProjection
return super.substituteArgument(projection, index)
}
}
return substitutor.substituteOrSelf(resolvedBounds.first().type)
}
private fun IrVararg.applyConversionOnVararg(
argument: FirExpression,
conversion: IrExpression.(FirExpression) -> IrExpression
@@ -27416,6 +27416,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/invokedynamic/sam/simpleIndySam.kt");
}
@Test
@TestMetadata("starProjectionSam.kt")
public void testStarProjectionSam() throws Exception {
runTest("compiler/testData/codegen/box/invokedynamic/sam/starProjectionSam.kt");
}
@Test
@TestMetadata("streamApi1.kt")
public void testStreamApi1() throws Exception {
@@ -27416,6 +27416,12 @@ public class FirPsiBlackBoxCodegenTestGenerated extends AbstractFirPsiBlackBoxCo
runTest("compiler/testData/codegen/box/invokedynamic/sam/simpleIndySam.kt");
}
@Test
@TestMetadata("starProjectionSam.kt")
public void testStarProjectionSam() throws Exception {
runTest("compiler/testData/codegen/box/invokedynamic/sam/starProjectionSam.kt");
}
@Test
@TestMetadata("streamApi1.kt")
public void testStreamApi1() throws Exception {
@@ -268,7 +268,7 @@ interface ConeTypeContext : TypeSystemContext, TypeSystemOptimizationContext, Ty
}
}
override fun TypeConstructorMarker.getParameters(): List<TypeParameterMarker> {
override fun TypeConstructorMarker.getParameters(): List<ConeTypeParameterLookupTag> {
return when (val symbol = toClassLikeSymbol()) {
is FirAnonymousObjectSymbol -> symbol.fir.typeParameters.map { it.symbol.toLookupTag() }
is FirRegularClassSymbol -> symbol.fir.typeParameters.map { it.symbol.toLookupTag() }