diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromLibrary/conditionalEffects.dot b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromLibrary/conditionalEffects.dot index 8e5f4244495..dd2e6f8ded6 100644 --- a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromLibrary/conditionalEffects.dot +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromLibrary/conditionalEffects.dot @@ -12,19 +12,12 @@ digraph conditionalEffects_kt { 2 [label="Access variable R|/x|"]; 3 [label="Type operator: (R|/x| is R|kotlin/Int|)"]; 4 [label="Function call: R|kotlin/require|(...)"]; - subgraph cluster_2 { - color=blue - 5 [label="Enter contract"]; - 6 [label="Access variable R|/x|"]; - 7 [label="Type operator: (R|/x| is R|kotlin/Int|)"]; - 8 [label="Exit contract"]; - } - 9 [label="Access variable R|/x|"]; - 10 [label="Smart cast: R|/x|"]; - 11 [label="Function call: R|/x|.R|kotlin/Int.inc|()"]; - 12 [label="Exit block"]; + 5 [label="Access variable R|/x|"]; + 6 [label="Smart cast: R|/x|"]; + 7 [label="Function call: R|/x|.R|kotlin/Int.inc|()"]; + 8 [label="Exit block"]; } - 13 [label="Exit function test_1" style="filled" fillcolor=red]; + 9 [label="Exit function test_1" style="filled" fillcolor=red]; } 0 -> {1}; 1 -> {2}; @@ -35,39 +28,48 @@ digraph conditionalEffects_kt { 6 -> {7}; 7 -> {8}; 8 -> {9}; - 9 -> {10}; + + subgraph cluster_2 { + color=red + 10 [label="Enter function test_2" style="filled" fillcolor=red]; + subgraph cluster_3 { + color=blue + 11 [label="Enter block"]; + 12 [label="Access variable R|/x|"]; + 13 [label="Function call: R|kotlin/requireNotNull|(...)"]; + 14 [label="Access variable R|/x|"]; + 15 [label="Smart cast: R|/x|"]; + 16 [label="Access variable R|kotlin/String.length|"]; + 17 [label="Exit block"]; + } + 18 [label="Exit function test_2" style="filled" fillcolor=red]; + } 10 -> {11}; 11 -> {12}; 12 -> {13}; - - subgraph cluster_3 { - color=red - 14 [label="Enter function test_2" style="filled" fillcolor=red]; - subgraph cluster_4 { - color=blue - 15 [label="Enter block"]; - 16 [label="Access variable R|/x|"]; - 17 [label="Function call: R|kotlin/requireNotNull|(...)"]; - subgraph cluster_5 { - color=blue - 18 [label="Enter contract"]; - 19 [label="Access variable R|/x|"]; - 20 [label="Const: Null(null)"]; - 21 [label="Equality operator !="]; - 22 [label="Exit contract"]; - } - 23 [label="Access variable R|/x|"]; - 24 [label="Smart cast: R|/x|"]; - 25 [label="Access variable R|kotlin/String.length|"]; - 26 [label="Exit block"]; - } - 27 [label="Exit function test_2" style="filled" fillcolor=red]; - } + 13 -> {14}; 14 -> {15}; 15 -> {16}; 16 -> {17}; 17 -> {18}; - 18 -> {19}; + + subgraph cluster_4 { + color=red + 19 [label="Enter function test_3" style="filled" fillcolor=red]; + subgraph cluster_5 { + color=blue + 20 [label="Enter block"]; + 21 [label="Access variable R|/x|"]; + 22 [label="Const: Null(null)"]; + 23 [label="Equality operator !="]; + 24 [label="Function call: R|kotlin/require|(...)"]; + 25 [label="Access variable R|/x|"]; + 26 [label="Smart cast: R|/x|"]; + 27 [label="Access variable R|kotlin/String.length|"]; + 28 [label="Exit block"]; + } + 29 [label="Exit function test_3" style="filled" fillcolor=red]; + } 19 -> {20}; 20 -> {21}; 21 -> {22}; @@ -76,40 +78,44 @@ digraph conditionalEffects_kt { 24 -> {25}; 25 -> {26}; 26 -> {27}; + 27 -> {28}; + 28 -> {29}; subgraph cluster_6 { color=red - 28 [label="Enter function test_3" style="filled" fillcolor=red]; + 30 [label="Enter function test_4" style="filled" fillcolor=red]; subgraph cluster_7 { color=blue - 29 [label="Enter block"]; - 30 [label="Access variable R|/x|"]; - 31 [label="Const: Null(null)"]; - 32 [label="Equality operator !="]; - 33 [label="Function call: R|kotlin/require|(...)"]; + 31 [label="Enter block"]; subgraph cluster_8 { color=blue - 34 [label="Enter contract"]; - 35 [label="Access variable R|/x|"]; - 36 [label="Const: Null(null)"]; - 37 [label="Equality operator !="]; - 38 [label="Exit contract"]; + 32 [label="Enter &&"]; + 33 [label="Access variable R|/x|"]; + 34 [label="Type operator: (R|/x| is R|kotlin/String|)"]; + 35 [label="Exit left part of &&"]; + 36 [label="Enter right part of &&"]; + 37 [label="Access variable R|/y|"]; + 38 [label="Const: Null(null)"]; + 39 [label="Equality operator !="]; + 40 [label="Exit &&"]; } - 39 [label="Access variable R|/x|"]; - 40 [label="Smart cast: R|/x|"]; - 41 [label="Access variable R|kotlin/String.length|"]; - 42 [label="Exit block"]; + 41 [label="Function call: R|kotlin/require|(...)"]; + 42 [label="Access variable R|/x|"]; + 43 [label="Smart cast: R|/x|"]; + 44 [label="Access variable R|kotlin/String.length|"]; + 45 [label="Access variable R|/y|"]; + 46 [label="Smart cast: R|/y|"]; + 47 [label="Access variable R|kotlin/String.length|"]; + 48 [label="Exit block"]; } - 43 [label="Exit function test_3" style="filled" fillcolor=red]; + 49 [label="Exit function test_4" style="filled" fillcolor=red]; } - 28 -> {29}; - 29 -> {30}; 30 -> {31}; 31 -> {32}; 32 -> {33}; 33 -> {34}; 34 -> {35}; - 35 -> {36}; + 35 -> {40 36}; 36 -> {37}; 37 -> {38}; 38 -> {39}; @@ -117,73 +123,77 @@ digraph conditionalEffects_kt { 40 -> {41}; 41 -> {42}; 42 -> {43}; - - subgraph cluster_9 { - color=red - 44 [label="Enter function test_4" style="filled" fillcolor=red]; - subgraph cluster_10 { - color=blue - 45 [label="Enter block"]; - subgraph cluster_11 { - color=blue - 46 [label="Enter &&"]; - 47 [label="Access variable R|/x|"]; - 48 [label="Type operator: (R|/x| is R|kotlin/String|)"]; - 49 [label="Exit left part of &&"]; - 50 [label="Enter right part of &&"]; - 51 [label="Access variable R|/y|"]; - 52 [label="Const: Null(null)"]; - 53 [label="Equality operator !="]; - 54 [label="Exit &&"]; - } - 55 [label="Function call: R|kotlin/require|(...)"]; - subgraph cluster_12 { - color=blue - 56 [label="Enter contract"]; - subgraph cluster_13 { - color=blue - 57 [label="Enter &&"]; - 58 [label="Access variable R|/x|"]; - 59 [label="Type operator: (R|/x| is R|kotlin/String|)"]; - 60 [label="Exit left part of &&"]; - 61 [label="Enter right part of &&"]; - 62 [label="Access variable R|/y|"]; - 63 [label="Const: Null(null)"]; - 64 [label="Equality operator !="]; - 65 [label="Exit &&"]; - } - 66 [label="Exit contract"]; - } - 67 [label="Access variable R|/x|"]; - 68 [label="Smart cast: R|/x|"]; - 69 [label="Access variable R|kotlin/String.length|"]; - 70 [label="Access variable R|/y|"]; - 71 [label="Smart cast: R|/y|"]; - 72 [label="Access variable R|kotlin/String.length|"]; - 73 [label="Exit block"]; - } - 74 [label="Exit function test_4" style="filled" fillcolor=red]; - } + 43 -> {44}; 44 -> {45}; 45 -> {46}; 46 -> {47}; 47 -> {48}; 48 -> {49}; - 49 -> {54 50}; + + subgraph cluster_9 { + color=red + 50 [label="Enter function test_5" style="filled" fillcolor=red]; + subgraph cluster_10 { + color=blue + 51 [label="Enter block"]; + subgraph cluster_11 { + color=blue + 52 [label="Enter when"]; + subgraph cluster_12 { + color=blue + 53 [label="Enter when branch condition "]; + 54 [label="Access variable R|/b|"]; + 55 [label="Exit when branch condition"]; + } + subgraph cluster_13 { + color=blue + 56 [label="Enter when branch condition else"]; + 57 [label="Exit when branch condition"]; + } + 58 [label="Enter when branch result"]; + subgraph cluster_14 { + color=blue + 59 [label="Enter block"]; + 60 [label="Access variable R|/x|"]; + 61 [label="Access variable #"]; + 62 [label="Exit block"]; + } + 63 [label="Exit when branch result"]; + 64 [label="Enter when branch result"]; + subgraph cluster_15 { + color=blue + 65 [label="Enter block"]; + 66 [label="Access variable R|/x|"]; + 67 [label="Type operator: (R|/x| is R|kotlin/String|)"]; + 68 [label="Function call: R|kotlin/require|(...)"]; + 69 [label="Access variable R|/x|"]; + 70 [label="Smart cast: R|/x|"]; + 71 [label="Access variable R|kotlin/String.length|"]; + 72 [label="Exit block"]; + } + 73 [label="Exit when branch result"]; + 74 [label="Exit when"]; + } + 75 [label="Access variable R|/x|"]; + 76 [label="Access variable #"]; + 77 [label="Exit block"]; + } + 78 [label="Exit function test_5" style="filled" fillcolor=red]; + } 50 -> {51}; 51 -> {52}; 52 -> {53}; 53 -> {54}; 54 -> {55}; - 55 -> {56}; + 55 -> {64 56}; 56 -> {57}; 57 -> {58}; 58 -> {59}; 59 -> {60}; - 60 -> {65 61}; + 60 -> {61}; 61 -> {62}; 62 -> {63}; - 63 -> {64}; + 63 -> {74}; 64 -> {65}; 65 -> {66}; 66 -> {67}; @@ -194,78 +204,76 @@ digraph conditionalEffects_kt { 71 -> {72}; 72 -> {73}; 73 -> {74}; - - subgraph cluster_14 { - color=red - 75 [label="Enter function test_5" style="filled" fillcolor=red]; - subgraph cluster_15 { - color=blue - 76 [label="Enter block"]; - subgraph cluster_16 { - color=blue - 77 [label="Enter when"]; - subgraph cluster_17 { - color=blue - 78 [label="Enter when branch condition "]; - 79 [label="Access variable R|/b|"]; - 80 [label="Exit when branch condition"]; - } - subgraph cluster_18 { - color=blue - 81 [label="Enter when branch condition else"]; - 82 [label="Exit when branch condition"]; - } - 83 [label="Enter when branch result"]; - subgraph cluster_19 { - color=blue - 84 [label="Enter block"]; - 85 [label="Access variable R|/x|"]; - 86 [label="Access variable #"]; - 87 [label="Exit block"]; - } - 88 [label="Exit when branch result"]; - 89 [label="Enter when branch result"]; - subgraph cluster_20 { - color=blue - 90 [label="Enter block"]; - 91 [label="Access variable R|/x|"]; - 92 [label="Type operator: (R|/x| is R|kotlin/String|)"]; - 93 [label="Function call: R|kotlin/require|(...)"]; - subgraph cluster_21 { - color=blue - 94 [label="Enter contract"]; - 95 [label="Access variable R|/x|"]; - 96 [label="Type operator: (R|/x| is R|kotlin/String|)"]; - 97 [label="Exit contract"]; - } - 98 [label="Access variable R|/x|"]; - 99 [label="Smart cast: R|/x|"]; - 100 [label="Access variable R|kotlin/String.length|"]; - 101 [label="Exit block"]; - } - 102 [label="Exit when branch result"]; - 103 [label="Exit when"]; - } - 104 [label="Access variable R|/x|"]; - 105 [label="Access variable #"]; - 106 [label="Exit block"]; - } - 107 [label="Exit function test_5" style="filled" fillcolor=red]; - } + 74 -> {75}; 75 -> {76}; 76 -> {77}; 77 -> {78}; - 78 -> {79}; + + subgraph cluster_16 { + color=red + 79 [label="Enter function test_6" style="filled" fillcolor=red]; + subgraph cluster_17 { + color=blue + 80 [label="Enter block"]; + subgraph cluster_18 { + color=blue + 81 [label="Enter when"]; + subgraph cluster_19 { + color=blue + 82 [label="Enter when branch condition "]; + 83 [label="Access variable R|/b|"]; + 84 [label="Exit when branch condition"]; + } + subgraph cluster_20 { + color=blue + 85 [label="Enter when branch condition else"]; + 86 [label="Exit when branch condition"]; + } + 87 [label="Enter when branch result"]; + subgraph cluster_21 { + color=blue + 88 [label="Enter block"]; + 89 [label="Access variable R|/x|"]; + 90 [label="Type operator: (R|/x| is R|kotlin/String|)"]; + 91 [label="Function call: R|kotlin/require|(...)"]; + 92 [label="Access variable R|/x|"]; + 93 [label="Smart cast: R|/x|"]; + 94 [label="Access variable R|kotlin/String.length|"]; + 95 [label="Exit block"]; + } + 96 [label="Exit when branch result"]; + 97 [label="Enter when branch result"]; + subgraph cluster_22 { + color=blue + 98 [label="Enter block"]; + 99 [label="Access variable R|/x|"]; + 100 [label="Type operator: (R|/x| is R|kotlin/String|)"]; + 101 [label="Function call: R|kotlin/require|(...)"]; + 102 [label="Access variable R|/x|"]; + 103 [label="Smart cast: R|/x|"]; + 104 [label="Access variable R|kotlin/String.length|"]; + 105 [label="Exit block"]; + } + 106 [label="Exit when branch result"]; + 107 [label="Exit when"]; + } + 108 [label="Access variable R|/x|"]; + 109 [label="Smart cast: R|/x|"]; + 110 [label="Access variable R|kotlin/String.length|"]; + 111 [label="Exit block"]; + } + 112 [label="Exit function test_6" style="filled" fillcolor=red]; + } 79 -> {80}; - 80 -> {89 81}; + 80 -> {81}; 81 -> {82}; 82 -> {83}; 83 -> {84}; - 84 -> {85}; + 84 -> {97 85}; 85 -> {86}; 86 -> {87}; 87 -> {88}; - 88 -> {103}; + 88 -> {89}; 89 -> {90}; 90 -> {91}; 91 -> {92}; @@ -273,7 +281,7 @@ digraph conditionalEffects_kt { 93 -> {94}; 94 -> {95}; 95 -> {96}; - 96 -> {97}; + 96 -> {107}; 97 -> {98}; 98 -> {99}; 99 -> {100}; @@ -284,116 +292,10 @@ digraph conditionalEffects_kt { 104 -> {105}; 105 -> {106}; 106 -> {107}; - - subgraph cluster_22 { - color=red - 108 [label="Enter function test_6" style="filled" fillcolor=red]; - subgraph cluster_23 { - color=blue - 109 [label="Enter block"]; - subgraph cluster_24 { - color=blue - 110 [label="Enter when"]; - subgraph cluster_25 { - color=blue - 111 [label="Enter when branch condition "]; - 112 [label="Access variable R|/b|"]; - 113 [label="Exit when branch condition"]; - } - subgraph cluster_26 { - color=blue - 114 [label="Enter when branch condition else"]; - 115 [label="Exit when branch condition"]; - } - 116 [label="Enter when branch result"]; - subgraph cluster_27 { - color=blue - 117 [label="Enter block"]; - 118 [label="Access variable R|/x|"]; - 119 [label="Type operator: (R|/x| is R|kotlin/String|)"]; - 120 [label="Function call: R|kotlin/require|(...)"]; - subgraph cluster_28 { - color=blue - 121 [label="Enter contract"]; - 122 [label="Access variable R|/x|"]; - 123 [label="Type operator: (R|/x| is R|kotlin/String|)"]; - 124 [label="Exit contract"]; - } - 125 [label="Access variable R|/x|"]; - 126 [label="Smart cast: R|/x|"]; - 127 [label="Access variable R|kotlin/String.length|"]; - 128 [label="Exit block"]; - } - 129 [label="Exit when branch result"]; - 130 [label="Enter when branch result"]; - subgraph cluster_29 { - color=blue - 131 [label="Enter block"]; - 132 [label="Access variable R|/x|"]; - 133 [label="Type operator: (R|/x| is R|kotlin/String|)"]; - 134 [label="Function call: R|kotlin/require|(...)"]; - subgraph cluster_30 { - color=blue - 135 [label="Enter contract"]; - 136 [label="Access variable R|/x|"]; - 137 [label="Type operator: (R|/x| is R|kotlin/String|)"]; - 138 [label="Exit contract"]; - } - 139 [label="Access variable R|/x|"]; - 140 [label="Smart cast: R|/x|"]; - 141 [label="Access variable R|kotlin/String.length|"]; - 142 [label="Exit block"]; - } - 143 [label="Exit when branch result"]; - 144 [label="Exit when"]; - } - 145 [label="Access variable R|/x|"]; - 146 [label="Smart cast: R|/x|"]; - 147 [label="Access variable R|kotlin/String.length|"]; - 148 [label="Exit block"]; - } - 149 [label="Exit function test_6" style="filled" fillcolor=red]; - } + 107 -> {108}; 108 -> {109}; 109 -> {110}; 110 -> {111}; 111 -> {112}; - 112 -> {113}; - 113 -> {130 114}; - 114 -> {115}; - 115 -> {116}; - 116 -> {117}; - 117 -> {118}; - 118 -> {119}; - 119 -> {120}; - 120 -> {121}; - 121 -> {122}; - 122 -> {123}; - 123 -> {124}; - 124 -> {125}; - 125 -> {126}; - 126 -> {127}; - 127 -> {128}; - 128 -> {129}; - 129 -> {144}; - 130 -> {131}; - 131 -> {132}; - 132 -> {133}; - 133 -> {134}; - 134 -> {135}; - 135 -> {136}; - 136 -> {137}; - 137 -> {138}; - 138 -> {139}; - 139 -> {140}; - 140 -> {141}; - 141 -> {142}; - 142 -> {143}; - 143 -> {144}; - 144 -> {145}; - 145 -> {146}; - 146 -> {147}; - 147 -> {148}; - 148 -> {149}; } diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/propertyAccessors.kt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/propertyAccessors.kt index c4b2164ce96..7f7e3ea88fd 100644 --- a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/propertyAccessors.kt +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/propertyAccessors.kt @@ -12,10 +12,10 @@ var Any?.isNotNull: Boolean return this != null } set(value) { - contract { + contract { returns() implies (this@isNotNull != null) require(this != null) - } + } } fun test_1(a: A?) { diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirReturnsImpliesAnalyzer.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirReturnsImpliesAnalyzer.kt index 03a0d168bc9..f483c2cee4a 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirReturnsImpliesAnalyzer.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirReturnsImpliesAnalyzer.kt @@ -7,12 +7,10 @@ package org.jetbrains.kotlin.fir.analysis.cfa import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.diagnostics.reportOn -import org.jetbrains.kotlin.fir.BuiltinTypes import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.analysis.cfa.util.previousCfgNodes import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext -import org.jetbrains.kotlin.fir.analysis.checkers.isSupertypeOf import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.contracts.FirResolvedContractDescription import org.jetbrains.kotlin.fir.contracts.coneEffects @@ -27,10 +25,11 @@ import org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph import org.jetbrains.kotlin.fir.resolve.dfa.cfg.JumpNode import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirPropertyAccessorSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirValueParameterSymbol import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.types.AbstractTypeChecker -import org.jetbrains.kotlin.types.ConstantValueKind object FirReturnsImpliesAnalyzer : FirControlFlowChecker() { @@ -47,11 +46,10 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() { val logicSystem = object : PersistentLogicSystem(context.session.typeContext) { override val variableStorage: VariableStorageImpl - get() = dataFlowInfo.variableStorage as VariableStorageImpl + get() = throw IllegalStateException("shouldn't be called") - override fun ConeKotlinType.isAcceptableForSmartcast(): Boolean { - return !isNullableNothing - } + override fun ConeKotlinType.isAcceptableForSmartcast(): Boolean = + !isNullableNothing } effects.forEach { effect -> @@ -89,94 +87,70 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() { } } - // TODO: create separate variable storage and don't modify existing one - val variableStorage = dataFlowInfo.variableStorage as VariableStorageImpl val flow = dataFlowInfo.flowOnNodes.getValue(node) as PersistentFlow var typeStatements: TypeStatements = flow.approvedTypeStatements - if (effect.value != ConeConstantReference.WILDCARD) { - val operation = effect.value.toOperation() - if (expressionType != null && expressionType.isInapplicableWith(operation, context.session)) return false - + val operation = effect.value.toOperation() + if (operation != null) { if (resultExpression is FirConstExpression<*>) { - if (!resultExpression.isApplicableWith(operation)) return false + if (!operation.isTrueFor(resultExpression.value)) return false } else { - val resultVar = variableStorage.getOrCreate(flow, resultExpression) - typeStatements = logicSystem.andForTypeStatements( - typeStatements, - logicSystem.approveOperationStatement(flow, OperationStatement(resultVar, operation)) - ) - } - } - - val conditionStatements = effectDeclaration.condition.buildTypeStatements( - function, logicSystem, variableStorage, context - ) ?: return false - - for ((realVar, requiredTypeStatement) in conditionStatements) { - val fixedRealVar = typeStatements.keys.find { it.identifier == realVar.identifier } ?: realVar - val originalType = function.getParameterType(fixedRealVar.identifier.symbol, context) ?: continue - val resultType = typeStatements[fixedRealVar]?.exactType.intersectWith(typeContext, originalType) - val requiredType = typeContext.intersectTypesOrNull(requiredTypeStatement.exactType.toList()) - if (requiredType != null && !requiredType.isSupertypeOf(typeContext, resultType)) return true - } - return false - } - - private fun ConeBooleanExpression.buildTypeStatements( - function: FirFunction, - logicSystem: LogicSystem<*>, - variableStorage: VariableStorage, - context: CheckerContext - ): TypeStatements? { - fun ConeValueParameterReference.toVariable(): RealVariable { - val parameterSymbol = function.getParameterSymbol(parameterIndex, context) - return variableStorage.getLocalVariable(parameterSymbol) - ?: RealVariable( - Identifier(parameterSymbol, null, null), - parameterIndex < 0, null, parameterIndex + 1, PropertyStability.STABLE_VALUE - ) - } - - fun ConeBooleanExpression.toTypeStatements(inverted: Boolean): TypeStatements? = when (this) { - is ConeBinaryLogicExpression -> { - val left = left.toTypeStatements(inverted) - val right = right.toTypeStatements(inverted) - when { - left == null -> right - right == null -> left - (kind == LogicOperationKind.AND) == !inverted -> logicSystem.andForTypeStatements(left, right) - else -> logicSystem.orForTypeStatements(left, right) + if (expressionType != null && !operation.canBeTrueFor(context.session, expressionType)) return false + // TODO: avoid modifying the storage + val variableStorage = dataFlowInfo.variableStorage as VariableStorageImpl + val resultVar = variableStorage.getOrCreateIfReal(flow, resultExpression) + if (resultVar != null) { + typeStatements = logicSystem.andForTypeStatements( + typeStatements, + logicSystem.approveOperationStatement(flow, OperationStatement(resultVar, operation)) + ) } } - is ConeIsInstancePredicate -> - if (isNegated == inverted) (arg.toVariable() typeEq type).singleton() else mapOf() - is ConeIsNullPredicate -> - arg.toVariable().nullabilityStatement(context.session.builtinTypes, isNull = isNegated == inverted).singleton() - is ConeLogicalNot -> arg.toTypeStatements(!inverted) - else -> null } - return toTypeStatements(inverted = false) + // TODO: if this is not a top-level function, `FirDataFlowAnalyzer` has erased its value parameters + // from `dataFlowInfo.variableStorage` for some reason, so its `getLocalVariable` doesn't work. + val knownVariables = typeStatements.keys.associateBy { it.identifier } + val argumentVariables = Array(function.valueParameters.size + 1) { i -> + val parameterSymbol = if (i > 0) { + function.valueParameters[i - 1].symbol + } else { + if (function.symbol is FirPropertyAccessorSymbol) { + context.containingProperty?.symbol + } else { + null + } ?: function.symbol + } + val identifier = Identifier(parameterSymbol, null, null) + // Might be unknown if there are no statements made about that parameter, but it's still possible that trivial + // contracts are valid. E.g. `returns() implies (x is String)` when `x`'s *original type* is already `String`. + knownVariables[identifier] ?: RealVariable(identifier, i == 0, null, i, PropertyStability.STABLE_VALUE) + } + + val conditionStatements = logicSystem.approveContractStatement( + flow, effectDeclaration.condition, argumentVariables, substitutor = null + ) ?: return true + + return !conditionStatements.values.all { requirement -> + val originalType = requirement.variable.identifier.symbol.correspondingParameterType ?: return@all true + val requiredType = requirement.smartCastedType(typeContext, originalType) + val actualType = typeStatements[requirement.variable].smartCastedType(typeContext, originalType) + actualType.isSubtypeOf(typeContext, requiredType) + } } - private fun RealVariable.nullabilityStatement(builtinTypes: BuiltinTypes, isNull: Boolean) = - this typeEq (if (isNull) builtinTypes.nullableNothingType.type else builtinTypes.anyType.type) - - private fun TypeStatement.singleton(): TypeStatements = - mapOf(variable to this) - - private fun ConeKotlinType.isInapplicableWith(operation: Operation, session: FirSession): Boolean { - return (operation == Operation.EqFalse || operation == Operation.EqTrue) - && !AbstractTypeChecker.isSubtypeOf(session.typeContext, session.builtinTypes.booleanType.type, this) - || operation == Operation.EqNull && !isNullable + private fun Operation.canBeTrueFor(session: FirSession, type: ConeKotlinType): Boolean = when (this) { + Operation.EqTrue, Operation.EqFalse -> + AbstractTypeChecker.isSubtypeOf(session.typeContext, session.builtinTypes.booleanType.type, type) + Operation.EqNull -> type.canBeNull + Operation.NotEqNull -> !type.isNullableNothing } - private fun FirConstExpression<*>.isApplicableWith(operation: Operation): Boolean = when { - kind == ConstantValueKind.Null -> operation == Operation.EqNull - kind == ConstantValueKind.Boolean && operation == Operation.EqTrue -> (value as Boolean) - kind == ConstantValueKind.Boolean && operation == Operation.EqFalse -> !(value as Boolean) - else -> true + private fun Operation.isTrueFor(value: Any?) = when (this) { + Operation.EqTrue -> value == true + Operation.EqFalse -> value == false + Operation.EqNull -> value == null + Operation.NotEqNull -> value != null } private fun CFGNode<*>.collectBranchExits(nodes: MutableList> = mutableListOf()): List> { @@ -189,28 +163,10 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() { private val CheckerContext.containingProperty: FirProperty? get() = (containingDeclarations.lastOrNull { it is FirProperty } as? FirProperty) - private fun FirFunction.getParameterType(symbol: FirBasedSymbol<*>, context: CheckerContext): ConeKotlinType? { - val typeRef = if (this.symbol == symbol) { - if (symbol is FirPropertyAccessorSymbol) { - context.containingProperty?.receiverParameter?.typeRef - } else { - receiverParameter?.typeRef - } - } else { - valueParameters.find { it.symbol == symbol }?.returnTypeRef + private val FirBasedSymbol<*>.correspondingParameterType: ConeKotlinType? + get() = when (this) { + is FirValueParameterSymbol -> resolvedReturnType + is FirCallableSymbol<*> -> resolvedReceiverTypeRef?.coneType + else -> null } - return typeRef?.coneType - } - - private fun FirFunction.getParameterSymbol(index: Int, context: CheckerContext): FirBasedSymbol<*> { - return if (index == -1) { - if (symbol !is FirPropertyAccessorSymbol) { - symbol - } else { - context.containingProperty?.symbol ?: symbol - } - } else { - this.valueParameters[index].symbol - } - } } 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 86e875c45b5..5b05ea39c2e 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 @@ -11,19 +11,16 @@ import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.fir.* import org.jetbrains.kotlin.fir.contracts.FirResolvedContractDescription import org.jetbrains.kotlin.fir.contracts.description.ConeConditionalEffectDeclaration -import org.jetbrains.kotlin.fir.contracts.description.ConeConstantReference import org.jetbrains.kotlin.fir.contracts.description.ConeReturnsEffectDeclaration import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor import org.jetbrains.kotlin.fir.declarations.utils.isLocal import org.jetbrains.kotlin.fir.expressions.* +import org.jetbrains.kotlin.fir.expressions.impl.FirNoReceiverExpression import org.jetbrains.kotlin.fir.references.FirControlFlowGraphReference import org.jetbrains.kotlin.fir.resolve.* import org.jetbrains.kotlin.fir.resolve.calls.ImplicitReceiverValue 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.substitution.ConeSubstitutor import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutorByMap import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformer import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.resultType @@ -35,7 +32,6 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol import org.jetbrains.kotlin.fir.types.* -import org.jetbrains.kotlin.fir.visitors.transformSingle import org.jetbrains.kotlin.name.StandardClassIds import org.jetbrains.kotlin.types.ConstantValueKind import org.jetbrains.kotlin.util.OperatorNameConventions @@ -99,7 +95,7 @@ abstract class FirDataFlowAnalyzer( override fun receiverUpdated(symbol: FirBasedSymbol<*>, info: TypeStatement?) { val index = receiverStack.getReceiverIndex(symbol) ?: return val originalType = receiverStack.getOriginalType(index) - receiverStack.replaceReceiverType(index, info?.exactType.intersectWith(typeContext, originalType)) + receiverStack.replaceReceiverType(index, info.smartCastedType(typeContext, originalType)) } override val logicSystem: PersistentLogicSystem = @@ -139,8 +135,6 @@ abstract class FirDataFlowAnalyzer( private val graphBuilder get() = context.graphBuilder private val variableStorage get() = context.variableStorage - private var contractDescriptionVisitingMode = false - private val any = components.session.builtinTypes.anyType.type private val nullableNothing = components.session.builtinTypes.nullableNothingType.type @@ -393,7 +387,11 @@ abstract class FirDataFlowAnalyzer( flow.addImplication((expressionVariable eq isType) implies (operandVariable typeEq type)) } if (!type.canBeNull) { + // x is (T & Any) => x != null flow.addImplication((expressionVariable eq isType) implies (operandVariable notEq null)) + } else { + // TODO? (KT-22996) x !is T? => x != null; don't forget to change `approveContractStatement` + // flow.addImplication((expressionVariable eq !isType) implies (operandVariable notEq null)) } } } @@ -408,7 +406,8 @@ abstract class FirDataFlowAnalyzer( } else { val expressionVariable = variableStorage.createSynthetic(typeOperatorCall) flow.addImplication((expressionVariable notEq null) implies (operandVariable notEq null)) - // TODO? flow.addImplication((expressionVariable eq null) implies (operandVariable eq null)) + // TODO? (x as T?) == null => x == null + // flow.addImplication((expressionVariable eq null) implies (operandVariable eq null)) } } @@ -771,8 +770,8 @@ abstract class FirDataFlowAnalyzer( fun enterQualifiedAccessExpression() {} fun exitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) { - graphBuilder.exitQualifiedAccessExpression(qualifiedAccessExpression).mergeIncomingFlow() - processConditionalContract(qualifiedAccessExpression) + val flow = graphBuilder.exitQualifiedAccessExpression(qualifiedAccessExpression).mergeIncomingFlow() + processConditionalContract(flow, qualifiedAccessExpression) } fun exitSmartCastExpression(smartCastExpression: FirSmartCastExpression) { @@ -843,10 +842,7 @@ abstract class FirDataFlowAnalyzer( val (functionCallNode, unionNode) = graphBuilder.exitFunctionCall(functionCall, callCompleted) unionNode?.unionFlowFromArguments() val flow = functionCallNode.mergeIncomingFlow() - if (functionCall.isBooleanNot()) { - exitBooleanNot(flow, functionCall, functionCallNode) - } - processConditionalContract(functionCall) + processConditionalContract(flow, functionCall) } fun exitDelegatedConstructorCall(call: FirDelegatedConstructorCall, callCompleted: Boolean) { @@ -865,69 +861,74 @@ abstract class FirDataFlowAnalyzer( flow = logicSystem.unionFlow(previousNodes.map { it.flow }) } - private fun processConditionalContract(qualifiedAccess: FirQualifiedAccess) { - val owner: FirContractDescriptionOwner? = when (qualifiedAccess) { + private fun FirQualifiedAccess.orderedArguments(callee: FirFunction): Array? { + val receiver = extensionReceiver.takeIf { it != FirNoReceiverExpression } + ?: dispatchReceiver.takeIf { it != FirNoReceiverExpression } + return when (this) { + is FirFunctionCall -> { + val argumentToParameter = resolvedArgumentMapping ?: return null + val parameterToArgument = argumentToParameter.entries.associate { it.value to it.key.unwrapArgument() } + Array(callee.valueParameters.size + 1) { i -> + if (i > 0) parameterToArgument[callee.valueParameters[i - 1]] else receiver + } + } + is FirQualifiedAccessExpression -> arrayOf(receiver) + is FirVariableAssignment -> arrayOf(receiver, rValue) + else -> return null + } + } + + private fun processConditionalContract(flow: FLOW, qualifiedAccess: FirQualifiedAccess) { + val callee = when (qualifiedAccess) { is FirFunctionCall -> qualifiedAccess.toResolvedCallableSymbol()?.fir as? FirSimpleFunction - is FirQualifiedAccessExpression -> { - val property = qualifiedAccess.calleeReference.resolvedSymbol?.fir as? FirProperty - property?.getter - } - is FirVariableAssignment -> { - val property = qualifiedAccess.lValue.resolvedSymbol?.fir as? FirProperty - property?.setter - } + is FirQualifiedAccessExpression -> (qualifiedAccess.calleeReference.resolvedSymbol?.fir as? FirProperty)?.getter + is FirVariableAssignment -> (qualifiedAccess.lValue.resolvedSymbol?.fir as? FirProperty)?.setter else -> null + } ?: return + + if (callee.symbol.callableId == StandardClassIds.Callables.not) { + // Special hardcoded contract for Boolean.not(): + // returns(true) implies (this == false) + // returns(false) implies (this == true) + return exitBooleanNot(flow, qualifiedAccess as FirFunctionCall) } - val contractDescription = owner?.contractDescription as? FirResolvedContractDescription ?: return - val conditionalEffects = contractDescription.effects.map { it.effect }.filterIsInstance() + val contractDescription = callee.contractDescription as? FirResolvedContractDescription ?: return + val conditionalEffects = contractDescription.effects.mapNotNull { it.effect as? ConeConditionalEffectDeclaration } if (conditionalEffects.isEmpty()) return - val argumentsMapping = createArgumentsMapping(qualifiedAccess) ?: return - val typeParameters = (owner as? FirTypeParameterRefsOwner)?.typeParameters - val substitutor = if (!typeParameters.isNullOrEmpty()) { + val arguments = qualifiedAccess.orderedArguments(callee) ?: return + val argumentVariables = Array(arguments.size) { i -> arguments[i]?.let { variableStorage.getOrCreateIfReal(flow, it) } } + if (argumentVariables.all { it == null }) return + + val typeParameters = callee.typeParameters + val substitutor = if (typeParameters.isNotEmpty()) { @Suppress("UNCHECKED_CAST") val substitutionFromArguments = typeParameters.zip(qualifiedAccess.typeArguments).map { (typeParameterRef, typeArgument) -> typeParameterRef.symbol to typeArgument.toConeTypeProjection().type }.filter { it.second != null }.toMap() as Map ConeSubstitutorByMap(substitutionFromArguments, components.session) } else { - ConeSubstitutor.Empty + null } - contractDescriptionVisitingMode = true - graphBuilder.enterContract(qualifiedAccess).mergeIncomingFlow() - val lastFlow = graphBuilder.lastNode.flow - val functionCallVariable = variableStorage.getOrCreate(lastFlow, qualifiedAccess) for (conditionalEffect in conditionalEffects) { - val fir = conditionalEffect.buildContractFir(argumentsMapping, substitutor) ?: continue val effect = conditionalEffect.effect as? ConeReturnsEffectDeclaration ?: continue - fir.transformSingle(components.transformer, ResolutionMode.ContextDependent) - val argumentVariable = variableStorage.get(lastFlow, fir) ?: continue - val lastNode = graphBuilder.lastNode - when (val value = effect.value) { - ConeConstantReference.WILDCARD -> { - lastNode.flow.commitOperationStatement(argumentVariable eq true) - } - - else -> { - logicSystem.translateVariableFromConditionInStatements( - lastNode.flow, - argumentVariable, - functionCallVariable, - shouldRemoveOriginalStatements = true, - filter = { it.condition.operation == Operation.EqTrue }, - transform = { OperationStatement(it.condition.variable, value.toOperation()) implies it.effect } - ) - } + val operation = effect.value.toOperation() + val statements = logicSystem.approveContractStatement( + flow, conditionalEffect.condition, argumentVariables, substitutor, removeApprovedOrImpossible = operation == null + ) ?: continue // TODO: do what if the result is known to be false? + if (operation == null) { + statements.values.forEach { flow.addTypeStatement(it) } + } else { + val functionCallVariable = variableStorage.getOrCreate(flow, qualifiedAccess) + flow.addAllConditionally(OperationStatement(functionCallVariable, operation), statements) } } - graphBuilder.exitContract(qualifiedAccess).mergeIncomingFlow() - contractDescriptionVisitingMode = false } fun exitConstExpression(constExpression: FirConstExpression<*>) { - if (constExpression.resultType is FirResolvedTypeRef && !contractDescriptionVisitingMode) return + if (constExpression.resultType is FirResolvedTypeRef) return graphBuilder.exitConstExpression(constExpression).mergeIncomingFlow() } @@ -949,7 +950,7 @@ abstract class FirDataFlowAnalyzer( logicSystem.recordNewAssignment(flow, variable, context.newAssignmentIndex()) } } - processConditionalContract(assignment) + processConditionalContract(flow, assignment) } private fun exitVariableInitialization( @@ -1089,9 +1090,9 @@ abstract class FirDataFlowAnalyzer( } } - private fun exitBooleanNot(flow: FLOW, functionCall: FirFunctionCall, node: FunctionCallNode) { - val argumentVariable = variableStorage.get(flow, node.firstPreviousNode.fir) ?: return - val expressionVariable = variableStorage.createSynthetic(functionCall) + private fun exitBooleanNot(flow: FLOW, expression: FirFunctionCall) { + val argumentVariable = variableStorage.get(flow, expression.dispatchReceiver) ?: return + val expressionVariable = variableStorage.createSynthetic(expression) // Alternatively: (expression == true => argument == false) && (expression == false => argument == true) // Which implementation is faster and/or consumes less memory is an open question. logicSystem.translateVariableFromConditionInStatements(flow, argumentVariable, expressionVariable) { 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 7e95120d671..27031dedc4e 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 @@ -888,14 +888,6 @@ class ControlFlowGraphBuilder { return leftExitNode to rightExitNode } - fun enterContract(qualifiedAccess: FirQualifiedAccess): EnterContractNode { - return createEnterContractNode(qualifiedAccess).also { addNewSimpleNode(it) } - } - - fun exitContract(qualifiedAccess: FirQualifiedAccess): ExitContractNode { - return createExitContractNode(qualifiedAccess).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 4bd74b59b2a..933885e66ff 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 @@ -166,12 +166,6 @@ fun ControlFlowGraphBuilder.createElvisExitNode(fir: FirElvisExpression): ElvisE fun ControlFlowGraphBuilder.createVariableDeclarationNode(fir: FirProperty): VariableDeclarationNode = VariableDeclarationNode(currentGraph, fir, levelCounter, createId()) -fun ControlFlowGraphBuilder.createExitContractNode(fir: FirQualifiedAccess): ExitContractNode = - ExitContractNode(currentGraph, fir, levelCounter, createId()) - -fun ControlFlowGraphBuilder.createEnterContractNode(fir: FirQualifiedAccess): EnterContractNode = - EnterContractNode(currentGraph, fir, levelCounter, createId()) - fun ControlFlowGraphBuilder.createConstExpressionNode(fir: FirConstExpression<*>): ConstExpressionNode = ConstExpressionNode(currentGraph, fir, levelCounter, createId()) 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 deleted file mode 100644 index 95d9fcef3a6..00000000000 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/contracts/ConeConditionalEffectToFirVisitor.kt +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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.builder.* -import org.jetbrains.kotlin.fir.expressions.impl.FirNoReceiverExpression -import org.jetbrains.kotlin.fir.references.builder.buildSimpleNamedReference -import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor -import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef -import org.jetbrains.kotlin.types.ConstantValueKind -import org.jetbrains.kotlin.util.OperatorNameConventions - -private class ConeConditionalEffectToFirVisitor( - val valueParametersMapping: Map, - val substitutor: ConeSubstitutor -) : ConeContractDescriptionVisitor() { - override fun visitConditionalEffectDeclaration(conditionalEffect: ConeConditionalEffectDeclaration, data: Nothing?): FirExpression? { - return conditionalEffect.condition.accept(this, data) - } - - override fun visitConstantDescriptor(constantReference: ConeConstantReference, data: Nothing?): FirExpression? { - return when (constantReference) { - ConeBooleanConstantReference.TRUE -> buildConstExpression(null, ConstantValueKind.Boolean, true) - ConeBooleanConstantReference.FALSE -> buildConstExpression(null, ConstantValueKind.Boolean, false) - ConeConstantReference.NULL -> createConstNull() - else -> null - } - } - - override fun visitLogicalBinaryOperationContractExpression( - binaryLogicExpression: ConeBinaryLogicExpression, - data: Nothing? - ): FirExpression? { - val leftExpression = binaryLogicExpression.left.accept(this, data) ?: return null - val rightExpression = binaryLogicExpression.right.accept(this, data) ?: return null - return buildBinaryLogicExpression { - leftOperand = leftExpression - rightOperand = rightExpression - kind = binaryLogicExpression.kind - } - } - - override fun visitLogicalNot(logicalNot: ConeLogicalNot, data: Nothing?): FirExpression? { - val explicitReceiver = logicalNot.arg.accept(this, data) ?: return null - return buildFunctionCall { - calleeReference = buildSimpleNamedReference { name = OperatorNameConventions.NOT } - this.explicitReceiver = explicitReceiver - origin = FirFunctionCallOrigin.Operator - } - } - - override fun visitIsInstancePredicate(isInstancePredicate: ConeIsInstancePredicate, data: Nothing?): FirExpression? { - val argument = isInstancePredicate.arg.accept(this@ConeConditionalEffectToFirVisitor, data) ?: return null - return buildTypeOperatorCall { - argumentList = buildUnaryArgumentList(argument) - operation = if (isInstancePredicate.isNegated) { - FirOperation.NOT_IS - } else { - FirOperation.IS - } - conversionTypeRef = buildResolvedTypeRef { type = substitutor.substituteOrSelf(isInstancePredicate.type) } - } - } - - override fun visitIsNullPredicate(isNullPredicate: ConeIsNullPredicate, data: Nothing?): FirExpression? { - val argument = isNullPredicate.arg.accept(this, data) ?: return null - return buildEqualityOperatorCall { - operation = if (isNullPredicate.isNegated) { - FirOperation.NOT_EQ - } else { - FirOperation.EQ - } - argumentList = buildBinaryArgumentList(argument, createConstNull()) - } - } - - override fun visitValueParameterReference(valueParameterReference: ConeValueParameterReference, data: Nothing?): FirExpression? { - return valueParametersMapping[valueParameterReference.parameterIndex] - } - - private fun createConstNull(): FirConstExpression<*> = buildConstExpression(null, ConstantValueKind.Null, null) -} - -fun ConeConditionalEffectDeclaration.buildContractFir( - argumentMapping: Map, - substitutor: ConeSubstitutor -): FirExpression? { - return condition.accept(ConeConditionalEffectToFirVisitor(argumentMapping, substitutor), null) -} - -fun createArgumentsMapping(qualifiedAccess: FirQualifiedAccess): Map? { - val argumentsMapping = mutableMapOf() - qualifiedAccess.extensionReceiver.takeIf { it != FirNoReceiverExpression }?.let { argumentsMapping[-1] = it } - ?: qualifiedAccess.dispatchReceiver.takeIf { it != FirNoReceiverExpression }?.let { argumentsMapping[-1] = it } - when (qualifiedAccess) { - is FirFunctionCall -> { - val function = qualifiedAccess.toResolvedCallableSymbol()?.fir as? FirSimpleFunction ?: return null - val parameterToIndex = function.valueParameters.mapIndexed { index, parameter -> parameter to index }.toMap() - val callArgumentMapping = qualifiedAccess.resolvedArgumentMapping ?: return null - for (argument in qualifiedAccess.arguments) { - argumentsMapping[parameterToIndex.getValue(callArgumentMapping.getValue(argument))] = argument.unwrapArgument() - } - } - is FirVariableAssignment -> { - argumentsMapping[0] = qualifiedAccess.rValue - } - } - return argumentsMapping -} - diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt index 38746192031..0f481f1477e 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt @@ -28,7 +28,6 @@ abstract class LogicSystem(protected val context: ConeInferenceCont originalVariable: DataFlowVariable, newVariable: DataFlowVariable, shouldRemoveOriginalStatements: Boolean = originalVariable.isSynthetic(), - filter: (Implication) -> Boolean = { true }, transform: (Implication) -> Implication? = { it }, ) diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/PersistentLogicSystem.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/PersistentLogicSystem.kt index 8c70c515677..9c34b1a9540 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/PersistentLogicSystem.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/PersistentLogicSystem.kt @@ -237,12 +237,11 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste originalVariable: DataFlowVariable, newVariable: DataFlowVariable, shouldRemoveOriginalStatements: Boolean, - filter: (Implication) -> Boolean, transform: (Implication) -> Implication? ) { with(flow) { val statements = logicStatements[originalVariable]?.takeIf { it.isNotEmpty() } ?: return - val newStatements = statements.filter(filter).mapNotNull { + val newStatements = statements.mapNotNull { val newStatement = OperationStatement(newVariable, it.condition.operation) implies it.effect transform(newStatement) }.toPersistentList() diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNode.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNode.kt index be229d5745a..16749844236 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNode.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNode.kt @@ -761,19 +761,6 @@ class VariableAssignmentNode(owner: ControlFlowGraph, override val fir: FirVaria } } -class EnterContractNode(owner: ControlFlowGraph, override val fir: FirQualifiedAccess, level: Int, id: Int) : CFGNode(owner, level, id), - EnterNodeMarker { - override fun accept(visitor: ControlFlowGraphVisitor, data: D): R { - return visitor.visitEnterContractNode(this, data) - } -} -class ExitContractNode(owner: ControlFlowGraph, override val fir: FirQualifiedAccess, level: Int, id: Int) : CFGNode(owner, level, id), - ExitNodeMarker { - override fun accept(visitor: ControlFlowGraphVisitor, data: D): R { - return visitor.visitExitContractNode(this, data) - } -} - class EnterSafeCallNode(owner: ControlFlowGraph, override val fir: FirSafeCallExpression, level: Int, id: Int) : CFGNode(owner, level, id) { override fun accept(visitor: ControlFlowGraphVisitor, data: D): R { return visitor.visitEnterSafeCallNode(this, data) diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNodeRenderer.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNodeRenderer.kt index 511b771e187..769cbd160f1 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNodeRenderer.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNodeRenderer.kt @@ -92,9 +92,6 @@ fun CFGNode<*>.render(): String = is AnnotationEnterNode -> "Enter annotation" is AnnotationExitNode -> "Exit annotation" - is EnterContractNode -> "Enter contract" - is ExitContractNode -> "Exit contract" - is EnterSafeCallNode -> "Enter safe call" is ExitSafeCallNode -> "Exit safe call" diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphVisitor.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphVisitor.kt index 7a778edd6fe..4ea6228ec11 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphVisitor.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphVisitor.kt @@ -339,14 +339,6 @@ abstract class ControlFlowGraphVisitor { return visitNode(node, data) } - open fun visitEnterContractNode(node: EnterContractNode, data: D): R { - return visitNode(node, data) - } - - open fun visitExitContractNode(node: ExitContractNode, data: D): R { - return visitNode(node, data) - } - open fun visitEnterSafeCallNode(node: EnterSafeCallNode, data: D): R { return visitNode(node, data) } diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphVisitorVoid.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphVisitorVoid.kt index 638af6f9c72..efe5cede981 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphVisitorVoid.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphVisitorVoid.kt @@ -273,14 +273,6 @@ abstract class ControlFlowGraphVisitorVoid : ControlFlowGraphVisitor null + ConeConstantReference.NULL -> Operation.EqNull + ConeConstantReference.NOT_NULL -> Operation.NotEqNull + ConeBooleanConstantReference.TRUE -> Operation.EqTrue + ConeBooleanConstantReference.FALSE -> Operation.EqFalse + else -> throw IllegalArgumentException("$this can not be transformed to Operation") +} + +// Returns `null` if the statement is always false. +fun LogicSystem.approveContractStatement( + flow: F, + statement: ConeBooleanExpression, + arguments: Array, // 0 = receiver (null if doesn't exist) + substitutor: ConeSubstitutor?, + removeApprovedOrImpossible: Boolean = false, +): TypeStatements? { + fun OperationStatement.approve() = + approveOperationStatement(flow, this, removeApprovedOrImpossible) + + fun DataFlowVariable.processEqNull(isEq: Boolean): TypeStatements = + OperationStatement(this, if (isEq) Operation.EqNull else Operation.NotEqNull).approve() + + fun ConeBooleanExpression.visit(inverted: Boolean): TypeStatements? = when (this) { + is ConeBooleanConstantReference -> + if (inverted == (this == ConeBooleanConstantReference.TRUE)) null else mapOf() + is ConeLogicalNot -> arg.visit(inverted = !inverted) + is ConeIsInstancePredicate -> + arguments.getOrNull(arg.parameterIndex + 1)?.let { + val isType = inverted == isNegated + val substitutedType = substitutor?.substituteOrNull(type) ?: type + when { + substitutedType.isAny -> it.processEqNull(!isType) + substitutedType.isNullableNothing -> it.processEqNull(isType) + else -> { + // x is (T & Any) => x != null + // TODO? (KT-22996) x !is T? => x != null: change `&&` to `==` + val fromNullability = if (isType && !type.canBeNull) it.processEqNull(false) else mapOf() + if (isType && it is RealVariable) { + andForTypeStatements(fromNullability, mapOf(it to (it typeEq substitutedType))) + } else { + fromNullability + } + } + } + } ?: mapOf() + is ConeIsNullPredicate -> + arguments.getOrNull(arg.parameterIndex + 1)?.processEqNull(inverted == isNegated) ?: mapOf() + is ConeBooleanValueParameterReference -> + arguments.getOrNull(parameterIndex + 1)?.let { + OperationStatement(it, if (inverted) Operation.EqFalse else Operation.EqTrue).approve() + } ?: mapOf() + is ConeBinaryLogicExpression -> { + val a = left.visit(inverted) + val b = right.visit(inverted) + val isAnd = inverted != (kind == LogicOperationKind.AND) + when { + a == null -> b.takeIf { !isAnd } // false || b == b; false && b = false + b == null -> a.takeIf { !isAnd } // a || false == a; a && false = false + isAnd -> andForTypeStatements(a, b) + else -> orForTypeStatements(a, b) + } + } + else -> mapOf() + } + + return statement.visit(inverted = false) +} diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/util.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/util.kt index fd1e5ad20bc..f8d99dbc038 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/util.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/util.kt @@ -7,8 +7,6 @@ package org.jetbrains.kotlin.fir.resolve.dfa import kotlinx.collections.immutable.PersistentMap import org.jetbrains.kotlin.fir.FirElement -import org.jetbrains.kotlin.fir.contracts.description.ConeBooleanConstantReference -import org.jetbrains.kotlin.fir.contracts.description.ConeConstantReference import org.jetbrains.kotlin.fir.declarations.FirDeclaration import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference @@ -16,12 +14,8 @@ import org.jetbrains.kotlin.fir.references.FirThisReference import org.jetbrains.kotlin.fir.references.impl.FirSimpleNamedReference import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol -import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirSyntheticPropertySymbol -import org.jetbrains.kotlin.fir.types.ConeKotlinType -import org.jetbrains.kotlin.fir.types.ConeTypeContext -import org.jetbrains.kotlin.fir.types.coneType -import org.jetbrains.kotlin.name.StandardClassIds +import org.jetbrains.kotlin.fir.types.* import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract @@ -58,9 +52,9 @@ internal inline fun PersistentMap.put( } } -fun Set?.intersectWith(context: ConeTypeContext, originalType: ConeKotlinType): ConeKotlinType = - if (!isNullOrEmpty()) { - context.intersectTypes(toMutableList().also { it += originalType }) +fun TypeStatement?.smartCastedType(context: ConeTypeContext, originalType: ConeKotlinType): ConeKotlinType = + if (this != null && exactType.isNotEmpty()) { + context.intersectTypes(exactType.toMutableList().also { it += originalType }) } else { originalType } @@ -74,19 +68,6 @@ fun FirOperation.isEq(): Boolean { } } -fun FirFunctionCall.isBooleanNot(): Boolean { - val symbol = (calleeReference as? FirResolvedNamedReference)?.resolvedSymbol as? FirNamedFunctionSymbol ?: return false - return symbol.callableId == StandardClassIds.Callables.not -} - -fun ConeConstantReference.toOperation(): Operation = when (this) { - ConeConstantReference.NULL -> Operation.EqNull - ConeConstantReference.NOT_NULL -> Operation.NotEqNull - ConeBooleanConstantReference.TRUE -> Operation.EqTrue - ConeBooleanConstantReference.FALSE -> Operation.EqFalse - else -> throw IllegalArgumentException("$this can not be transformed to Operation") -} - @DfaInternals val FirExpression.coneType: ConeKotlinType get() = typeRef.coneType