diff --git a/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/BaseConverter.kt b/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/BaseConverter.kt index 7e46150e7f2..aeb2b7d1139 100644 --- a/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/BaseConverter.kt +++ b/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/BaseConverter.kt @@ -113,6 +113,16 @@ open class BaseConverter( return kidsRef.get() } + fun LighterASTNode?.getFirstChild(): LighterASTNode? { + val firstChild: LighterASTNode? + try { + firstChild = getChildrenAsArray()[0] + } catch (e: ArrayIndexOutOfBoundsException) { + return null + } + return firstChild + } + @OptIn(ExperimentalContracts::class) protected inline fun LighterASTNode.forEachChildren(vararg skipTokens: KtToken, f: (LighterASTNode) -> Unit) { val kidsArray = this.getChildrenAsArray() diff --git a/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/DeclarationsConverter.kt b/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/DeclarationsConverter.kt index 86397ac776e..c3532a3dc64 100644 --- a/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/DeclarationsConverter.kt +++ b/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/DeclarationsConverter.kt @@ -17,6 +17,7 @@ import org.jetbrains.kotlin.fir.builder.Context import org.jetbrains.kotlin.fir.builder.extractContractDescriptionIfPossible import org.jetbrains.kotlin.fir.builder.generateAccessorsByDelegate import org.jetbrains.kotlin.fir.contracts.FirContractDescription +import org.jetbrains.kotlin.fir.contracts.builder.buildRawContractDescription import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.builder.* import org.jetbrains.kotlin.fir.declarations.impl.FirDeclarationStatusImpl @@ -1083,6 +1084,7 @@ class DeclarationsConverter( } var block: LighterASTNode? = null var expression: LighterASTNode? = null + var outerContractDescription: FirContractDescription? = null getterOrSetter.forEachChildren { if (it.asText == "set") isGetter = false when (it.tokenType) { @@ -1090,6 +1092,7 @@ class DeclarationsConverter( MODIFIER_LIST -> modifiers = convertModifierList(it) TYPE_REFERENCE -> returnType = convertType(it) VALUE_PARAMETER_LIST -> firValueParameters = convertSetterParameter(it, propertyTypeRef) + CONTRACT_EFFECT_LIST -> outerContractDescription = obtainContractDescription(it) BLOCK -> block = it else -> if (it.isExpression()) expression = it } @@ -1137,10 +1140,12 @@ class DeclarationsConverter( valueParameters += firValueParameters } - val (body, contractDescription) = convertFunctionBody(block, expression) - this.body = body + val hasContractEffectList = outerContractDescription != null + val bodyWithContractDescription = convertFunctionBody(block, expression, hasContractEffectList) + this.body = bodyWithContractDescription.first + val contractDescription = outerContractDescription ?: bodyWithContractDescription.second contractDescription?.let { - this.contractDescription = contractDescription + this.contractDescription = it } context.firFunctionTargets.removeLast() }.also { @@ -1148,6 +1153,31 @@ class DeclarationsConverter( } } + private fun obtainContractDescription(rawContractDescription: LighterASTNode): FirContractDescription? = + buildRawContractDescription { + source = rawContractDescription.toFirSourceElement() + extractRawEffects(rawContractDescription, rawEffects) + } + + private fun extractRawEffects(rawContractDescription: LighterASTNode, destination: MutableList) { + rawContractDescription.forEachChildren { + val errorReason = "The contract effect is not an expression" + when (it.tokenType) { + CONTRACT_EFFECT -> { + val effect = it.getFirstChild() + if (effect == null) { + val errorExpression = buildErrorExpression(null, ConeSimpleDiagnostic(errorReason, DiagnosticKind.ExpressionRequired)) + destination.add(errorExpression) + } else { + val expression = expressionConverter.convertExpression(effect, errorReason) + destination.add(expression as FirExpression) + } + } + else -> Unit + } + } + } + /** * this is just a VALUE_PARAMETER_LIST * @@ -1195,6 +1225,7 @@ class DeclarationsConverter( var expression: LighterASTNode? = null var hasEqToken = false var typeParameterList: LighterASTNode? = null + var outerContractDescription: FirContractDescription? = null functionDeclaration.forEachChildren { when (it.tokenType) { MODIFIER_LIST -> modifiers = convertModifierList(it) @@ -1204,6 +1235,7 @@ class DeclarationsConverter( COLON -> isReturnType = true TYPE_REFERENCE -> if (isReturnType) returnType = convertType(it) else receiverType = convertType(it) TYPE_CONSTRAINT_LIST -> typeConstraints += convertTypeConstraints(it) + CONTRACT_EFFECT_LIST -> outerContractDescription = obtainContractDescription(it) BLOCK -> block = it EQ -> hasEqToken = true else -> if (it.isExpression()) expression = it @@ -1269,8 +1301,11 @@ class DeclarationsConverter( addCapturedTypeParameters(typeParameters) } valueParametersList?.let { list -> valueParameters += convertValueParameters(list).map { it.firValueParameter } } - val (body, contractDescription) = convertFunctionBody(block, expression) - this.body = body + + val hasContractEffectList = outerContractDescription != null + val bodyWithContractDescription = convertFunctionBody(block, expression, hasContractEffectList) + this.body = bodyWithContractDescription.first + val contractDescription = outerContractDescription ?: bodyWithContractDescription.second contractDescription?.let { // TODO: add error reporting for contracts on lambdas if (this is FirSimpleFunctionBuilder) { @@ -1288,9 +1323,20 @@ class DeclarationsConverter( * @see org.jetbrains.kotlin.parsing.KotlinParsing.parseFunctionBody * @see org.jetbrains.kotlin.fir.builder.RawFirBuilder.Visitor.buildFirBody */ - private fun convertFunctionBody(blockNode: LighterASTNode?, expression: LighterASTNode?): Pair { + private fun convertFunctionBody( + blockNode: LighterASTNode?, + expression: LighterASTNode?, + hasContractEffectList: Boolean = false + ): Pair { return when { - blockNode != null -> convertBlock(blockNode).extractContractDescriptionIfPossible() + blockNode != null -> { + val block = convertBlock(blockNode) + if (hasContractEffectList) { + block to null + } else { + block.extractContractDescriptionIfPossible() + } + } expression != null -> FirSingleExpressionBlock( expressionConverter.getAsFirExpression(expression, "Function has no body (but should)").toReturn() ) to null