diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/CopyUtils.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/CopyUtils.kt index 2a3b3890337..9833e6c7026 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/CopyUtils.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/CopyUtils.kt @@ -11,6 +11,7 @@ import org.jetbrains.kotlin.fir.declarations.FirTypeParameter import org.jetbrains.kotlin.fir.declarations.FirValueParameter import org.jetbrains.kotlin.fir.declarations.builder.buildAnonymousFunction import org.jetbrains.kotlin.fir.declarations.builder.buildTypeParameter +import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.expressions.builder.* import org.jetbrains.kotlin.fir.references.FirControlFlowGraphReference @@ -18,10 +19,8 @@ import org.jetbrains.kotlin.fir.references.FirNamedReference import org.jetbrains.kotlin.fir.references.FirReference import org.jetbrains.kotlin.fir.scopes.impl.FirIntegerOperatorCall import org.jetbrains.kotlin.fir.scopes.impl.FirIntegerOperatorCallBuilder -import org.jetbrains.kotlin.fir.types.ConeKotlinType -import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef -import org.jetbrains.kotlin.fir.types.FirTypeProjection -import org.jetbrains.kotlin.fir.types.FirTypeRef +import org.jetbrains.kotlin.fir.types.* +import org.jetbrains.kotlin.fir.types.builder.buildErrorTypeRef import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef fun FirFunctionCall.copy( @@ -100,6 +99,15 @@ fun FirTypeRef.resolvedTypeFromPrototype( } } +fun FirTypeRef.errorTypeFromPrototype( + diagnostic: ConeDiagnostic +): FirErrorTypeRef { + return buildErrorTypeRef { + source = this@errorTypeFromPrototype.source + this.diagnostic = diagnostic + } +} + fun FirTypeParameter.copy( bounds: List = this.bounds, annotations: List = this.annotations diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt index be0534c8437..99fd9879bb1 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt @@ -953,6 +953,16 @@ abstract class FirDataFlowAnalyzer( return controlFlowGraph } + // ----------------------------------- Contract description ----------------------------------- + + fun enterContractDescription() { + graphBuilder.enterContractDescription().mergeIncomingFlow() + } + + fun exitContractDescription() { + graphBuilder.exitContractDescription() + } + // ------------------------------------------------------ Utils ------------------------------------------------------ private var CFGNode<*>.flow: FLOW diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNodeRenderer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNodeRenderer.kt index 4f1429dda52..d4b2795d1a2 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNodeRenderer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNodeRenderer.kt @@ -104,6 +104,8 @@ fun CFGNode<*>.render(): String = is LocalClassExitNode -> "Exit local class ${owner.name}" is AnonymousObjectExitNode -> "Exit anonymous object" + is ContractDescriptionEnterNode -> "Enter contract description" + is AbstractBinaryExitNode -> throw IllegalStateException() }, ) diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt index 6ff68403775..45fe049a652 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt @@ -520,6 +520,13 @@ class StubNode(owner: ControlFlowGraph, level: Int, id: Int) : CFGNode( } } +class ContractDescriptionEnterNode(owner: ControlFlowGraph, level: Int, id: Int) : CFGNode(owner, level, id) { + override val fir: FirStub = FirStub + override fun accept(visitor: ControlFlowGraphVisitor, data: D): R { + return visitor.visitContractDescriptionEnterNode(this, data) + } +} + class VariableDeclarationNode(owner: ControlFlowGraph, override val fir: FirProperty, level: Int, id: Int) : CFGNode(owner, level, id) { override fun accept(visitor: ControlFlowGraphVisitor, data: D): R { return visitor.visitVariableDeclarationNode(this, data) diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt index e510e0d8050..38cbc8ab871 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt @@ -870,6 +870,22 @@ class ControlFlowGraphBuilder { } } + // ----------------------------------- Contract description ----------------------------------- + + fun enterContractDescription(): CFGNode<*> { + graphs.push(ControlFlowGraph(null, "contract description", ControlFlowGraph.Kind.TopLevel)) + val node = createContractDescriptionEnterNode().also { + graph.enterNode = it + } + lexicalScopes.push(stackOf(node)) + return node + } + + fun exitContractDescription() { + lexicalScopes.pop() + graphs.pop() + } + // ------------------------------------------------------------------------------------------------------------------------- private fun addNodeThatReturnsNothing(node: CFGNode<*>, preferredKind: EdgeKind = EdgeKind.Simple) { diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphNodeBuilder.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphNodeBuilder.kt index 4fa4b35057f..6f36c73ef45 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphNodeBuilder.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphNodeBuilder.kt @@ -11,6 +11,9 @@ import org.jetbrains.kotlin.fir.expressions.* fun ControlFlowGraphBuilder.createStubNode(): StubNode = StubNode(graph, levelCounter, createId()) +fun ControlFlowGraphBuilder.createContractDescriptionEnterNode(): ContractDescriptionEnterNode = + ContractDescriptionEnterNode(graph, levelCounter, createId()) + fun ControlFlowGraphBuilder.createLoopExitNode(fir: FirLoop): LoopExitNode = LoopExitNode(graph, fir, levelCounter, createId()) fun ControlFlowGraphBuilder.createLoopEnterNode(fir: FirLoop): LoopEnterNode = LoopEnterNode(graph, fir, levelCounter, createId()) diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphVisitor.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphVisitor.kt index 8085ad3a827..a62bc8d0313 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphVisitor.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphVisitor.kt @@ -269,6 +269,10 @@ abstract class ControlFlowGraphVisitor { return visitNode(node, data) } + open fun visitContractDescriptionEnterNode(node: ContractDescriptionEnterNode, data: D): R { + return visitNode(node, data) + } + open fun visitVariableDeclarationNode(node: VariableDeclarationNode, data: D): R { return visitNode(node, data) } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/diagnostics/FirDiagnostics.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/diagnostics/FirDiagnostics.kt index 31be5507661..a14ccacc032 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/diagnostics/FirDiagnostics.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/diagnostics/FirDiagnostics.kt @@ -53,6 +53,8 @@ class ConeTypeMismatchError(val expectedType: ConeKotlinType, val actualType: Co get() = "Type mismatch. Expected: $expectedType, Actual: $actualType" } +class ConeContractDescriptionError(override val reason: String) : ConeDiagnostic() + private fun describeSymbol(symbol: AbstractFirBasedSymbol<*>): String { return when (symbol) { is FirClassLikeSymbol<*> -> symbol.classId.asString() diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/ResolvePhaseUtils.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/ResolvePhaseUtils.kt index 577e500c936..8c3d7c207de 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/ResolvePhaseUtils.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/ResolvePhaseUtils.kt @@ -10,6 +10,7 @@ import org.jetbrains.kotlin.fir.declarations.FirResolvePhase.* import org.jetbrains.kotlin.fir.resolve.ScopeSession import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirBodyResolveTransformerAdapter import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirImplicitTypeBodyResolveTransformerAdapter +import org.jetbrains.kotlin.fir.resolve.transformers.contracts.FirContractResolveTransformerAdapter import org.jetbrains.kotlin.fir.visitors.FirTransformer // TODO: add FirSession parameter @@ -22,6 +23,7 @@ fun FirResolvePhase.createTransformerByPhase(scopeSession: ScopeSession): FirTra SEALED_CLASS_INHERITORS -> FirSealedClassInheritorsTransformer() TYPES -> FirTypeResolveTransformerAdapter(scopeSession) STATUS -> FirStatusResolveTransformerAdapter() + CONTRACTS -> FirContractResolveTransformerAdapter(scopeSession) IMPLICIT_TYPES_BODY_RESOLVE -> FirImplicitTypeBodyResolveTransformerAdapter(scopeSession) BODY_RESOLVE -> FirBodyResolveTransformerAdapter(scopeSession) } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirBodyResolveTransformer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirBodyResolveTransformer.kt index 88aaaabe81e..d87dc95813c 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirBodyResolveTransformer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirBodyResolveTransformer.kt @@ -38,7 +38,7 @@ open class FirBodyResolveTransformer( BodyResolveTransformerComponents(session, scopeSession, this, context) private val expressionsTransformer = FirExpressionsResolveTransformer(this) - private val declarationsTransformer = FirDeclarationsResolveTransformer(this) + protected open val declarationsTransformer = FirDeclarationsResolveTransformer(this) private val controlFlowStatementsTransformer = FirControlFlowStatementsResolveTransformer(this) override fun transformFile(file: FirFile, data: ResolutionMode): CompositeTransformResult { diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirDeclarationsResolveTransformer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirDeclarationsResolveTransformer.kt index b632f272da9..81e3dffb0cf 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirDeclarationsResolveTransformer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirDeclarationsResolveTransformer.kt @@ -34,7 +34,7 @@ import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef import org.jetbrains.kotlin.fir.visitors.* import org.jetbrains.kotlin.name.Name -class FirDeclarationsResolveTransformer(transformer: FirBodyResolveTransformer) : FirPartialBodyResolveTransformer(transformer) { +open class FirDeclarationsResolveTransformer(transformer: FirBodyResolveTransformer) : FirPartialBodyResolveTransformer(transformer) { private var primaryConstructorParametersScope: FirLocalScope? = null private var containingClass: FirRegularClass? = null @@ -66,7 +66,7 @@ class FirDeclarationsResolveTransformer(transformer: FirBodyResolveTransformer) } } - private inline fun withTypeParametersOf(declaration: FirMemberDeclaration, crossinline l: () -> T): T { + protected inline fun withTypeParametersOf(declaration: FirMemberDeclaration, crossinline l: () -> T): T { if (declaration.typeParameters.isEmpty()) return l() for (typeParameter in declaration.typeParameters) { @@ -670,7 +670,7 @@ class FirDeclarationsResolveTransformer(transformer: FirBodyResolveTransformer) return this } - private inline fun withLabelAndReceiverType( + protected inline fun withLabelAndReceiverType( labelName: Name?, owner: FirDeclaration, type: ConeKotlinType?, diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/contracts/ConeEffectExtractor.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/contracts/ConeEffectExtractor.kt new file mode 100644 index 00000000000..b1670f03e88 --- /dev/null +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/contracts/ConeEffectExtractor.kt @@ -0,0 +1,179 @@ +/* + * 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.resolve.transformers.contracts + +import org.jetbrains.kotlin.contracts.description.InvocationKind +import org.jetbrains.kotlin.fir.FirElement +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.contracts.description.* +import org.jetbrains.kotlin.fir.declarations.FirContractDescriptionOwner +import org.jetbrains.kotlin.fir.declarations.FirValueParameter +import org.jetbrains.kotlin.fir.expressions.* +import org.jetbrains.kotlin.fir.types.ConeKotlinType +import org.jetbrains.kotlin.fir.types.coneTypeSafe +import org.jetbrains.kotlin.fir.types.coneTypeUnsafe +import org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor + +class ConeEffectExtractor( + private val session: FirSession, + private val owner: FirContractDescriptionOwner, + private val valueParameters: List +) : FirDefaultVisitor() { + companion object { + private val BOOLEAN_AND = FirContractsDslNames.id("kotlin", "Boolean", "and") + private val BOOLEAN_OR = FirContractsDslNames.id("kotlin", "Boolean", "or") + private val BOOLEAN_NOT = FirContractsDslNames.id("kotlin", "Boolean", "not") + } + + override fun visitElement(element: FirElement, data: Nothing?): ConeContractDescriptionElement? { + return null + } + + override fun visitReturnExpression(returnExpression: FirReturnExpression, data: Nothing?): ConeContractDescriptionElement? { + return returnExpression.result.accept(this, data) + } + + override fun visitFunctionCall(functionCall: FirFunctionCall, data: Nothing?): ConeContractDescriptionElement? { + val resolvedId = functionCall.toResolvedCallableSymbol()?.callableId ?: return null + return when (resolvedId) { + FirContractsDslNames.IMPLIES -> { + val effect = functionCall.explicitReceiver?.accept(this, null) as? ConeEffectDeclaration + ?: return null + val condition = functionCall.argument.accept(this, null) as? ConeBooleanExpression + ?: return null + ConeConditionalEffectDeclaration(effect, condition) + } + + FirContractsDslNames.RETURNS -> { + val argument = functionCall.arguments.firstOrNull() + val value = if (argument == null) { + ConeConstantReference.WILDCARD + } else { + argument.accept(this, null) as? ConeConstantReference + ?: return null + } + ConeReturnsEffectDeclaration(value) + } + + FirContractsDslNames.RETURNS_NOT_NULL -> { + ConeReturnsEffectDeclaration(ConeConstantReference.NULL) + } + + FirContractsDslNames.CALLS_IN_PLACE -> { + val reference = functionCall.arguments[0].accept(this, null) as? ConeValueParameterReference + ?: return null + val kind = functionCall.arguments.getOrNull(1)?.parseInvocationKind() ?: InvocationKind.UNKNOWN + ConeCallsEffectDeclaration(reference, kind) + } + + BOOLEAN_AND, BOOLEAN_OR -> { + val left = functionCall.explicitReceiver?.accept(this, null) as? ConeBooleanExpression ?: return null + val right = functionCall.argument.accept(this, null) as? ConeBooleanExpression ?: return null + val kind = when (resolvedId) { + BOOLEAN_AND -> LogicOperationKind.AND + BOOLEAN_OR -> LogicOperationKind.OR + else -> throw IllegalStateException() + } + ConeBinaryLogicExpression(left, right, kind) + } + + BOOLEAN_NOT -> { + val arg = functionCall.explicitReceiver?.accept(this, null) as? ConeBooleanExpression ?: return null + ConeLogicalNot(arg) + } + else -> null + } + } + + override fun visitBinaryLogicExpression( + binaryLogicExpression: FirBinaryLogicExpression, + data: Nothing? + ): ConeContractDescriptionElement? { + val left = binaryLogicExpression.leftOperand.accept(this, null) as? ConeBooleanExpression ?: return null + val right = binaryLogicExpression.rightOperand.accept(this, null) as? ConeBooleanExpression ?: return null + return ConeBinaryLogicExpression(left, right, binaryLogicExpression.kind) + } + + override fun visitOperatorCall(operatorCall: FirOperatorCall, data: Nothing?): ConeContractDescriptionElement? { + val isNegated = when (operatorCall.operation) { + FirOperation.EQ -> false + FirOperation.NOT_EQ -> true + else -> return null + } + val const = operatorCall.arguments[1] as? FirConstExpression<*> ?: return null + if (const.kind != FirConstKind.Null) return null + val arg = operatorCall.arguments[0].accept(this, null) as? ConeValueParameterReference ?: return null + return ConeIsNullPredicate(arg, isNegated) + } + + override fun visitQualifiedAccessExpression( + qualifiedAccessExpression: FirQualifiedAccessExpression, + data: Nothing? + ): ConeContractDescriptionElement? { + val symbol = qualifiedAccessExpression.toResolvedCallableSymbol() ?: return null + val parameter = symbol.fir as? FirValueParameter ?: return null + val index = valueParameters.indexOf(parameter).takeUnless { it < 0 } ?: return null + val type = parameter.returnTypeRef.coneTypeUnsafe() + + val name = parameter.name.asString() + return toValueParameterReference(type, index, name) + } + + private fun toValueParameterReference( + type: ConeKotlinType, + index: Int, + name: String + ): ConeValueParameterReference { + return if (type == session.builtinTypes.booleanType.type) { + ConeBooleanValueParameterReference(index, name) + } else { + ConeValueParameterReference(index, name) + } + } + + override fun visitThisReceiverExpression( + thisReceiverExpression: FirThisReceiverExpression, + data: Nothing? + ): ConeContractDescriptionElement? { + val declaration = thisReceiverExpression.calleeReference.boundSymbol?.fir ?: return null + return if (declaration == owner) { + val type = thisReceiverExpression.typeRef.coneTypeSafe() ?: return null + toValueParameterReference(type, -1, "this") + } else { + null + } + } + + override fun visitConstExpression(constExpression: FirConstExpression, data: Nothing?): ConeContractDescriptionElement? { + return when (constExpression.kind) { + FirConstKind.Null -> ConeConstantReference.NULL + FirConstKind.Boolean -> when (constExpression.value as Boolean) { + true -> ConeBooleanConstantReference.TRUE + false -> ConeBooleanConstantReference.FALSE + } + else -> null + } + } + + override fun visitTypeOperatorCall(typeOperatorCall: FirTypeOperatorCall, data: Nothing?): ConeContractDescriptionElement? { + val arg = typeOperatorCall.argument.accept(this, data) as? ConeValueParameterReference ?: return null + val type = typeOperatorCall.conversionTypeRef.coneTypeSafe() ?: return null + val isNegated = typeOperatorCall.operation == FirOperation.NOT_IS + return ConeIsInstancePredicate(arg, type, isNegated) + } + + private fun FirExpression.parseInvocationKind(): InvocationKind? { + if (this !is FirQualifiedAccessExpression) return null + val resolvedId = toResolvedCallableSymbol()?.callableId ?: return null + return when (resolvedId) { + FirContractsDslNames.EXACTLY_ONCE_KIND -> InvocationKind.EXACTLY_ONCE + FirContractsDslNames.AT_LEAST_ONCE_KIND -> InvocationKind.AT_LEAST_ONCE + FirContractsDslNames.AT_MOST_ONCE_KIND -> InvocationKind.AT_MOST_ONCE + FirContractsDslNames.UNKNOWN_KIND -> InvocationKind.UNKNOWN + else -> null + } + } +} \ No newline at end of file diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/contracts/FirContractResolveTransformer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/contracts/FirContractResolveTransformer.kt new file mode 100644 index 00000000000..3a324ad6f88 --- /dev/null +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/contracts/FirContractResolveTransformer.kt @@ -0,0 +1,139 @@ +/* + * 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.resolve.transformers.contracts + +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.contracts.FirRawContractDescription +import org.jetbrains.kotlin.fir.contracts.builder.buildResolvedContractDescription +import org.jetbrains.kotlin.fir.contracts.description.ConeEffectDeclaration +import org.jetbrains.kotlin.fir.contracts.impl.FirEmptyContractDescription +import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.errorTypeFromPrototype +import org.jetbrains.kotlin.fir.expressions.* +import org.jetbrains.kotlin.fir.resolve.ResolutionMode +import org.jetbrains.kotlin.fir.resolve.ScopeSession +import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeContractDescriptionError +import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirBodyResolveTransformer +import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer +import org.jetbrains.kotlin.fir.scopes.impl.FirLocalScope +import org.jetbrains.kotlin.fir.types.coneTypeUnsafe +import org.jetbrains.kotlin.fir.visitors.CompositeTransformResult +import org.jetbrains.kotlin.fir.visitors.compose +import org.jetbrains.kotlin.fir.visitors.transformSingle + +class FirContractResolveTransformer(session: FirSession, scopeSession: ScopeSession) : FirBodyResolveTransformer( + session, + FirResolvePhase.CONTRACTS, + implicitTypeOnly = false, + scopeSession +) { + override val declarationsTransformer: FirDeclarationsResolveTransformer = FirDeclarationsContractResolveTransformer(this) + + private class FirDeclarationsContractResolveTransformer(transformer: FirBodyResolveTransformer) : FirDeclarationsResolveTransformer(transformer) { + override fun transformSimpleFunction( + simpleFunction: FirSimpleFunction, + data: ResolutionMode + ): CompositeTransformResult { + if (!simpleFunction.hasContractToResolve) { + simpleFunction.replaceResolvePhase(FirResolvePhase.CONTRACTS) + return simpleFunction.compose() + } + val containingDeclaration = context.containerIfAny + if (containingDeclaration != null && containingDeclaration !is FirClass<*>) { + simpleFunction.replaceResolvePhase(FirResolvePhase.CONTRACTS) + simpleFunction.replaceReturnTypeRef( + simpleFunction.returnTypeRef.errorTypeFromPrototype( + ConeContractDescriptionError("Local function can not be used in contract description") + ) + ) + return simpleFunction.compose() + } + @Suppress("UNCHECKED_CAST") + return withTypeParametersOf(simpleFunction) { + val receiverTypeRef = simpleFunction.receiverTypeRef + if (receiverTypeRef != null) { + withLabelAndReceiverType(simpleFunction.name, simpleFunction, receiverTypeRef.coneTypeUnsafe()) { + transformContractDescriptionOwner(simpleFunction) + } + } else { + transformContractDescriptionOwner(simpleFunction) + } + } + } + + private fun transformContractDescriptionOwner( + owner: T + ): CompositeTransformResult { + dataFlowAnalyzer.enterContractDescription() + val contractDescription = owner.contractDescription + require(contractDescription is FirRawContractDescription) + val valueParameters = owner.valueParameters + val contractCall = withLocalScopeCleanup { + addLocalScope(FirLocalScope()) + for (valueParameter in valueParameters) { + context.storeVariable(valueParameter) + } + context.withContainer(owner as FirDeclaration) { + contractDescription.contractCall.transformSingle(transformer, ResolutionMode.ContextIndependent) + } + } + val resolvedId = contractCall.toResolvedCallableSymbol()?.callableId ?: return transformOwnerWithUnresolvedContract(owner) + if (resolvedId != FirContractsDslNames.CONTRACT) return transformOwnerWithUnresolvedContract(owner) + if (contractCall.arguments.size != 1) return transformOwnerOfErrorContract(owner) + val argument = contractCall.argument as? FirLambdaArgumentExpression ?: return transformOwnerOfErrorContract(owner) + val lambdaBody = (argument.expression as FirAnonymousFunction).body ?: return transformOwnerOfErrorContract(owner) + + val resolvedContractDescription = buildResolvedContractDescription { + val effectExtractor = ConeEffectExtractor(session, owner, valueParameters) + for (statement in lambdaBody.statements) { + val effect = statement.accept(effectExtractor, null) as? ConeEffectDeclaration + if (effect == null) { + unresolvedEffects += statement + } else { + effects += effect + } + } + } + owner.replaceContractDescription(resolvedContractDescription) + dataFlowAnalyzer.exitContractDescription() + return owner.compose() + } + + private fun transformOwnerWithUnresolvedContract(owner: T): CompositeTransformResult { + val contractDescription = owner.contractDescription as FirRawContractDescription + val functionCall = contractDescription.contractCall + owner.replaceContractDescription(FirEmptyContractDescription) + owner.body.replaceFirstStatement(functionCall) + dataFlowAnalyzer.exitContractDescription() + return owner.compose() + } + + private fun transformOwnerOfErrorContract(owner: T): CompositeTransformResult { + // TODO + dataFlowAnalyzer.exitContractDescription() + return owner.compose() + } + + private val FirContractDescriptionOwner.hasContractToResolve: Boolean + get() = contractDescription is FirRawContractDescription + } +} + +private val FirContractDescriptionOwner.valueParameters: List + get() = when (this) { + is FirSimpleFunction -> valueParameters + is FirPropertyAccessor -> valueParameters + else -> error() + } + +private val FirContractDescriptionOwner.body: FirBlock + get() = when (this) { + is FirSimpleFunction -> body!! + is FirPropertyAccessor -> body!! + else -> error() + } + +private fun FirContractDescriptionOwner.error(): Nothing = throw IllegalStateException("${this::class} can not be a contract owner") diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/contracts/FirContractResolveTransformerAdapter.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/contracts/FirContractResolveTransformerAdapter.kt new file mode 100644 index 00000000000..217da0afd43 --- /dev/null +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/contracts/FirContractResolveTransformerAdapter.kt @@ -0,0 +1,31 @@ +/* + * 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.resolve.transformers.contracts + +import org.jetbrains.kotlin.fir.FirElement +import org.jetbrains.kotlin.fir.declarations.FirDeclaration +import org.jetbrains.kotlin.fir.declarations.FirFile +import org.jetbrains.kotlin.fir.resolve.ResolutionMode +import org.jetbrains.kotlin.fir.resolve.ScopeSession +import org.jetbrains.kotlin.fir.resolve.transformers.AdapterForResolvePhase +import org.jetbrains.kotlin.fir.visitors.CompositeTransformResult +import org.jetbrains.kotlin.fir.visitors.FirTransformer +import org.jetbrains.kotlin.fir.visitors.compose + +@AdapterForResolvePhase +class FirContractResolveTransformerAdapter(private val scopeSession: ScopeSession) : FirTransformer() { + override fun transformElement(element: E, data: Nothing?): CompositeTransformResult { + return element.compose() + } + + override fun transformFile(file: FirFile, data: Nothing?): CompositeTransformResult { + val transformer = FirContractResolveTransformer( + file.session, + scopeSession + ) + return file.transform(transformer, ResolutionMode.ContextIndependent) + } +} \ No newline at end of file diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/contracts/FirContractsDslNames.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/contracts/FirContractsDslNames.kt new file mode 100644 index 00000000000..3d5252a8dcc --- /dev/null +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/contracts/FirContractsDslNames.kt @@ -0,0 +1,56 @@ +/* + * 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.resolve.transformers.contracts + +import org.jetbrains.kotlin.fir.symbols.CallableId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name + +object FirContractsDslNames { + // Internal marker-annotation for distinguishing our API + val CONTRACTS_DSL_ANNOTATION_FQN = id("kotlin.internal", "ContractsDsl") + + // Types + val EFFECT = id("Effect") + val CONDITIONAL_EFFECT = id("ConditionalEffect") + val SIMPLE_EFFECT = id("SimpleEffect") + val RETURNS_EFFECT = id("Returns") + val RETURNS_NOT_NULL_EFFECT = id("ReturnsNotNull") + val CALLS_IN_PLACE_EFFECT = id("CallsInPlace") + + // Structure-defining calls + val CONTRACT = id("contract") + val IMPLIES = simpleEffect("implies") + + // Effect-declaration calls + val RETURNS = contractBuilder("returns") + val RETURNS_NOT_NULL = contractBuilder("returnsNotNull") + val CALLS_IN_PLACE = contractBuilder("callsInPlace") + + // enum class InvocationKind + val INVOCATION_KIND_ENUM = id("InvocationKind") + val EXACTLY_ONCE_KIND = invocationKind("EXACTLY_ONCE") + val AT_LEAST_ONCE_KIND = invocationKind("AT_LEAST_ONCE") + val UNKNOWN_KIND = invocationKind("UNKNOWN") + val AT_MOST_ONCE_KIND = invocationKind("AT_MOST_ONCE") + + private const val CONTRACT_BUILDER = "ContractBuilder" + + private fun contractBuilder(name: String): CallableId = id(CONTRACT_PACKAGE, CONTRACT_BUILDER, name) + private fun invocationKind(name: String): CallableId = id(CONTRACT_PACKAGE, INVOCATION_KIND_ENUM.callableName.asString(), name) + private fun simpleEffect(name: String): CallableId = id(CONTRACT_PACKAGE, SIMPLE_EFFECT.callableName.asString(), name) + private fun id(name: String): CallableId = id(CONTRACT_PACKAGE, name) + private fun id(packageName: String, name: String): CallableId = id(packageName, className = null, name) + internal fun id(packageName: String, className: String?, name: String): CallableId { + return CallableId( + FqName(packageName), + className?.let { FqName(it) }, + Name.identifier(name) + ) + } + + private const val CONTRACT_PACKAGE = "kotlin.contracts" +} \ No newline at end of file diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirResolvePhase.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirResolvePhase.kt index bd62e6a6272..1732a3677af 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirResolvePhase.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirResolvePhase.kt @@ -12,6 +12,7 @@ enum class FirResolvePhase { SEALED_CLASS_INHERITORS, TYPES, STATUS, + CONTRACTS, IMPLICIT_TYPES_BODY_RESOLVE, BODY_RESOLVE;