diff --git a/compiler/fir/raw-fir/fir-common/src/org/jetbrains/kotlin/fir/builder/ConversionUtils.kt b/compiler/fir/raw-fir/fir-common/src/org/jetbrains/kotlin/fir/builder/ConversionUtils.kt index 624ea5f76a3..35d4d63d741 100644 --- a/compiler/fir/raw-fir/fir-common/src/org/jetbrains/kotlin/fir/builder/ConversionUtils.kt +++ b/compiler/fir/raw-fir/fir-common/src/org/jetbrains/kotlin/fir/builder/ConversionUtils.kt @@ -9,6 +9,8 @@ import com.intellij.psi.tree.IElementType import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.fir.* +import org.jetbrains.kotlin.fir.contracts.FirContractDescription +import org.jetbrains.kotlin.fir.contracts.builder.buildRawContractDescription import org.jetbrains.kotlin.fir.declarations.FirProperty import org.jetbrains.kotlin.fir.declarations.FirVariable import org.jetbrains.kotlin.fir.declarations.builder.* @@ -17,7 +19,9 @@ import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.expressions.builder.* import org.jetbrains.kotlin.fir.expressions.impl.FirSingleExpressionBlock +import org.jetbrains.kotlin.fir.expressions.impl.FirStubStatement import org.jetbrains.kotlin.fir.expressions.impl.buildSingleExpressionBlock +import org.jetbrains.kotlin.fir.references.FirNamedReference import org.jetbrains.kotlin.fir.references.builder.* import org.jetbrains.kotlin.fir.symbols.StandardClassIds import org.jetbrains.kotlin.fir.symbols.constructStarProjectedType @@ -35,6 +39,8 @@ import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.types.Variance import org.jetbrains.kotlin.types.expressions.OperatorConventions import org.jetbrains.kotlin.util.OperatorNameConventions +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract fun String.parseCharacter(): Char? { // Strip the quotes @@ -458,3 +464,32 @@ private val GET_VALUE = Name.identifier("getValue") private val SET_VALUE = Name.identifier("setValue") private val PROVIDE_DELEGATE = Name.identifier("provideDelegate") private val DELEGATED_SETTER_PARAM = Name.special("") + +fun FirBlock?.extractContractDescriptionIfPossible(): Pair { + if (this == null) return null to null + if (!isContractPresentFirCheck()) return this to null + val contractCall = replaceFirstStatement(FirStubStatement) as FirFunctionCall + return this to buildRawContractDescription { + source = contractCall.source + this.contractCall = contractCall + } +} + +fun FirBlock.isContractPresentFirCheck(): Boolean { + val firstStatement = statements.firstOrNull() ?: return false + val contractCall = firstStatement as? FirFunctionCall ?: return false + if (contractCall.calleeReference.name.asString() != "contract") return false + val receiver = contractCall.explicitReceiver as? FirQualifiedAccessExpression ?: return true + if (!contractCall.checkReceiver("contracts")) return false + if (!receiver.checkReceiver("kotlin")) return false + val receiverOfReceiver = receiver.explicitReceiver as? FirQualifiedAccessExpression ?: return false + if (receiverOfReceiver.explicitReceiver != null) return false + return true +} + +private fun FirExpression.checkReceiver(name: String?): Boolean { + if (this !is FirQualifiedAccessExpression) return false + val receiver = explicitReceiver as? FirQualifiedAccessExpression ?: return false + val receiverName = (receiver.calleeReference as? FirNamedReference)?.name?.asString() ?: return false + return receiverName == name +} 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 5e1ed6eb2d2..04f166e251f 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 @@ -15,7 +15,9 @@ import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget import org.jetbrains.kotlin.fir.* 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.declarations.* import org.jetbrains.kotlin.fir.declarations.builder.* import org.jetbrains.kotlin.fir.declarations.impl.FirDeclarationStatusImpl @@ -757,7 +759,8 @@ class DeclarationsConverter( annotations += modifiers.annotations typeParameters += constructorTypeParametersFromConstructedClass(classWrapper.classBuilder.typeParameters) valueParameters += firValueParameters.map { it.firValueParameter } - body = convertFunctionBody(block, null) + val (body, _) = convertFunctionBody(block, null) + this.body = body context.firFunctionTargets.removeLast() }.also { target.bind(it) @@ -1053,7 +1056,11 @@ class DeclarationsConverter( valueParameters += firValueParameters } - body = convertFunctionBody(block, expression) + val (body, contractDescription) = convertFunctionBody(block, expression) + this.body = body + contractDescription?.let { + this.contractDescription = contractDescription + } context.firFunctionTargets.removeLast() }.also { target.bind(it) @@ -1178,7 +1185,14 @@ class DeclarationsConverter( addCapturedTypeParameters(typeParameters) } valueParametersList?.let { list -> valueParameters += convertValueParameters(list).map { it.firValueParameter } } - body = convertFunctionBody(block, expression) + val (body, contractDescription) = convertFunctionBody(block, expression) + this.body = body + contractDescription?.let { + // TODO: add error reporting for contracts on lambdas + if (this is FirSimpleFunctionBuilder) { + this.contractDescription = it + } + } } context.firFunctionTargets.removeLast() }.build().also { @@ -1190,13 +1204,13 @@ 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?): FirBlock? { + private fun convertFunctionBody(blockNode: LighterASTNode?, expression: LighterASTNode?): Pair { return when { - blockNode != null -> return convertBlock(blockNode) + blockNode != null -> convertBlock(blockNode).extractContractDescriptionIfPossible() expression != null -> FirSingleExpressionBlock( expressionConverter.getAsFirExpression(expression, "Function has no body (but should)").toReturn() - ) - else -> null + ) to null + else -> null to null } } diff --git a/compiler/fir/raw-fir/light-tree2fir/tests/org/jetbrains/kotlin/fir/lightTree/LightTree2FirConverterTestCaseGenerated.java b/compiler/fir/raw-fir/light-tree2fir/tests/org/jetbrains/kotlin/fir/lightTree/LightTree2FirConverterTestCaseGenerated.java index 1e6af973f31..ac3575eb102 100644 --- a/compiler/fir/raw-fir/light-tree2fir/tests/org/jetbrains/kotlin/fir/lightTree/LightTree2FirConverterTestCaseGenerated.java +++ b/compiler/fir/raw-fir/light-tree2fir/tests/org/jetbrains/kotlin/fir/lightTree/LightTree2FirConverterTestCaseGenerated.java @@ -55,6 +55,11 @@ public class LightTree2FirConverterTestCaseGenerated extends AbstractLightTree2F runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/declarations/constructorInObject.kt"); } + @TestMetadata("contractDescription.kt") + public void testContractDescription() throws Exception { + runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/declarations/contractDescription.kt"); + } + @TestMetadata("derivedClass.kt") public void testDerivedClass() throws Exception { runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/declarations/derivedClass.kt"); diff --git a/compiler/fir/raw-fir/psi2fir/src/org/jetbrains/kotlin/fir/builder/RawFirBuilder.kt b/compiler/fir/raw-fir/psi2fir/src/org/jetbrains/kotlin/fir/builder/RawFirBuilder.kt index 5771db44a4a..8e7ac95eee3 100644 --- a/compiler/fir/raw-fir/psi2fir/src/org/jetbrains/kotlin/fir/builder/RawFirBuilder.kt +++ b/compiler/fir/raw-fir/psi2fir/src/org/jetbrains/kotlin/fir/builder/RawFirBuilder.kt @@ -14,6 +14,8 @@ import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.descriptors.Visibility import org.jetbrains.kotlin.fir.* +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 @@ -26,6 +28,7 @@ import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.expressions.builder.* import org.jetbrains.kotlin.fir.expressions.impl.FirModifiableQualifiedAccess import org.jetbrains.kotlin.fir.expressions.impl.FirSingleExpressionBlock +import org.jetbrains.kotlin.fir.expressions.impl.FirStubStatement import org.jetbrains.kotlin.fir.references.FirNamedReference import org.jetbrains.kotlin.fir.references.builder.* import org.jetbrains.kotlin.fir.scopes.FirScopeProvider @@ -42,10 +45,24 @@ import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier import org.jetbrains.kotlin.psi.psiUtil.hasExpectModifier +import org.jetbrains.kotlin.psi.psiUtil.isContractDescriptionCallPsiCheck import org.jetbrains.kotlin.types.Variance import org.jetbrains.kotlin.types.expressions.OperatorConventions import org.jetbrains.kotlin.util.OperatorNameConventions import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull +import kotlin.collections.MutableList +import kotlin.collections.any +import kotlin.collections.contains +import kotlin.collections.filterIsInstance +import kotlin.collections.first +import kotlin.collections.firstOrNull +import kotlin.collections.forEach +import kotlin.collections.isNotEmpty +import kotlin.collections.lastOrNull +import kotlin.collections.plusAssign +import kotlin.collections.reverse +import kotlin.collections.withIndex +import kotlin.collections.zip class RawFirBuilder( session: FirSession, val baseScopeProvider: FirScopeProvider, val stubMode: Boolean @@ -199,19 +216,20 @@ class RawFirBuilder( FirSingleExpressionBlock(convert()) } - private fun KtDeclarationWithBody.buildFirBody(): FirBlock? = + private fun KtDeclarationWithBody.buildFirBody(): Pair = when { !hasBody() -> - null + null to null hasBlockBody() -> if (!stubMode) { - bodyBlockExpression?.accept(this@Visitor, Unit) as? FirBlock + val block = bodyBlockExpression?.accept(this@Visitor, Unit) as? FirBlock + block.extractContractDescriptionIfPossible() } else { - FirSingleExpressionBlock(buildExpressionStub { source = this@buildFirBody.toFirSourceElement() }.toReturn()) + FirSingleExpressionBlock(buildExpressionStub { source = this@buildFirBody.toFirSourceElement() }.toReturn()) to null } else -> { val result = { bodyExpression }.toFirExpression("Function has no body (but should)") // basePsi is null, because 'return' is synthetic & should not be bound to some PSI - FirSingleExpressionBlock(result.toReturn(baseSource = null)) + FirSingleExpressionBlock(result.toReturn(baseSource = null)) to null } } @@ -289,7 +307,11 @@ class RawFirBuilder( } } symbol = FirPropertyAccessorSymbol() - body = this@toFirPropertyAccessor.buildFirBody() + val (body, contractDescription) = this@toFirPropertyAccessor.buildFirBody() + this.body = body + contractDescription?.let { + this.contractDescription = it + } }.also { accessorTarget.bind(it) this@RawFirBuilder.context.firFunctionTargets.removeLast() @@ -824,7 +846,14 @@ class RawFirBuilder( } withCapturedTypeParameters { if (this is FirSimpleFunctionBuilder) addCapturedTypeParameters(this.typeParameters) - body = function.buildFirBody() + val (body, contractDescription) = function.buildFirBody() + this.body = body + contractDescription?.let { + // TODO: add error reporting for contracts on lambdas + if (this is FirSimpleFunctionBuilder) { + this.contractDescription = it + } + } } context.firFunctionTargets.removeLast() }.build().also { @@ -945,7 +974,8 @@ class RawFirBuilder( extractAnnotationsTo(this) typeParameters += constructorTypeParametersFromConstructedClass(ownerTypeParameters) extractValueParametersTo(this) - body = buildFirBody() + val (body, _) = buildFirBody() + this.body = body this@RawFirBuilder.context.firFunctionTargets.removeLast() }.also { target.bind(it) diff --git a/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/declarations/contractDescription.kt b/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/declarations/contractDescription.kt new file mode 100644 index 00000000000..0fe5029931e --- /dev/null +++ b/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/declarations/contractDescription.kt @@ -0,0 +1,62 @@ +// Should have raw description +fun test_1() { + contract { + callsInPlace() + } + test_1() +} + +fun test_2() { + kotlin.contracts.contract { + callsInPlace() + callsInPlace() + } + test_2() +} + +var test_3: Int = 1 + get() { + contract { + callsInPlace() + } + return 1 + } + set(value) { + kotlin.contracts.contract { + callsInPlace() + callsInPlace() + } + } + +fun test_4() { + contract() + test_4() +} + +// should not have raw description + +fun test_5() { + test_5() + contract() +} + +fun test_6() { + aaa.bbb.ccc.contract { + + } + test_6() +} + +fun test_7() { + contracts.contract { + + } + test_7() +} + +fun test_8() { + aaa.kotlin.contracts.contract { + + } + test_8() +} diff --git a/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/declarations/contractDescription.txt b/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/declarations/contractDescription.txt new file mode 100644 index 00000000000..c21501c640b --- /dev/null +++ b/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/declarations/contractDescription.txt @@ -0,0 +1,80 @@ +FILE: contractDescription.kt + public? final? fun test_1(): R|kotlin/Unit| + [Contract description] < + contract#( = contract@fun .(): { + callsInPlace#() + } + ) + > + { + [StubStatement] + test_1#() + } + public? final? fun test_2(): R|kotlin/Unit| + [Contract description] < + kotlin#.contracts#.contract#( = contract@fun .(): { + callsInPlace#() + callsInPlace#() + } + ) + > + { + [StubStatement] + test_2#() + } + public? final? var test_3: Int = IntegerLiteral(1) + public? get(): Int + [Contract description] < + contract#( = contract@fun .(): { + callsInPlace#() + } + ) + > + { + [StubStatement] + ^ IntegerLiteral(1) + } + public? set(value: Int): R|kotlin/Unit| + [Contract description] < + kotlin#.contracts#.contract#( = contract@fun .(): { + callsInPlace#() + callsInPlace#() + } + ) + > + { + [StubStatement] + } + public? final? fun test_4(): R|kotlin/Unit| + [Contract description] < + contract#() + > + { + [StubStatement] + test_4#() + } + public? final? fun test_5(): R|kotlin/Unit| { + test_5#() + contract#() + } + public? final? fun test_6(): R|kotlin/Unit| { + aaa#.bbb#.ccc#.contract#( = contract@fun .(): { + Unit + } + ) + test_6#() + } + public? final? fun test_7(): R|kotlin/Unit| { + contracts#.contract#( = contract@fun .(): { + Unit + } + ) + test_7#() + } + public? final? fun test_8(): R|kotlin/Unit| { + aaa#.kotlin#.contracts#.contract#( = contract@fun .(): { + Unit + } + ) + test_8#() + } diff --git a/compiler/fir/raw-fir/psi2fir/tests/org/jetbrains/kotlin/fir/builder/RawFirBuilderTestCaseGenerated.java b/compiler/fir/raw-fir/psi2fir/tests/org/jetbrains/kotlin/fir/builder/RawFirBuilderTestCaseGenerated.java index ec9679aaf37..00ceb1dc6ee 100644 --- a/compiler/fir/raw-fir/psi2fir/tests/org/jetbrains/kotlin/fir/builder/RawFirBuilderTestCaseGenerated.java +++ b/compiler/fir/raw-fir/psi2fir/tests/org/jetbrains/kotlin/fir/builder/RawFirBuilderTestCaseGenerated.java @@ -55,6 +55,11 @@ public class RawFirBuilderTestCaseGenerated extends AbstractRawFirBuilderTestCas runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/declarations/constructorInObject.kt"); } + @TestMetadata("contractDescription.kt") + public void testContractDescription() throws Exception { + runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/declarations/contractDescription.kt"); + } + @TestMetadata("derivedClass.kt") public void testDerivedClass() throws Exception { runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/declarations/derivedClass.kt"); diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/FirRenderer.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/FirRenderer.kt index 3972c4e89ea..92e6b377f16 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/FirRenderer.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/FirRenderer.kt @@ -544,7 +544,11 @@ class FirRenderer(builder: StringBuilder, private val mode: RenderMode = RenderM } override fun visitStatement(statement: FirStatement) { - visitElement(statement) + if (statement is FirStubStatement) { + print("[StubStatement]") + } else { + visitElement(statement) + } } override fun visitReturnExpression(returnExpression: FirReturnExpression) { diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/FirExpressionUtil.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/FirExpressionUtil.kt index 735ca0beedf..76ca5a40d7d 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/FirExpressionUtil.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/FirExpressionUtil.kt @@ -84,4 +84,13 @@ fun FirBlock.transformAllStatementsExceptLast(transformer: FirTransformer TransformData.Nothing } } +} + +fun FirBlock.replaceFirstStatement(statement: FirStatement): FirStatement { + require(this is FirBlockImpl) { + "replaceFirstStatement should not be called for ${this::class.simpleName}" + } + val existed = statements[0] + statements[0] = statement + return existed } \ No newline at end of file diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/impl/FirStubStatement.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/impl/FirStubStatement.kt new file mode 100644 index 00000000000..bfad41033d5 --- /dev/null +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/impl/FirStubStatement.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2010-2020 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.expressions.impl + +import org.jetbrains.kotlin.fir.FirElement +import org.jetbrains.kotlin.fir.FirPureAbstractElement +import org.jetbrains.kotlin.fir.FirSourceElement +import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall +import org.jetbrains.kotlin.fir.expressions.FirStatement +import org.jetbrains.kotlin.fir.visitors.FirTransformer +import org.jetbrains.kotlin.fir.visitors.FirVisitor + +object FirStubStatement : FirPureAbstractElement(), FirStatement { + override val source: FirSourceElement? + get() = null + + override val annotations: List + get() = emptyList() + + override fun transformAnnotations(transformer: FirTransformer, data: D): FirStatement { + return this + } + + override fun acceptChildren(visitor: FirVisitor, data: D) {} + + override fun transformChildren(transformer: FirTransformer, data: D): FirElement { + return this + } +} \ No newline at end of file diff --git a/compiler/visualizer/tests/org/jetbrains/kotlin/visualizer/fir/FirVisualizerForRawFirDataGenerated.java b/compiler/visualizer/tests/org/jetbrains/kotlin/visualizer/fir/FirVisualizerForRawFirDataGenerated.java index d3086a95eb9..c75404782da 100644 --- a/compiler/visualizer/tests/org/jetbrains/kotlin/visualizer/fir/FirVisualizerForRawFirDataGenerated.java +++ b/compiler/visualizer/tests/org/jetbrains/kotlin/visualizer/fir/FirVisualizerForRawFirDataGenerated.java @@ -55,6 +55,11 @@ public class FirVisualizerForRawFirDataGenerated extends AbstractFirVisualizer { runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/declarations/constructorInObject.kt"); } + @TestMetadata("contractDescription.kt") + public void testContractDescription() throws Exception { + runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/declarations/contractDescription.kt"); + } + @TestMetadata("derivedClass.kt") public void testDerivedClass() throws Exception { runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/declarations/derivedClass.kt"); diff --git a/compiler/visualizer/tests/org/jetbrains/kotlin/visualizer/psi/PsiVisualizerForRawFirDataGenerated.java b/compiler/visualizer/tests/org/jetbrains/kotlin/visualizer/psi/PsiVisualizerForRawFirDataGenerated.java index 1b0227394b3..c22cba166ce 100644 --- a/compiler/visualizer/tests/org/jetbrains/kotlin/visualizer/psi/PsiVisualizerForRawFirDataGenerated.java +++ b/compiler/visualizer/tests/org/jetbrains/kotlin/visualizer/psi/PsiVisualizerForRawFirDataGenerated.java @@ -55,6 +55,11 @@ public class PsiVisualizerForRawFirDataGenerated extends AbstractPsiVisualizer { runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/declarations/constructorInObject.kt"); } + @TestMetadata("contractDescription.kt") + public void testContractDescription() throws Exception { + runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/declarations/contractDescription.kt"); + } + @TestMetadata("derivedClass.kt") public void testDerivedClass() throws Exception { runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/declarations/derivedClass.kt");