diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/deserialization/FirContractDeserializer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/deserialization/FirContractDeserializer.kt new file mode 100644 index 00000000000..a7e76efbfe8 --- /dev/null +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/deserialization/FirContractDeserializer.kt @@ -0,0 +1,221 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.fir.deserialization + +import org.jetbrains.kotlin.contracts.description.InvocationKind +import org.jetbrains.kotlin.fir.contracts.FirContractDescription +import org.jetbrains.kotlin.fir.contracts.description.* +import org.jetbrains.kotlin.fir.contracts.impl.FirContractDescriptionImpl +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.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 + +class FirContractDeserializer(private val c: FirDeserializationContext) { + fun loadContract(proto: ProtoBuf.Contract, owner: FirContractDescriptionOwner): FirContractDescription? { + val effects = proto.effectList.map { loadPossiblyConditionalEffect(it, owner) ?: return null } + return FirContractDescriptionImpl(null).apply { + this.effects.addAll(effects) + } + } + + private fun loadPossiblyConditionalEffect( + proto: ProtoBuf.Effect, + 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 + InvocationKind.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 = 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 + + val typeRef = if (valueParameterIndex < 0) { + ownerFunction.receiverTypeRef + } else { + ownerFunction.valueParameters.getOrNull(valueParameterIndex)?.returnTypeRef + } ?: return null + + return if (!typeRef.isBoolean) + ConeValueParameterReference(valueParameterIndex) + else + ConeBooleanValueParameterReference(valueParameterIndex) + } + + private fun ProtoBuf.Effect.InvocationKind.toDescriptorInvocationKind(): InvocationKind? = when (this) { + ProtoBuf.Effect.InvocationKind.AT_MOST_ONCE -> InvocationKind.AT_MOST_ONCE + ProtoBuf.Effect.InvocationKind.EXACTLY_ONCE -> InvocationKind.EXACTLY_ONCE + ProtoBuf.Effect.InvocationKind.AT_LEAST_ONCE -> InvocationKind.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 + } + } + + 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 = 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() + } + + 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 + + } +} \ No newline at end of file diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/deserialization/FirMemberDeserializer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/deserialization/FirMemberDeserializer.kt index 72cad243478..300b0f1b54a 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/deserialization/FirMemberDeserializer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/deserialization/FirMemberDeserializer.kt @@ -118,6 +118,8 @@ class FirDeserializationContext( class FirDeserializationComponents class FirMemberDeserializer(private val c: FirDeserializationContext) { + private val contractDeserializer = FirContractDeserializer(c) + private fun loadOldFlags(oldFlags: Int): Int { val lowSixBits = oldFlags and 0x3f val rest = (oldFlags shr 8) shl 6 @@ -221,7 +223,8 @@ class FirMemberDeserializer(private val c: FirDeserializationContext) { } // TODO: support contracts - return FirSimpleFunctionImpl( + + val simpleFunction = FirSimpleFunctionImpl( null, c.session, proto.returnType(local.typeTable).toTypeRef(local), @@ -235,6 +238,13 @@ class FirMemberDeserializer(private val c: FirDeserializationContext) { valueParameters += local.memberDeserializer.valueParameters(proto.valueParameterList) annotations += local.annotationDeserializer.loadFunctionAnnotations(proto, local.nameResolver) } + if (proto.hasContract()) { + val contractDescription = contractDeserializer.loadContract(proto.contract, simpleFunction) + if (contractDescription != null) { + simpleFunction.contractDescription = contractDescription + } + } + return simpleFunction } fun loadConstructor(proto: ProtoBuf.Constructor, klass: FirRegularClass): FirConstructor { diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/types/FirTypeUtils.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/types/FirTypeUtils.kt index 77f7f1250cd..0d827aac5c4 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/types/FirTypeUtils.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/types/FirTypeUtils.kt @@ -17,9 +17,16 @@ val FirTypeRef.isNullableAny: Boolean get() = isBuiltinType(StandardClassIds.Any val FirTypeRef.isNothing: Boolean get() = isBuiltinType(StandardClassIds.Nothing, false) val FirTypeRef.isNullableNothing: Boolean get() = isBuiltinType(StandardClassIds.Nothing, true) val FirTypeRef.isUnit: Boolean get() = isBuiltinType(StandardClassIds.Unit, false) +val FirTypeRef.isBoolean: Boolean get() = isBuiltinType(StandardClassIds.Boolean, false) -private fun FirTypeRef.isBuiltinType(classId: ClassId, isNullable: Boolean): Boolean = - this is FirImplicitBuiltinTypeRef && type.lookupTag.classId == classId && type.isNullable == isNullable +private fun FirTypeRef.isBuiltinType(classId: ClassId, isNullable: Boolean): Boolean { + val type = when (this) { + is FirImplicitBuiltinTypeRef -> type + is FirResolvedTypeRef -> type as? ConeClassLikeType ?: return false + else -> return false + } + return type.lookupTag.classId == classId && type.isNullable == isNullable +} val FirFunctionTypeRef.parametersCount: Int get() = if (receiverTypeRef != null)