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 482f792f582..5621db659a7 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 @@ -7,9 +7,8 @@ package org.jetbrains.kotlin.fir.resolve.dfa import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.FirSymbolOwner -import org.jetbrains.kotlin.fir.declarations.FirAnonymousInitializer -import org.jetbrains.kotlin.fir.declarations.FirFunction -import org.jetbrains.kotlin.fir.declarations.FirProperty +import org.jetbrains.kotlin.fir.contracts.description.* +import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.expressions.impl.FirThisReceiverExpressionImpl import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference @@ -18,6 +17,8 @@ import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents import org.jetbrains.kotlin.fir.resolve.ImplicitReceiverStackImpl import org.jetbrains.kotlin.fir.resolve.dfa.Condition.* import org.jetbrains.kotlin.fir.resolve.dfa.cfg.* +import org.jetbrains.kotlin.fir.resolve.dfa.contracts.buildContractFir +import org.jetbrains.kotlin.fir.resolve.dfa.contracts.createArgumentsMapping import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformer import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol import org.jetbrains.kotlin.fir.symbols.CallableId @@ -26,13 +27,14 @@ 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.types.isMarkedNullable +import org.jetbrains.kotlin.fir.visitors.transformSingle import org.jetbrains.kotlin.ir.expressions.IrConstKind import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addToStdlib.safeAs -class FirDataFlowAnalyzer(components: FirAbstractBodyResolveTransformer.BodyResolveTransformerComponents) : BodyResolveComponents by components { +class FirDataFlowAnalyzer(private val components: FirAbstractBodyResolveTransformer.BodyResolveTransformerComponents) : BodyResolveComponents by components { companion object { private val KOTLIN_BOOLEAN_NOT = CallableId(FqName("kotlin"), FqName("Boolean"), Name.identifier("not")) } @@ -470,8 +472,55 @@ class FirDataFlowAnalyzer(components: FirAbstractBodyResolveTransformer.BodyReso exitBooleanNot(functionCall, node) return } + processConditionalContract(functionCall) } + private fun processConditionalContract(functionCall: FirFunctionCall) { + val contractDescription = (functionCall.resolvedSymbol?.fir as? FirSimpleFunction)?.contractDescription ?: return + val conditionalEffects = contractDescription.effects.filterIsInstance() + if (conditionalEffects.isEmpty()) return + val argumentsMapping = createArgumentsMapping(functionCall) ?: return + graphBuilder.enterContract(functionCall).mergeIncomingFlow() + val functionCallVariable = getOrCreateVariable(functionCall) + for (conditionalEffect in conditionalEffects) { + val fir = conditionalEffect.buildContractFir(argumentsMapping) ?: continue + val effect = conditionalEffect.effect as? ConeReturnsEffectDeclaration ?: continue + fir.transformSingle(components.transformer, null) + val argumentVariable = getOrCreateVariable(fir) + val lastNode = graphBuilder.lastNode + when (val value = effect.value) { + ConeConstantReference.WILDCARD -> { + lastNode.flow = logicSystem.approveFactsInsideFlow( + argumentVariable, + EqTrue, + lastNode.flow, + shouldForkFlow = false, + shouldRemoveSynthetics = true + ) + } + + ConeBooleanConstantReference.TRUE, ConeBooleanConstantReference.FALSE -> { + logicSystem.changeVariableForConditionFlow(lastNode.flow, argumentVariable, functionCallVariable) { + it.takeIf { it.condition == if (value == ConeBooleanConstantReference.TRUE) EqTrue else EqFalse } + } + } + + ConeConstantReference.NOT_NULL, ConeConstantReference.NULL -> { + logicSystem.changeVariableForConditionFlow(lastNode.flow, argumentVariable, functionCallVariable) { + it.takeIf { it.condition == EqTrue }?.let { + val condition = if (value == ConeConstantReference.NOT_NULL) Condition.NotEqNull else NotEqNull + ConditionalFirDataFlowInfo(condition, it.variable, it.info) + } + } + } + + else -> throw IllegalArgumentException(value.toString()) + } + } + graphBuilder.exitContract(functionCall).mergeIncomingFlow() + } + + private val FirElement.resolvedSymbol: AbstractFirBasedSymbol<*>? get() { return when (this) { diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt index 6d76552c2c1..edfe84a6e83 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt @@ -47,11 +47,11 @@ abstract class LogicSystem(private val context: DataFlowInferenceContext) { flow: Flow, sourceVariable: DataFlowVariable, newVariable: DataFlowVariable, - transform: ((ConditionalFirDataFlowInfo) -> ConditionalFirDataFlowInfo)? = null + transform: ((ConditionalFirDataFlowInfo) -> ConditionalFirDataFlowInfo?)? = null ) { var infos = flow.getConditionalInfos(sourceVariable) if (transform != null) { - infos = infos.map(transform) + infos = infos.mapNotNull(transform) } flow.conditionalInfos.putAll(newVariable, infos) if (sourceVariable.isSynthetic()) { 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 8394f5d62e5..83280950941 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 @@ -156,6 +156,9 @@ class StubNode(owner: ControlFlowGraph, level: Int) : CFGNode(owner, le class VariableDeclarationNode(owner: ControlFlowGraph, override val fir: FirProperty, level: Int) : CFGNode(owner, level) class VariableAssignmentNode(owner: ControlFlowGraph, override val fir: FirVariableAssignment, level: Int) : CFGNode(owner, level) +class EnterContractNode(owner: ControlFlowGraph, override val fir: FirFunctionCall, level: Int) : CFGNode(owner, level), EnterNode +class ExitContractNode(owner: ControlFlowGraph, override val fir: FirFunctionCall, level: Int) : CFGNode(owner, level), ExitNode + // ----------------------------------- Other ----------------------------------- class AnnotationEnterNode(owner: ControlFlowGraph, override val fir: FirAnnotationCall, level: Int) : CFGNode(owner, level), EnterNode 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 ff491c11dfb..d1fb3a71234 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 @@ -391,6 +391,14 @@ class ControlFlowGraphBuilder { return leftExitNode to rightExitNode } + fun enterContract(functionCall: FirFunctionCall): EnterContractNode { + return createEnterContractNode(functionCall).also { addNewSimpleNode(it) } + } + + fun exitContract(functionCall: FirFunctionCall): ExitContractNode { + return createExitContractNode(functionCall).also { addNewSimpleNode(it) } + } + fun exitBinaryOr(binaryLogicExpression: FirBinaryLogicExpression): BinaryOrExitNode { assert(binaryLogicExpression.kind == LogicOperationKind.OR) levelCounter-- 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 82efee710a3..e3df5036791 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 @@ -121,6 +121,12 @@ fun ControlFlowGraphBuilder.createAnnotationEnterNode(fir: FirAnnotationCall): A fun ControlFlowGraphBuilder.createVariableDeclarationNode(fir: FirProperty): VariableDeclarationNode = VariableDeclarationNode(graph, fir, levelCounter) +fun ControlFlowGraphBuilder.createExitContractNode(fir: FirFunctionCall): ExitContractNode = + ExitContractNode(graph, fir, levelCounter) + +fun ControlFlowGraphBuilder.createEnterContractNode(fir: FirFunctionCall): EnterContractNode = + EnterContractNode(graph, fir, levelCounter) + fun ControlFlowGraphBuilder.createConstExpressionNode(fir: FirConstExpression<*>): ConstExpressionNode = ConstExpressionNode(graph, fir, levelCounter) diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphRenderer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphRenderer.kt index 10619e9e8a9..6fb63f69820 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphRenderer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphRenderer.kt @@ -145,6 +145,9 @@ fun CFGNode<*>.render(): String = is AnnotationEnterNode -> "Enter annotation" is AnnotationExitNode -> "Exit annotation" + is EnterContractNode -> "Enter contract" + is ExitContractNode -> "Exit contract" + else -> TODO(this@render.toString()) } ) diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/contracts/ConeConditionalEffectToFirVisitor.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/contracts/ConeConditionalEffectToFirVisitor.kt new file mode 100644 index 00000000000..3060802ca2e --- /dev/null +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/contracts/ConeConditionalEffectToFirVisitor.kt @@ -0,0 +1,98 @@ +/* + * 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.resolve.dfa.contracts + +import org.jetbrains.kotlin.fir.contracts.description.* +import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction +import org.jetbrains.kotlin.fir.expressions.* +import org.jetbrains.kotlin.fir.expressions.impl.* +import org.jetbrains.kotlin.fir.references.impl.FirSimpleNamedReference +import org.jetbrains.kotlin.fir.types.impl.FirResolvedTypeRefImpl +import org.jetbrains.kotlin.ir.expressions.IrConstKind +import org.jetbrains.kotlin.util.OperatorNameConventions + +private object ConeConditionalEffectToFirVisitor : ConeContractDescriptionVisitor>() { + override fun visitConditionalEffectDeclaration(conditionalEffect: ConeConditionalEffectDeclaration, data: Map): FirExpression? { + return conditionalEffect.condition.accept(this, data) + } + + override fun visitConstantDescriptor(constantReference: ConeConstantReference, data: Map): FirExpression? { + return when (constantReference) { + ConeBooleanConstantReference.TRUE -> FirConstExpressionImpl(null, IrConstKind.Boolean, true) + ConeBooleanConstantReference.FALSE -> FirConstExpressionImpl(null, IrConstKind.Boolean, false) + ConeConstantReference.NULL -> createConstNull() + else -> null + } + } + + override fun visitLogicalBinaryOperationContractExpression( + binaryLogicExpression: ConeBinaryLogicExpression, + data: Map + ): FirExpression? { + val leftExpression = binaryLogicExpression.left.accept(this, data) ?: return null + val rightExpression = binaryLogicExpression.right.accept(this, data) ?: return null + return FirBinaryLogicExpressionImpl(null, leftExpression, rightExpression, binaryLogicExpression.kind) + } + + override fun visitLogicalNot(logicalNot: ConeLogicalNot, data: Map): FirExpression? { + val explicitReceiver = logicalNot.arg.accept(this, data) ?: return null + return FirFunctionCallImpl(null).apply { + calleeReference = FirSimpleNamedReference(null, OperatorNameConventions.NOT, null) + this.explicitReceiver = explicitReceiver + } + } + + override fun visitIsInstancePredicate(isInstancePredicate: ConeIsInstancePredicate, data: Map): FirExpression? { + val operation = if (isInstancePredicate.isNegated) { + FirOperation.NOT_IS + } else { + FirOperation.IS + } + return FirTypeOperatorCallImpl(null, operation, FirResolvedTypeRefImpl(null, isInstancePredicate.type)) + } + + override fun visitIsNullPredicate(isNullPredicate: ConeIsNullPredicate, data: Map): FirExpression? { + val argument = isNullPredicate.arg.accept(this, data) ?: return null + val operation = if (isNullPredicate.isNegated) { + FirOperation.NOT_EQ + } else { + FirOperation.EQ + } + return FirOperatorCallImpl(null, operation).apply { + arguments += argument + arguments += createConstNull() + } + } + + override fun visitValueParameterReference(valueParameterReference: ConeValueParameterReference, data: Map): FirExpression? { + return data[valueParameterReference.parameterIndex] + } + + private fun createConstNull(): FirConstExpression<*> = FirConstExpressionImpl(null, IrConstKind.Null, null) +} + +fun ConeConditionalEffectDeclaration.buildContractFir(argumentMapping: Map): FirExpression? { + return condition.accept(ConeConditionalEffectToFirVisitor, argumentMapping) +} + +fun createArgumentsMapping(functionCall: FirFunctionCall): Map? { + val function = functionCall.toResolvedCallableSymbol()?.fir as? FirSimpleFunction ?: return null + val argumentsMapping = mutableMapOf() + // TODO: process implicit receiver + // TODO: change to mapping from candidate + // problem: we have already resolved reference without candidate + functionCall.explicitReceiver?.let { argumentsMapping[-1] = it } + + + val nameToIndex = function.valueParameters.mapIndexed { index, parameter -> parameter.name to index }.toMap() + functionCall.arguments.forEachIndexed { index, argument -> + when (argument) { + is FirNamedArgumentExpression -> argumentsMapping[nameToIndex[argument.name]!!] = argument + else -> argumentsMapping[index] = argument + } + } + return argumentsMapping +} \ No newline at end of file diff --git a/compiler/fir/resolve/testData/resolve/stdlib/contracts/conditionalEffects.dot b/compiler/fir/resolve/testData/resolve/stdlib/contracts/conditionalEffects.dot new file mode 100644 index 00000000000..d2232e7d9d7 --- /dev/null +++ b/compiler/fir/resolve/testData/resolve/stdlib/contracts/conditionalEffects.dot @@ -0,0 +1,387 @@ +digraph conditionalEffects_kt { + graph [splines=ortho nodesep=3] + node [shape=box penwidth=2] + edge [penwidth=2] + + subgraph cluster_0 { + color=red + 0 [label="Enter function test_1" style="filled" fillcolor=red]; + subgraph cluster_1 { + color=blue + 1 [label="Enter block"]; + 2 [label="Access variable R|/x|"]; + 3 [label="Type operator: x is Int"]; + 4 [label="Function call: R|kotlin/require|((R|/x| is R|kotlin/Int|))"]; + subgraph cluster_2 { + color=blue + 5 [label="Enter contract"]; + 6 [label="Access variable R|/x|"]; + 7 [label="Type operator: x is Int"]; + 8 [label="Exit contract"]; + } + 9 [label="Access variable R|/x|"]; + 10 [label="Function call: R|/x|.R|kotlin/Int.inc|()"]; + 11 [label="Exit block"]; + } + 12 [label="Exit function test_1" style="filled" fillcolor=red]; + } + + 0 -> {1}; + 1 -> {2}; + 2 -> {3}; + 3 -> {4}; + 4 -> {5}; + 5 -> {6}; + 6 -> {7}; + 7 -> {8}; + 8 -> {9}; + 9 -> {10}; + 10 -> {11}; + 11 -> {12}; + + subgraph cluster_3 { + color=red + 13 [label="Enter function test_2" style="filled" fillcolor=red]; + subgraph cluster_4 { + color=blue + 14 [label="Enter block"]; + 15 [label="Access variable R|/x|"]; + 16 [label="Function call: R|kotlin/requireNotNull|(R|/x|)"]; + subgraph cluster_5 { + color=blue + 17 [label="Enter contract"]; + 18 [label="Access variable R|/x|"]; + 19 [label="Const: Null(null)"]; + 20 [label="Operator !="]; + 21 [label="Exit contract"]; + } + 22 [label="Access variable R|/x|"]; + 23 [label="Access variable R|kotlin/String.length|"]; + 24 [label="Exit block"]; + } + 25 [label="Exit function test_2" style="filled" fillcolor=red]; + } + + 13 -> {14}; + 14 -> {15}; + 15 -> {16}; + 16 -> {17}; + 17 -> {18}; + 18 -> {19}; + 19 -> {20}; + 20 -> {21}; + 21 -> {22}; + 22 -> {23}; + 23 -> {24}; + 24 -> {25}; + + subgraph cluster_6 { + color=red + 26 [label="Enter function test_3" style="filled" fillcolor=red]; + subgraph cluster_7 { + color=blue + 27 [label="Enter block"]; + 28 [label="Access variable R|/x|"]; + 29 [label="Const: Null(null)"]; + 30 [label="Operator !="]; + 31 [label="Function call: R|kotlin/require|(!=(R|/x|, Null(null)))"]; + subgraph cluster_8 { + color=blue + 32 [label="Enter contract"]; + 33 [label="Access variable R|/x|"]; + 34 [label="Const: Null(null)"]; + 35 [label="Operator !="]; + 36 [label="Exit contract"]; + } + 37 [label="Access variable R|/x|"]; + 38 [label="Access variable R|kotlin/String.length|"]; + 39 [label="Exit block"]; + } + 40 [label="Exit function test_3" style="filled" fillcolor=red]; + } + + 26 -> {27}; + 27 -> {28}; + 28 -> {29}; + 29 -> {30}; + 30 -> {31}; + 31 -> {32}; + 32 -> {33}; + 33 -> {34}; + 34 -> {35}; + 35 -> {36}; + 36 -> {37}; + 37 -> {38}; + 38 -> {39}; + 39 -> {40}; + + subgraph cluster_9 { + color=red + 41 [label="Enter function test_4" style="filled" fillcolor=red]; + subgraph cluster_10 { + color=blue + 42 [label="Enter block"]; + subgraph cluster_11 { + color=blue + 43 [label="Enter &&"]; + 44 [label="Access variable R|/x|"]; + 45 [label="Type operator: x is String"]; + 46 [label="Exit left part of &&"]; + 47 [label="Enter right part of &&"]; + 48 [label="Access variable R|/y|"]; + 49 [label="Const: Null(null)"]; + 50 [label="Operator !="]; + 51 [label="Exit &&"]; + } + 52 [label="Function call: R|kotlin/require|((R|/x| is R|kotlin/String|) && !=(R|/y|, Null(null)))"]; + subgraph cluster_12 { + color=blue + 53 [label="Enter contract"]; + subgraph cluster_13 { + color=blue + 54 [label="Enter &&"]; + 55 [label="Access variable R|/x|"]; + 56 [label="Type operator: x is String"]; + 57 [label="Exit left part of &&"]; + 58 [label="Enter right part of &&"]; + 59 [label="Access variable R|/y|"]; + 60 [label="Const: Null(null)"]; + 61 [label="Operator !="]; + 62 [label="Exit &&"]; + } + 63 [label="Exit contract"]; + } + 64 [label="Access variable R|/x|"]; + 65 [label="Access variable R|kotlin/String.length|"]; + 66 [label="Access variable R|/y|"]; + 67 [label="Access variable R|kotlin/String.length|"]; + 68 [label="Exit block"]; + } + 69 [label="Exit function test_4" style="filled" fillcolor=red]; + } + + 41 -> {42}; + 42 -> {43}; + 43 -> {44}; + 44 -> {45}; + 45 -> {46}; + 46 -> {51 47}; + 47 -> {48}; + 48 -> {49}; + 49 -> {50}; + 50 -> {51}; + 51 -> {52}; + 52 -> {53}; + 53 -> {54}; + 54 -> {55}; + 55 -> {56}; + 56 -> {57}; + 57 -> {62 58}; + 58 -> {59}; + 59 -> {60}; + 60 -> {61}; + 61 -> {62}; + 62 -> {63}; + 63 -> {64}; + 64 -> {65}; + 65 -> {66}; + 66 -> {67}; + 67 -> {68}; + 68 -> {69}; + + subgraph cluster_14 { + color=red + 70 [label="Enter function test_5" style="filled" fillcolor=red]; + subgraph cluster_15 { + color=blue + 71 [label="Enter block"]; + subgraph cluster_16 { + color=blue + 72 [label="Enter when"]; + subgraph cluster_17 { + color=blue + 73 [label="Enter when branch condition "]; + 74 [label="Access variable R|/b|"]; + 75 [label="Exit when branch condition"]; + } + subgraph cluster_18 { + color=blue + 76 [label="Enter when branch condition else"]; + 77 [label="Exit when branch condition"]; + } + 78 [label="Enter when branch result"]; + subgraph cluster_19 { + color=blue + 79 [label="Enter block"]; + 80 [label="Access variable R|/x|"]; + 81 [label="Access variable #"]; + 82 [label="Exit block"]; + } + 83 [label="Exit when branch result"]; + 84 [label="Enter when branch result"]; + subgraph cluster_20 { + color=blue + 85 [label="Enter block"]; + 86 [label="Access variable R|/x|"]; + 87 [label="Type operator: x is String"]; + 88 [label="Function call: R|kotlin/require|((R|/x| is R|kotlin/String|))"]; + subgraph cluster_21 { + color=blue + 89 [label="Enter contract"]; + 90 [label="Access variable R|/x|"]; + 91 [label="Type operator: x is String"]; + 92 [label="Exit contract"]; + } + 93 [label="Access variable R|/x|"]; + 94 [label="Access variable R|kotlin/String.length|"]; + 95 [label="Exit block"]; + } + 96 [label="Exit when branch result"]; + 97 [label="Exit when"]; + } + 98 [label="Access variable R|/x|"]; + 99 [label="Access variable #"]; + 100 [label="Exit block"]; + } + 101 [label="Exit function test_5" style="filled" fillcolor=red]; + } + + 70 -> {71}; + 71 -> {72}; + 72 -> {73}; + 73 -> {74}; + 74 -> {75}; + 75 -> {84 76}; + 76 -> {77}; + 77 -> {78}; + 78 -> {79}; + 79 -> {80}; + 80 -> {81}; + 81 -> {82}; + 82 -> {83}; + 83 -> {97}; + 84 -> {85}; + 85 -> {86}; + 86 -> {87}; + 87 -> {88}; + 88 -> {89}; + 89 -> {90}; + 90 -> {91}; + 91 -> {92}; + 92 -> {93}; + 93 -> {94}; + 94 -> {95}; + 95 -> {96}; + 96 -> {97}; + 97 -> {98}; + 98 -> {99}; + 99 -> {100}; + 100 -> {101}; + + subgraph cluster_22 { + color=red + 102 [label="Enter function test_6" style="filled" fillcolor=red]; + subgraph cluster_23 { + color=blue + 103 [label="Enter block"]; + subgraph cluster_24 { + color=blue + 104 [label="Enter when"]; + subgraph cluster_25 { + color=blue + 105 [label="Enter when branch condition "]; + 106 [label="Access variable R|/b|"]; + 107 [label="Exit when branch condition"]; + } + subgraph cluster_26 { + color=blue + 108 [label="Enter when branch condition else"]; + 109 [label="Exit when branch condition"]; + } + 110 [label="Enter when branch result"]; + subgraph cluster_27 { + color=blue + 111 [label="Enter block"]; + 112 [label="Access variable R|/x|"]; + 113 [label="Type operator: x is String"]; + 114 [label="Function call: R|kotlin/require|((R|/x| is R|kotlin/String|))"]; + subgraph cluster_28 { + color=blue + 115 [label="Enter contract"]; + 116 [label="Access variable R|/x|"]; + 117 [label="Type operator: x is String"]; + 118 [label="Exit contract"]; + } + 119 [label="Access variable R|/x|"]; + 120 [label="Access variable R|kotlin/String.length|"]; + 121 [label="Exit block"]; + } + 122 [label="Exit when branch result"]; + 123 [label="Enter when branch result"]; + subgraph cluster_29 { + color=blue + 124 [label="Enter block"]; + 125 [label="Access variable R|/x|"]; + 126 [label="Type operator: x is String"]; + 127 [label="Function call: R|kotlin/require|((R|/x| is R|kotlin/String|))"]; + subgraph cluster_30 { + color=blue + 128 [label="Enter contract"]; + 129 [label="Access variable R|/x|"]; + 130 [label="Type operator: x is String"]; + 131 [label="Exit contract"]; + } + 132 [label="Access variable R|/x|"]; + 133 [label="Access variable R|kotlin/String.length|"]; + 134 [label="Exit block"]; + } + 135 [label="Exit when branch result"]; + 136 [label="Exit when"]; + } + 137 [label="Access variable R|/x|"]; + 138 [label="Access variable R|kotlin/String.length|"]; + 139 [label="Exit block"]; + } + 140 [label="Exit function test_6" style="filled" fillcolor=red]; + } + + 102 -> {103}; + 103 -> {104}; + 104 -> {105}; + 105 -> {106}; + 106 -> {107}; + 107 -> {123 108}; + 108 -> {109}; + 109 -> {110}; + 110 -> {111}; + 111 -> {112}; + 112 -> {113}; + 113 -> {114}; + 114 -> {115}; + 115 -> {116}; + 116 -> {117}; + 117 -> {118}; + 118 -> {119}; + 119 -> {120}; + 120 -> {121}; + 121 -> {122}; + 122 -> {136}; + 123 -> {124}; + 124 -> {125}; + 125 -> {126}; + 126 -> {127}; + 127 -> {128}; + 128 -> {129}; + 129 -> {130}; + 130 -> {131}; + 131 -> {132}; + 132 -> {133}; + 133 -> {134}; + 134 -> {135}; + 135 -> {136}; + 136 -> {137}; + 137 -> {138}; + 138 -> {139}; + 139 -> {140}; + +} diff --git a/compiler/fir/resolve/testData/resolve/stdlib/contracts/conditionalEffects.kt b/compiler/fir/resolve/testData/resolve/stdlib/contracts/conditionalEffects.kt new file mode 100644 index 00000000000..e94a2b3f30e --- /dev/null +++ b/compiler/fir/resolve/testData/resolve/stdlib/contracts/conditionalEffects.kt @@ -0,0 +1,41 @@ +fun test_1(x: Any) { + require(x is Int) + x.inc() +} + +fun test_2(x: String?) { + requireNotNull(x) + x.length +} + +fun test_3(x: String?) { + require(x != null) + x.length +} + +fun test_4(x: Any, y: String?) { + require(x is String && y != null) + x.length + y.length +} + +fun test_5(x: Any, b: Boolean) { + if (b) { + require(x is String) + x.length + } else { + x.length + } + x.length +} + +fun test_6(x: Any, b: Boolean) { + if (b) { + require(x is String) + x.length + } else { + require(x is String) + x.length + } + x.length +} \ No newline at end of file diff --git a/compiler/fir/resolve/testData/resolve/stdlib/contracts/conditionalEffects.txt b/compiler/fir/resolve/testData/resolve/stdlib/contracts/conditionalEffects.txt new file mode 100644 index 00000000000..a2fd0228fb7 --- /dev/null +++ b/compiler/fir/resolve/testData/resolve/stdlib/contracts/conditionalEffects.txt @@ -0,0 +1,45 @@ +FILE: conditionalEffects.kt + public final fun test_1(x: R|kotlin/Any|): R|kotlin/Unit| { + R|kotlin/require|((R|/x| is R|kotlin/Int|)) + R|/x|.R|kotlin/Int.inc|() + } + public final fun test_2(x: R|kotlin/String?|): R|kotlin/Unit| { + R|kotlin/requireNotNull|(R|/x|) + R|/x|.R|kotlin/String.length| + } + public final fun test_3(x: R|kotlin/String?|): R|kotlin/Unit| { + R|kotlin/require|(!=(R|/x|, Null(null))) + R|/x|.R|kotlin/String.length| + } + public final fun test_4(x: R|kotlin/Any|, y: R|kotlin/String?|): R|kotlin/Unit| { + R|kotlin/require|((R|/x| is R|kotlin/String|) && !=(R|/y|, Null(null))) + R|/x|.R|kotlin/String.length| + R|/y|.R|kotlin/String.length| + } + public final fun test_5(x: R|kotlin/Any|, b: R|kotlin/Boolean|): R|kotlin/Unit| { + when () { + R|/b| -> { + R|kotlin/require|((R|/x| is R|kotlin/String|)) + R|/x|.R|kotlin/String.length| + } + else -> { + R|/x|.# + } + } + + R|/x|.# + } + public final fun test_6(x: R|kotlin/Any|, b: R|kotlin/Boolean|): R|kotlin/Unit| { + when () { + R|/b| -> { + R|kotlin/require|((R|/x| is R|kotlin/String|)) + R|/x|.R|kotlin/String.length| + } + else -> { + R|kotlin/require|((R|/x| is R|kotlin/String|)) + R|/x|.R|kotlin/String.length| + } + } + + R|/x|.R|kotlin/String.length| + } diff --git a/compiler/fir/resolve/tests/org/jetbrains/kotlin/fir/FirCfgBuildingWithStdlibTestGenerated.java b/compiler/fir/resolve/tests/org/jetbrains/kotlin/fir/FirCfgBuildingWithStdlibTestGenerated.java index 1dddab2372c..68430945d4a 100644 --- a/compiler/fir/resolve/tests/org/jetbrains/kotlin/fir/FirCfgBuildingWithStdlibTestGenerated.java +++ b/compiler/fir/resolve/tests/org/jetbrains/kotlin/fir/FirCfgBuildingWithStdlibTestGenerated.java @@ -32,4 +32,9 @@ public class FirCfgBuildingWithStdlibTestGenerated extends AbstractFirCfgBuildin public void testCallsInPlace() throws Exception { runTest("compiler/fir/resolve/testData/resolve/stdlib/contracts/callsInPlace.kt"); } + + @TestMetadata("conditionalEffects.kt") + public void testConditionalEffects() throws Exception { + runTest("compiler/fir/resolve/testData/resolve/stdlib/contracts/conditionalEffects.kt"); + } }