[FIR] Support Conditional effects

This commit is contained in:
Dmitriy Novozhilov
2019-10-24 15:49:42 +03:00
parent 57f1eac9a8
commit 7e55960943
11 changed files with 651 additions and 6 deletions
@@ -7,9 +7,8 @@ package org.jetbrains.kotlin.fir.resolve.dfa
import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirSymbolOwner import org.jetbrains.kotlin.fir.FirSymbolOwner
import org.jetbrains.kotlin.fir.declarations.FirAnonymousInitializer import org.jetbrains.kotlin.fir.contracts.description.*
import org.jetbrains.kotlin.fir.declarations.FirFunction import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.impl.FirThisReceiverExpressionImpl import org.jetbrains.kotlin.fir.expressions.impl.FirThisReceiverExpressionImpl
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference 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.ImplicitReceiverStackImpl
import org.jetbrains.kotlin.fir.resolve.dfa.Condition.* import org.jetbrains.kotlin.fir.resolve.dfa.Condition.*
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.* 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.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformer
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.CallableId 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.coneTypeSafe
import org.jetbrains.kotlin.fir.types.coneTypeUnsafe import org.jetbrains.kotlin.fir.types.coneTypeUnsafe
import org.jetbrains.kotlin.fir.types.isMarkedNullable 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.ir.expressions.IrConstKind
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.safeAs 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 { companion object {
private val KOTLIN_BOOLEAN_NOT = CallableId(FqName("kotlin"), FqName("Boolean"), Name.identifier("not")) 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) exitBooleanNot(functionCall, node)
return return
} }
processConditionalContract(functionCall)
} }
private fun processConditionalContract(functionCall: FirFunctionCall) {
val contractDescription = (functionCall.resolvedSymbol?.fir as? FirSimpleFunction)?.contractDescription ?: return
val conditionalEffects = contractDescription.effects.filterIsInstance<ConeConditionalEffectDeclaration>()
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<*>? private val FirElement.resolvedSymbol: AbstractFirBasedSymbol<*>?
get() { get() {
return when (this) { return when (this) {
@@ -47,11 +47,11 @@ abstract class LogicSystem(private val context: DataFlowInferenceContext) {
flow: Flow, flow: Flow,
sourceVariable: DataFlowVariable, sourceVariable: DataFlowVariable,
newVariable: DataFlowVariable, newVariable: DataFlowVariable,
transform: ((ConditionalFirDataFlowInfo) -> ConditionalFirDataFlowInfo)? = null transform: ((ConditionalFirDataFlowInfo) -> ConditionalFirDataFlowInfo?)? = null
) { ) {
var infos = flow.getConditionalInfos(sourceVariable) var infos = flow.getConditionalInfos(sourceVariable)
if (transform != null) { if (transform != null) {
infos = infos.map(transform) infos = infos.mapNotNull(transform)
} }
flow.conditionalInfos.putAll(newVariable, infos) flow.conditionalInfos.putAll(newVariable, infos)
if (sourceVariable.isSynthetic()) { if (sourceVariable.isSynthetic()) {
@@ -156,6 +156,9 @@ class StubNode(owner: ControlFlowGraph, level: Int) : CFGNode<FirStub>(owner, le
class VariableDeclarationNode(owner: ControlFlowGraph, override val fir: FirProperty, level: Int) : CFGNode<FirProperty>(owner, level) class VariableDeclarationNode(owner: ControlFlowGraph, override val fir: FirProperty, level: Int) : CFGNode<FirProperty>(owner, level)
class VariableAssignmentNode(owner: ControlFlowGraph, override val fir: FirVariableAssignment, level: Int) : CFGNode<FirVariableAssignment>(owner, level) class VariableAssignmentNode(owner: ControlFlowGraph, override val fir: FirVariableAssignment, level: Int) : CFGNode<FirVariableAssignment>(owner, level)
class EnterContractNode(owner: ControlFlowGraph, override val fir: FirFunctionCall, level: Int) : CFGNode<FirFunctionCall>(owner, level), EnterNode
class ExitContractNode(owner: ControlFlowGraph, override val fir: FirFunctionCall, level: Int) : CFGNode<FirFunctionCall>(owner, level), ExitNode
// ----------------------------------- Other ----------------------------------- // ----------------------------------- Other -----------------------------------
class AnnotationEnterNode(owner: ControlFlowGraph, override val fir: FirAnnotationCall, level: Int) : CFGNode<FirAnnotationCall>(owner, level), EnterNode class AnnotationEnterNode(owner: ControlFlowGraph, override val fir: FirAnnotationCall, level: Int) : CFGNode<FirAnnotationCall>(owner, level), EnterNode
@@ -391,6 +391,14 @@ class ControlFlowGraphBuilder {
return leftExitNode to rightExitNode 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 { fun exitBinaryOr(binaryLogicExpression: FirBinaryLogicExpression): BinaryOrExitNode {
assert(binaryLogicExpression.kind == LogicOperationKind.OR) assert(binaryLogicExpression.kind == LogicOperationKind.OR)
levelCounter-- levelCounter--
@@ -121,6 +121,12 @@ fun ControlFlowGraphBuilder.createAnnotationEnterNode(fir: FirAnnotationCall): A
fun ControlFlowGraphBuilder.createVariableDeclarationNode(fir: FirProperty): VariableDeclarationNode = fun ControlFlowGraphBuilder.createVariableDeclarationNode(fir: FirProperty): VariableDeclarationNode =
VariableDeclarationNode(graph, fir, levelCounter) 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 = fun ControlFlowGraphBuilder.createConstExpressionNode(fir: FirConstExpression<*>): ConstExpressionNode =
ConstExpressionNode(graph, fir, levelCounter) ConstExpressionNode(graph, fir, levelCounter)
@@ -145,6 +145,9 @@ fun CFGNode<*>.render(): String =
is AnnotationEnterNode -> "Enter annotation" is AnnotationEnterNode -> "Enter annotation"
is AnnotationExitNode -> "Exit annotation" is AnnotationExitNode -> "Exit annotation"
is EnterContractNode -> "Enter contract"
is ExitContractNode -> "Exit contract"
else -> TODO(this@render.toString()) else -> TODO(this@render.toString())
} }
) )
@@ -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<FirExpression?, Map<Int, FirExpression>>() {
override fun visitConditionalEffectDeclaration(conditionalEffect: ConeConditionalEffectDeclaration, data: Map<Int, FirExpression>): FirExpression? {
return conditionalEffect.condition.accept(this, data)
}
override fun visitConstantDescriptor(constantReference: ConeConstantReference, data: Map<Int, FirExpression>): 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<Int, FirExpression>
): 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<Int, FirExpression>): 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<Int, FirExpression>): 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<Int, FirExpression>): 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<Int, FirExpression>): FirExpression? {
return data[valueParameterReference.parameterIndex]
}
private fun createConstNull(): FirConstExpression<*> = FirConstExpressionImpl(null, IrConstKind.Null, null)
}
fun ConeConditionalEffectDeclaration.buildContractFir(argumentMapping: Map<Int, FirExpression>): FirExpression? {
return condition.accept(ConeConditionalEffectToFirVisitor, argumentMapping)
}
fun createArgumentsMapping(functionCall: FirFunctionCall): Map<Int, FirExpression>? {
val function = functionCall.toResolvedCallableSymbol()?.fir as? FirSimpleFunction ?: return null
val argumentsMapping = mutableMapOf<Int, FirExpression>()
// 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
}
@@ -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|<local>/x|"];
3 [label="Type operator: x is Int"];
4 [label="Function call: R|kotlin/require|((R|<local>/x| is R|kotlin/Int|))"];
subgraph cluster_2 {
color=blue
5 [label="Enter contract"];
6 [label="Access variable R|<local>/x|"];
7 [label="Type operator: x is Int"];
8 [label="Exit contract"];
}
9 [label="Access variable R|<local>/x|"];
10 [label="Function call: R|<local>/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|<local>/x|"];
16 [label="Function call: R|kotlin/requireNotNull|<R|kotlin/String|>(R|<local>/x|)"];
subgraph cluster_5 {
color=blue
17 [label="Enter contract"];
18 [label="Access variable R|<local>/x|"];
19 [label="Const: Null(null)"];
20 [label="Operator !="];
21 [label="Exit contract"];
}
22 [label="Access variable R|<local>/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|<local>/x|"];
29 [label="Const: Null(null)"];
30 [label="Operator !="];
31 [label="Function call: R|kotlin/require|(!=(R|<local>/x|, Null(null)))"];
subgraph cluster_8 {
color=blue
32 [label="Enter contract"];
33 [label="Access variable R|<local>/x|"];
34 [label="Const: Null(null)"];
35 [label="Operator !="];
36 [label="Exit contract"];
}
37 [label="Access variable R|<local>/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|<local>/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|<local>/y|"];
49 [label="Const: Null(null)"];
50 [label="Operator !="];
51 [label="Exit &&"];
}
52 [label="Function call: R|kotlin/require|((R|<local>/x| is R|kotlin/String|) && !=(R|<local>/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|<local>/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|<local>/y|"];
60 [label="Const: Null(null)"];
61 [label="Operator !="];
62 [label="Exit &&"];
}
63 [label="Exit contract"];
}
64 [label="Access variable R|<local>/x|"];
65 [label="Access variable R|kotlin/String.length|"];
66 [label="Access variable R|<local>/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|<local>/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|<local>/x|"];
81 [label="Access variable <Unresolved name: length>#"];
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|<local>/x|"];
87 [label="Type operator: x is String"];
88 [label="Function call: R|kotlin/require|((R|<local>/x| is R|kotlin/String|))"];
subgraph cluster_21 {
color=blue
89 [label="Enter contract"];
90 [label="Access variable R|<local>/x|"];
91 [label="Type operator: x is String"];
92 [label="Exit contract"];
}
93 [label="Access variable R|<local>/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|<local>/x|"];
99 [label="Access variable <Unresolved name: length>#"];
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|<local>/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|<local>/x|"];
113 [label="Type operator: x is String"];
114 [label="Function call: R|kotlin/require|((R|<local>/x| is R|kotlin/String|))"];
subgraph cluster_28 {
color=blue
115 [label="Enter contract"];
116 [label="Access variable R|<local>/x|"];
117 [label="Type operator: x is String"];
118 [label="Exit contract"];
}
119 [label="Access variable R|<local>/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|<local>/x|"];
126 [label="Type operator: x is String"];
127 [label="Function call: R|kotlin/require|((R|<local>/x| is R|kotlin/String|))"];
subgraph cluster_30 {
color=blue
128 [label="Enter contract"];
129 [label="Access variable R|<local>/x|"];
130 [label="Type operator: x is String"];
131 [label="Exit contract"];
}
132 [label="Access variable R|<local>/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|<local>/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};
}
@@ -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
}
@@ -0,0 +1,45 @@
FILE: conditionalEffects.kt
public final fun test_1(x: R|kotlin/Any|): R|kotlin/Unit| {
R|kotlin/require|((R|<local>/x| is R|kotlin/Int|))
R|<local>/x|.R|kotlin/Int.inc|()
}
public final fun test_2(x: R|kotlin/String?|): R|kotlin/Unit| {
R|kotlin/requireNotNull|<R|kotlin/String|>(R|<local>/x|)
R|<local>/x|.R|kotlin/String.length|
}
public final fun test_3(x: R|kotlin/String?|): R|kotlin/Unit| {
R|kotlin/require|(!=(R|<local>/x|, Null(null)))
R|<local>/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|<local>/x| is R|kotlin/String|) && !=(R|<local>/y|, Null(null)))
R|<local>/x|.R|kotlin/String.length|
R|<local>/y|.R|kotlin/String.length|
}
public final fun test_5(x: R|kotlin/Any|, b: R|kotlin/Boolean|): R|kotlin/Unit| {
when () {
R|<local>/b| -> {
R|kotlin/require|((R|<local>/x| is R|kotlin/String|))
R|<local>/x|.R|kotlin/String.length|
}
else -> {
R|<local>/x|.<Unresolved name: length>#
}
}
R|<local>/x|.<Unresolved name: length>#
}
public final fun test_6(x: R|kotlin/Any|, b: R|kotlin/Boolean|): R|kotlin/Unit| {
when () {
R|<local>/b| -> {
R|kotlin/require|((R|<local>/x| is R|kotlin/String|))
R|<local>/x|.R|kotlin/String.length|
}
else -> {
R|kotlin/require|((R|<local>/x| is R|kotlin/String|))
R|<local>/x|.R|kotlin/String.length|
}
}
R|<local>/x|.R|kotlin/String.length|
}
@@ -32,4 +32,9 @@ public class FirCfgBuildingWithStdlibTestGenerated extends AbstractFirCfgBuildin
public void testCallsInPlace() throws Exception { public void testCallsInPlace() throws Exception {
runTest("compiler/fir/resolve/testData/resolve/stdlib/contracts/callsInPlace.kt"); 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");
}
} }