[cls] write contracts information to cls stubs

^ KTIJ-24665
this information would be used to create resolved FirElements from stubs,
so no ProtoBuf would be kept in memory
This commit is contained in:
Anna Kozlova
2023-04-05 17:12:30 +02:00
committed by Space Team
parent 9d4db72d72
commit 4fe239375f
44 changed files with 1182 additions and 534 deletions
@@ -5,22 +5,24 @@
package org.jetbrains.kotlin.fir.deserialization
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
import org.jetbrains.kotlin.contracts.description.KtBooleanValueParameterReference
import org.jetbrains.kotlin.contracts.description.KtConstantReference
import org.jetbrains.kotlin.contracts.description.KtValueParameterReference
import org.jetbrains.kotlin.fir.contracts.FirContractDescription
import org.jetbrains.kotlin.fir.contracts.builder.buildResolvedContractDescription
import org.jetbrains.kotlin.fir.contracts.description.*
import org.jetbrains.kotlin.fir.contracts.toFirElement
import org.jetbrains.kotlin.fir.declarations.FirContractDescriptionOwner
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
import org.jetbrains.kotlin.fir.expressions.LogicOperationKind
import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.isBoolean
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.Flags
import org.jetbrains.kotlin.metadata.deserialization.isInstanceType
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.serialization.deserialization.ProtoBufContractDeserializer
class FirContractDeserializer(private val c: FirDeserializationContext) {
class FirContractDeserializer(private val c: FirDeserializationContext) :
ProtoBufContractDeserializer<ConeKotlinType, ConeDiagnostic, FirContractDescriptionOwner>() {
fun loadContract(proto: ProtoBuf.Contract, owner: FirContractDescriptionOwner): FirContractDescription? {
val effects = proto.effectList.map { loadPossiblyConditionalEffect(it, owner) ?: return null }
return buildResolvedContractDescription {
@@ -28,105 +30,16 @@ class FirContractDeserializer(private val c: FirDeserializationContext) {
}
}
private fun loadPossiblyConditionalEffect(
proto: ProtoBuf.Effect,
override fun extractType(proto: ProtoBuf.Expression): ConeKotlinType? {
return c.typeDeserializer.type(proto.isInstanceType(c.typeTable) ?: return null)
}
override fun extractVariable(
valueParameterIndex: Int,
owner: FirContractDescriptionOwner
): ConeEffectDeclaration? {
if (proto.hasConclusionOfConditionalEffect()) {
val conclusion = loadExpression(proto.conclusionOfConditionalEffect, owner) ?: return null
val effect = loadSimpleEffect(proto, owner) ?: return null
return ConeConditionalEffectDeclaration(effect, conclusion)
}
return loadSimpleEffect(proto, owner)
}
private fun loadSimpleEffect(proto: ProtoBuf.Effect, owner: FirContractDescriptionOwner): ConeEffectDeclaration? {
val type: ProtoBuf.Effect.EffectType = if (proto.hasEffectType()) proto.effectType else return null
return when(type) {
ProtoBuf.Effect.EffectType.RETURNS_CONSTANT -> {
val argument = proto.effectConstructorArgumentList.firstOrNull()
val returnValue = if (argument == null) {
ConeConstantReference.WILDCARD
} else {
loadExpression(argument, owner) as? ConeConstantReference ?: return null
}
ConeReturnsEffectDeclaration(returnValue)
}
ProtoBuf.Effect.EffectType.RETURNS_NOT_NULL -> {
ConeReturnsEffectDeclaration(ConeConstantReference.NOT_NULL)
}
ProtoBuf.Effect.EffectType.CALLS -> {
val argument = proto.effectConstructorArgumentList.firstOrNull() ?: return null
val callable = extractVariable(argument, owner) ?: return null
val invocationKind = if (proto.hasKind())
proto.kind.toDescriptorInvocationKind() ?: return null
else
EventOccurrencesRange.UNKNOWN
ConeCallsEffectDeclaration(callable, invocationKind)
}
}
}
private fun loadExpression(proto: ProtoBuf.Expression, owner: FirContractDescriptionOwner): ConeBooleanExpression? {
val primitiveType = getPrimitiveType(proto)
val primitiveExpression = extractPrimitiveExpression(proto, primitiveType, owner)
val complexType = getComplexType(proto)
val childs: MutableList<ConeBooleanExpression> = mutableListOf()
childs.addIfNotNull(primitiveExpression)
return when (complexType) {
ComplexExpressionType.AND_SEQUENCE -> {
proto.andArgumentList.mapTo(childs) { loadExpression(it, owner) ?: return null }
childs.reduce { acc, booleanExpression -> ConeBinaryLogicExpression(acc, booleanExpression, LogicOperationKind.AND) }
}
ComplexExpressionType.OR_SEQUENCE -> {
proto.orArgumentList.mapTo(childs) { loadExpression(it, owner) ?: return null }
childs.reduce { acc, booleanExpression -> ConeBinaryLogicExpression(acc, booleanExpression, LogicOperationKind.OR) }
}
null -> primitiveExpression
}
}
private fun extractPrimitiveExpression(proto: ProtoBuf.Expression, primitiveType: PrimitiveExpressionType?, owner: FirContractDescriptionOwner): ConeBooleanExpression? {
val isInverted = Flags.IS_NEGATED.get(proto.flags)
return when (primitiveType) {
PrimitiveExpressionType.VALUE_PARAMETER_REFERENCE, PrimitiveExpressionType.RECEIVER_REFERENCE -> {
(extractVariable(proto, owner) as? ConeBooleanValueParameterReference?)?.invertIfNecessary(isInverted)
}
PrimitiveExpressionType.CONSTANT ->
(loadConstant(proto.constantValue) as? ConeBooleanConstantReference)?.invertIfNecessary(isInverted)
PrimitiveExpressionType.INSTANCE_CHECK -> {
val variable = extractVariable(proto, owner) ?: return null
val type = extractType(proto) ?: return null
ConeIsInstancePredicate(variable, type, isInverted)
}
PrimitiveExpressionType.NULLABILITY_CHECK -> {
val variable = extractVariable(proto, owner) ?: return null
ConeIsNullPredicate(variable, isInverted)
}
null -> null
}
}
private fun ConeBooleanExpression.invertIfNecessary(shouldInvert: Boolean): ConeBooleanExpression =
if (shouldInvert) ConeLogicalNot(this) else this
private fun extractVariable(proto: ProtoBuf.Expression, owner: FirContractDescriptionOwner): ConeValueParameterReference? {
if (!proto.hasValueParameterReference()) return null
val ownerFunction = owner as FirSimpleFunction
val valueParameterIndex = proto.valueParameterReference - 1
): KtValueParameterReference<ConeKotlinType, ConeDiagnostic>? {
val name: String
val ownerFunction = owner as FirSimpleFunction
val typeRef = if (valueParameterIndex < 0) {
name = "this"
ownerFunction.receiverParameter?.typeRef
@@ -137,90 +50,24 @@ class FirContractDeserializer(private val c: FirDeserializationContext) {
} ?: return null
return if (!typeRef.isBoolean)
ConeValueParameterReference(valueParameterIndex, name)
KtValueParameterReference(valueParameterIndex, name)
else
ConeBooleanValueParameterReference(valueParameterIndex, name)
KtBooleanValueParameterReference(valueParameterIndex, name)
}
private fun ProtoBuf.Effect.InvocationKind.toDescriptorInvocationKind(): EventOccurrencesRange? = when (this) {
ProtoBuf.Effect.InvocationKind.AT_MOST_ONCE -> EventOccurrencesRange.AT_MOST_ONCE
ProtoBuf.Effect.InvocationKind.EXACTLY_ONCE -> EventOccurrencesRange.EXACTLY_ONCE
ProtoBuf.Effect.InvocationKind.AT_LEAST_ONCE -> EventOccurrencesRange.AT_LEAST_ONCE
}
private fun extractType(proto: ProtoBuf.Expression): ConeKotlinType? {
return c.typeDeserializer.type(proto.isInstanceType(c.typeTable) ?: return null)
}
private fun loadConstant(value: ProtoBuf.Expression.ConstantValue): ConeConstantReference? = when (value) {
ProtoBuf.Expression.ConstantValue.TRUE -> ConeBooleanConstantReference.TRUE
ProtoBuf.Expression.ConstantValue.FALSE -> ConeBooleanConstantReference.FALSE
ProtoBuf.Expression.ConstantValue.NULL -> ConeConstantReference.NULL
}
private fun getComplexType(proto: ProtoBuf.Expression): ComplexExpressionType? {
val isOrSequence = proto.orArgumentCount != 0
val isAndSequence = proto.andArgumentCount != 0
return when {
isOrSequence && isAndSequence -> null
isOrSequence -> ComplexExpressionType.OR_SEQUENCE
isAndSequence -> ComplexExpressionType.AND_SEQUENCE
else -> null
override fun loadConstant(value: ProtoBuf.Expression.ConstantValue): KtConstantReference<ConeKotlinType, ConeDiagnostic> {
return when (value) {
ProtoBuf.Expression.ConstantValue.TRUE -> ConeContractConstantValues.TRUE
ProtoBuf.Expression.ConstantValue.FALSE -> ConeContractConstantValues.FALSE
ProtoBuf.Expression.ConstantValue.NULL -> ConeContractConstantValues.NULL
}
}
private fun getPrimitiveType(proto: ProtoBuf.Expression): PrimitiveExpressionType? {
// Expected to be one element, but can be empty (unknown expression) or contain several elements (invalid data)
val expressionTypes: MutableList<PrimitiveExpressionType> = mutableListOf()
// Check for predicates
when {
proto.hasValueParameterReference() && proto.hasType() ->
expressionTypes.add(PrimitiveExpressionType.INSTANCE_CHECK)
proto.hasValueParameterReference() && Flags.IS_NULL_CHECK_PREDICATE.get(proto.flags) ->
expressionTypes.add(PrimitiveExpressionType.NULLABILITY_CHECK)
}
// If message contains correct predicate, then predicate's type overrides type of value,
// even is message has one
if (expressionTypes.isNotEmpty()) {
return expressionTypes.singleOrNull()
}
// Otherwise, check if it is a value
when {
proto.hasValueParameterReference() && proto.valueParameterReference > 0 ->
expressionTypes.add(PrimitiveExpressionType.VALUE_PARAMETER_REFERENCE)
proto.hasValueParameterReference() && proto.valueParameterReference == 0 ->
expressionTypes.add(PrimitiveExpressionType.RECEIVER_REFERENCE)
proto.hasConstantValue() -> expressionTypes.add(PrimitiveExpressionType.CONSTANT)
}
return expressionTypes.singleOrNull()
override fun getNotNull(): KtConstantReference<ConeKotlinType, ConeDiagnostic> {
return ConeContractConstantValues.NOT_NULL
}
private fun ProtoBuf.Expression.hasType(): Boolean = this.hasIsInstanceType() || this.hasIsInstanceTypeId()
// Arguments of expressions with such types are never other expressions
private enum class PrimitiveExpressionType {
VALUE_PARAMETER_REFERENCE,
RECEIVER_REFERENCE,
CONSTANT,
INSTANCE_CHECK,
NULLABILITY_CHECK
}
// Expressions with such type can take other expressions as arguments.
// Additionally, for performance reasons, "complex expression" and "primitive expression"
// can co-exist in the one and the same message. If "primitive expression" is present
// in the current message, it is treated as the first argument of "complex expression".
private enum class ComplexExpressionType {
AND_SEQUENCE,
OR_SEQUENCE
override fun getWildcard(): KtConstantReference<ConeKotlinType, ConeDiagnostic> {
return ConeContractConstantValues.WILDCARD
}
}