From 5d3b75ebc3e85e0b1ed28aa4fef2ab71ca346a90 Mon Sep 17 00:00:00 2001 From: Dmitriy Novozhilov Date: Thu, 12 Dec 2019 16:11:21 +0300 Subject: [PATCH] [FIR] Implement new data flow analysis --- compiler/compiler.pro | 2 + compiler/compiler.pro.191 | 2 + compiler/fir/resolve/build.gradle.kts | 5 + .../kotlin/fir/resolve/SessionHolder.kt | 4 +- .../fir/resolve/dfa/PersistentLogicSystem.kt | 266 ++++++ .../resolve/dfa/new/FirDataFlowAnalyzer.kt | 813 ++++++++++++++++++ .../kotlin/fir/resolve/dfa/new/LogicSystem.kt | 161 ++++ .../resolve/dfa/new/PersistentLogicSystem.kt | 411 +++++++++ .../fir/resolve/dfa/new/VariableStorage.kt | 152 ++++ .../kotlin/fir/resolve/dfa/new/model.kt | 228 +++++ .../kotlin/fir/resolve/dfa/new/util.kt | 117 +++ .../FirAbstractBodyResolveTransformer.kt | 6 +- .../resolve/testData/resolve/cfg/jumps.dot | 286 +++--- .../fir/resolve/testData/resolve/cfg/jumps.kt | 1 + .../resolve/testData/resolve/cfg/jumps.txt | 1 + .../smartcasts/anotherBoundSmartcasts.dot | 545 ++++++++---- .../smartcasts/anotherBoundSmartcasts.kt | 50 +- .../smartcasts/anotherBoundSmartcasts.txt | 61 +- .../resolve/smartcasts/booleanOperators.dot | 45 + .../resolve/smartcasts/booleanOperators.kt | 6 + .../resolve/smartcasts/booleanOperators.txt | 8 + .../resolve/smartcasts/boundSmartcasts.dot | 80 +- .../resolve/smartcasts/boundSmartcasts.kt | 7 +- .../resolve/smartcasts/boundSmartcasts.txt | 7 +- .../resolve/smartcasts/notBoundSmartcasts.dot | 248 +++--- .../resolve/smartcasts/notBoundSmartcasts.kt | 25 +- .../resolve/smartcasts/notBoundSmartcasts.txt | 30 +- .../resolve/smartcasts/nullability.dot | 6 +- .../resolve/smartcasts/nullability.kt | 6 +- .../resolve/smartcasts/nullability.txt | 4 +- .../smartcasts/smartcastAfterReassignment.dot | 11 +- 31 files changed, 3062 insertions(+), 532 deletions(-) create mode 100644 compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/PersistentLogicSystem.kt create mode 100644 compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/FirDataFlowAnalyzer.kt create mode 100644 compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/LogicSystem.kt create mode 100644 compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/PersistentLogicSystem.kt create mode 100644 compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/VariableStorage.kt create mode 100644 compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/model.kt create mode 100644 compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/util.kt diff --git a/compiler/compiler.pro b/compiler/compiler.pro index ea6fd9779ac..520088a3c1d 100644 --- a/compiler/compiler.pro +++ b/compiler/compiler.pro @@ -46,6 +46,8 @@ messages/**) -dontwarn javaslang.match.annotation.Unapply -dontwarn javaslang.match.annotation.Patterns -dontwarn javaslang.* +-dontwarn kotlinx.collections.immutable.* +-dontwarn kotlinx.collections.immutable.** -dontwarn com.google.errorprone.** -dontwarn com.google.j2objc.** -dontwarn javax.crypto.** diff --git a/compiler/compiler.pro.191 b/compiler/compiler.pro.191 index 6aad71f82fc..fb2958f5949 100644 --- a/compiler/compiler.pro.191 +++ b/compiler/compiler.pro.191 @@ -43,6 +43,8 @@ messages/**) -dontwarn javaslang.match.annotation.Unapply -dontwarn javaslang.match.annotation.Patterns -dontwarn javaslang.* +-dontwarn kotlinx.collections.immutable.* +-dontwarn kotlinx.collections.immutable.** -dontwarn com.google.errorprone.** -dontwarn com.google.j2objc.** -dontwarn javax.crypto.** diff --git a/compiler/fir/resolve/build.gradle.kts b/compiler/fir/resolve/build.gradle.kts index 89f80be7794..f968f307881 100644 --- a/compiler/fir/resolve/build.gradle.kts +++ b/compiler/fir/resolve/build.gradle.kts @@ -3,12 +3,17 @@ plugins { id("jps-compatible") } +repositories { + maven(url = "https://dl.bintray.com/kotlin/kotlinx") +} + dependencies { compile(project(":core:descriptors")) compile(project(":core:descriptors.jvm")) compile(project(":core:deserialization")) compile(project(":compiler:fir:cones")) compile(project(":compiler:fir:tree")) + compile("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.2") compileOnly(project(":kotlin-reflect-api")) compileOnly(intellijCoreDep()) { includeJars("intellij-core", "guava", rootProject = rootProject) } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/SessionHolder.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/SessionHolder.kt index 62b8e56ece1..320f90d1d71 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/SessionHolder.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/SessionHolder.kt @@ -13,7 +13,7 @@ import org.jetbrains.kotlin.fir.declarations.FirFile import org.jetbrains.kotlin.fir.declarations.FirResolvePhase import org.jetbrains.kotlin.fir.resolve.calls.InferenceComponents import org.jetbrains.kotlin.fir.resolve.calls.ResolutionStageRunner -import org.jetbrains.kotlin.fir.resolve.dfa.FirDataFlowAnalyzer +import org.jetbrains.kotlin.fir.resolve.dfa.new.FirDataFlowAnalyzer import org.jetbrains.kotlin.fir.resolve.transformers.* import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol import org.jetbrains.kotlin.fir.types.FirTypeRef @@ -36,7 +36,7 @@ interface BodyResolveComponents : SessionHolder { val callResolver: FirCallResolver val doubleColonExpressionResolver: FirDoubleColonExpressionResolver val syntheticCallGenerator: FirSyntheticCallGenerator - val dataFlowAnalyzer: FirDataFlowAnalyzer + val dataFlowAnalyzer: FirDataFlowAnalyzer<*> val integerLiteralTypeApproximator: IntegerLiteralTypeApproximationTransformer val integerOperatorsTypeUpdater: IntegerOperatorsTypeUpdater diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/PersistentLogicSystem.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/PersistentLogicSystem.kt new file mode 100644 index 00000000000..9fc8b4e4db4 --- /dev/null +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/PersistentLogicSystem.kt @@ -0,0 +1,266 @@ +///* +// * 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 +// +//import kotlinx.collections.immutable.* +//import kotlinx.collections.immutable.PersistentMap +//import kotlinx.collections.immutable.PersistentSet +//import org.jetbrains.kotlin.fir.types.ConeKotlinType +// +// +//private data class PersistentFirDataFlowInfo( +// override val exactType: PersistentSet, +// override val exactNotType: PersistentSet +//) : FirDataFlowInfo { +// +// override operator fun plus(other: FirDataFlowInfo): PersistentFirDataFlowInfo { +// return PersistentFirDataFlowInfo( +// exactType + other.exactType, +// exactNotType + other.exactNotType +// ) +// } +// +// override fun minus(other: FirDataFlowInfo): FirDataFlowInfo { +// // TODO +// throw IllegalStateException() +// } +// +// override val isNotEmpty: Boolean +// get() = exactType.isNotEmpty() || exactNotType.isNotEmpty() +// +// override fun invert(): PersistentFirDataFlowInfo { +// return PersistentFirDataFlowInfo(exactNotType, exactType) +// } +// +//// override fun toMutableInfo(): MutableFirDataFlowInfo { +//// return MutableFirDataFlowInfo(exactType.toMutableSet(), exactNotType.toMutableSet()) +//// } +//} +// +//private typealias PersistentApprovedInfos = PersistentMap +//private typealias PersistentConditionalInfos = PersistentMap> +// +//private fun FirDataFlowInfo.toPersistent(): PersistentFirDataFlowInfo = PersistentFirDataFlowInfo( +// exactType.toPersistentSet(), +// exactNotType.toPersistentSet() +//) +// +//private fun PersistentApprovedInfos.addNewInfo(variable: RealDataFlowVariable, info: FirDataFlowInfo): PersistentApprovedInfos { +// val existingInfo = this[variable] +// return if (existingInfo == null) { +// val persistentInfo = if (info is PersistentFirDataFlowInfo) info else info.toPersistent() +// put(variable, persistentInfo) +// } else { +// put(variable, existingInfo + info) +// } +//} +// +//private class PersistentFlow : Flow { +// val previousFlow: PersistentFlow? +// var approvedInfos: PersistentApprovedInfos +// var conditionalInfos: PersistentConditionalInfos +// +// constructor(previousFlow: PersistentFlow) { +// this.previousFlow = previousFlow +// approvedInfos = previousFlow.approvedInfos +// conditionalInfos = previousFlow.conditionalInfos +// level = previousFlow.level + 1 +// } +// +// constructor() { +// previousFlow = null +// approvedInfos = persistentHashMapOf() +// conditionalInfos = persistentHashMapOf() +// level = 1 +// } +// +// val level: Int +// var approvedInfosDiff: PersistentApprovedInfos = persistentHashMapOf() +// +// override fun getApprovedInfo(variable: RealDataFlowVariable): FirDataFlowInfo? { +// return approvedInfos[variable] +// } +// +// override fun getConditionalInfos(variable: DataFlowVariable): Collection { +// return conditionalInfos[variable] ?: emptyList() +// } +// +// override fun getVariablesInApprovedInfos(): Collection { +// return approvedInfos.keys +// } +// +// override fun removeConditionalInfos(variable: DataFlowVariable): Collection { +// val result = getConditionalInfos(variable) +// if (result.isNotEmpty()) { +// conditionalInfos = conditionalInfos.remove(variable) +// } +// return result +// } +//} +// +//abstract class PersistentLogicSystem(context: DataFlowInferenceContext) : LogicSystem(context) { +// override fun createEmptyFlow(): Flow { +// return PersistentFlow() +// } +// +// override fun forkFlow(flow: Flow): Flow { +// require(flow is PersistentFlow) +// return PersistentFlow(flow) +// } +// +// override fun joinFlow(flows: Collection): Flow { +// if (flows.isEmpty()) return createEmptyFlow() +// flows.singleOrNull()?.let { return it } +// +// @Suppress("UNCHECKED_CAST", "NAME_SHADOWING") +// val flows = flows as Collection +// val commonFlow = flows.reduce(this::lowestCommonFlow) +// +// val commonVariables = flows.map { it.diffVariablesIterable(commonFlow).toList() } +// .intersectSets() +// .takeIf { it.isNotEmpty() } +// ?: return commonFlow +// +// for (variable in commonVariables) { +// val info = or(flows.map { it.getApprovedDiff(variable, commonFlow) }) +// if (info.isEmpty) continue +// commonFlow.approvedInfos = commonFlow.approvedInfos.addNewInfo(variable, info) +// commonFlow.approvedInfosDiff = commonFlow.approvedInfosDiff.addNewInfo(variable, info) +// } +// +// updateAllReceivers(commonFlow) +// +// return commonFlow +// } +// +// private fun PersistentFlow.diffVariablesIterable(parentFlow: PersistentFlow): Iterable = +// object : DiffIterable(parentFlow, this) { +// override fun extractIterator(flow: PersistentFlow): Iterator { +// return flow.approvedInfosDiff.keys.iterator() +// } +// } +// +// private abstract class DiffIterable(private val parentFlow: PersistentFlow, private var currentFlow: PersistentFlow) : Iterable { +// private var currentIterator = extractIterator(currentFlow) +// +// abstract fun extractIterator(flow: PersistentFlow): Iterator +// +// override fun iterator(): Iterator { +// return object : Iterator { +// override fun hasNext(): Boolean { +// if (currentIterator.hasNext()) return true +// while (currentFlow != parentFlow) { +// currentFlow = currentFlow.previousFlow!! +// currentIterator = extractIterator(currentFlow) +// if (currentIterator.hasNext()) return true +// } +// return false +// } +// +// override fun next(): T { +// if (!hasNext()) { +// throw NoSuchElementException() +// } +// return currentIterator.next() +// } +// } +// } +// } +// +// private fun PersistentFlow.getApprovedDiff(variable: RealDataFlowVariable, parentFlow: PersistentFlow): MutableFirDataFlowInfo { +// var flow = this +// val result = MutableFirDataFlowInfo() +// while (flow != parentFlow) { +// flow.approvedInfosDiff[variable]?.let { +// result += it +// } +// flow = flow.previousFlow!! +// } +// return result +// } +// +// override fun collectInfoForBooleanOperator( +// leftFlow: Flow, +// leftVariable: DataFlowVariable, +// rightFlow: Flow, +// rightVariable: DataFlowVariable +// ): InfoForBooleanOperator { +// require(leftFlow is PersistentFlow && rightFlow is PersistentFlow) +// return InfoForBooleanOperator( +// leftFlow.conditionalInfos[leftVariable] ?: emptyList(), +// rightFlow.conditionalInfos[rightVariable] ?: emptyList(), +// rightFlow.approvedInfosDiff +// ) +// } +// +// override fun changeVariableForConditionFlow( +// flow: Flow, +// sourceVariable: DataFlowVariable, +// newVariable: DataFlowVariable, +// transform: ((ConditionalFirDataFlowInfo) -> ConditionalFirDataFlowInfo?)? +// ) { +// require(flow is PersistentFlow) +// with(flow) { +// val existingInfo = conditionalInfos[sourceVariable]?.takeIf { it.isNotEmpty() } ?: return +// val transformedInfo = if (transform == null) { +// existingInfo +// } else { +// existingInfo.map(transform).toPersistentList() +// } +// conditionalInfos = conditionalInfos.remove(sourceVariable).put(newVariable, transformedInfo) +// } +// } +// +// override fun addApprovedInfo(flow: Flow, variable: RealDataFlowVariable, info: FirDataFlowInfo) { +// require(flow is PersistentFlow) +// with(flow) { +// approvedInfos = approvedInfos.addNewInfo(variable, info) +// if (previousFlow != null) { +// approvedInfosDiff = approvedInfosDiff.addNewInfo(variable, info) +// } +// if (variable.isThisReference) { +// processUpdatedReceiverVariable(flow, variable) +// } +// } +// } +// +// override fun addConditionalInfo(flow: Flow, variable: DataFlowVariable, info: ConditionalFirDataFlowInfo) { +// require(flow is PersistentFlow) +// with(flow) { +// val existingInfo = conditionalInfos[variable] +// conditionalInfos = if (existingInfo == null) { +// conditionalInfos.put(variable, persistentListOf(info)) +// } else { +// conditionalInfos.put(variable, existingInfo + info) +// } +// } +// } +// +// override val Flow.approvedInfos: MutableApprovedInfos +// get() = throw IllegalStateException() +// +// override val Flow.conditionalInfos: ConditionalInfos +// get() = throw IllegalStateException() +// +// private fun lowestCommonFlow(left: PersistentFlow, right: PersistentFlow): PersistentFlow { +// val level = minOf(left.level, right.level) +// @Suppress("NAME_SHADOWING") +// var left = left +// while (left.level > level) { +// left = left.previousFlow!! +// } +// @Suppress("NAME_SHADOWING") +// var right = right +// while (right.level > level) { +// right = right.previousFlow!! +// } +// while (left != right) { +// left = left.previousFlow!! +// right = right.previousFlow!! +// } +// return left +// } +//} \ No newline at end of file diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/FirDataFlowAnalyzer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/FirDataFlowAnalyzer.kt new file mode 100644 index 00000000000..843c952fb8a --- /dev/null +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/FirDataFlowAnalyzer.kt @@ -0,0 +1,813 @@ +/* + * 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.new + +import org.jetbrains.kotlin.fir.contracts.description.ConeBooleanConstantReference +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.expressions.* +import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference +import org.jetbrains.kotlin.fir.resolve.ImplicitReceiverStackImpl +import org.jetbrains.kotlin.fir.resolve.ResolutionMode +import org.jetbrains.kotlin.fir.resolve.calls.ConeInferenceContext +import org.jetbrains.kotlin.fir.resolve.dfa.* +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.resultType +import org.jetbrains.kotlin.fir.resolve.withNullability +import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol +import org.jetbrains.kotlin.fir.symbols.CallableId +import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol +import org.jetbrains.kotlin.fir.types.* +import org.jetbrains.kotlin.fir.visitors.transformSingle +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.types.AbstractTypeChecker +import org.jetbrains.kotlin.utils.addToStdlib.safeAs +import kotlin.IllegalArgumentException + +abstract class FirDataFlowAnalyzer( + protected val components: FirAbstractBodyResolveTransformer.BodyResolveTransformerComponents +) { + companion object { + internal val KOTLIN_BOOLEAN_NOT = CallableId(FqName("kotlin"), FqName("Boolean"), Name.identifier("not")) + + fun createFirDataFlowAnalyzer( + components: FirAbstractBodyResolveTransformer.BodyResolveTransformerComponents + ): FirDataFlowAnalyzer<*> = object : FirDataFlowAnalyzer(components) { + private val receiverStack: ImplicitReceiverStackImpl = components.implicitReceiverStack as ImplicitReceiverStackImpl + + override val logicSystem: PersistentLogicSystem = object : PersistentLogicSystem(any, components.inferenceComponents.ctx) { + override fun processUpdatedReceiverVariable(flow: PersistentFlow, variable: RealVariable) { + val symbol = variable.identifier.symbol + + val index = receiverStack.getReceiverIndex(symbol) ?: return + val info = flow.getKnownInfo(variable) + + if (info == null) { + receiverStack.replaceReceiverType(index, receiverStack.getOriginalType(index)) + } else { + val types = info.exactType.toMutableList().also { + it += receiverStack.getOriginalType(index) + } + receiverStack.replaceReceiverType(index, context.intersectTypesOrNull(types)!!) + } + } + + override fun updateAllReceivers(flow: PersistentFlow) { + receiverStack.mapNotNull { variableStorage[it.boundSymbol, it.receiverExpression] }.forEach { processUpdatedReceiverVariable(flow, it) } + } + } + } + } + + protected abstract val logicSystem: LogicSystem + private val context: ConeInferenceContext = components.inferenceComponents.ctx + + private val graphBuilder = ControlFlowGraphBuilder() + protected val variableStorage = VariableStorage() + private val flowOnNodes = mutableMapOf, FLOW>() + + private val variablesForWhenConditions = mutableMapOf() + + private var contractDescriptionVisitingMode = false + + protected val any = components.session.builtinTypes.anyType.coneTypeUnsafe() + private val nullableNothing = components.session.builtinTypes.nullableNothingType.coneTypeUnsafe() + + // ----------------------------------- Requests ----------------------------------- + + fun getTypeUsingSmartcastInfo(qualifiedAccessExpression: FirQualifiedAccessExpression): Collection? { + /* + * DataFlowAnalyzer holds variables only for declarations that have some smartcast (or can have) + * If there is no useful information there is no data flow variable also + */ + val symbol: AbstractFirBasedSymbol<*> = qualifiedAccessExpression.symbol ?: return null + val variable = variableStorage[symbol, qualifiedAccessExpression] ?: return null + return graphBuilder.lastNode.flow.getKnownInfo(variable)?.exactType + } + + fun returnExpressionsOfAnonymousFunction(function: FirAnonymousFunction): List { + return graphBuilder.returnExpressionsOfAnonymousFunction(function) + } + + // ----------------------------------- Named function ----------------------------------- + + fun enterFunction(function: FirFunction<*>) { + val (functionEnterNode, previousNode) = graphBuilder.enterFunction(function) + if (previousNode == null) { + functionEnterNode.mergeIncomingFlow() + } else { + // Enter anonymous function + assert(functionEnterNode.previousNodes.isEmpty()) + functionEnterNode.flow = logicSystem.forkFlow(previousNode.flow) + } + } + + fun exitFunction(function: FirFunction<*>): ControlFlowGraph? { + val (node, graph) = graphBuilder.exitFunction(function) + if (function.body == null) { + node.mergeIncomingFlow() + } + if (!graphBuilder.isTopLevel()) { + for (valueParameter in function.valueParameters) { + variableStorage.removeRealVariable(valueParameter.symbol) + } + } + if (graphBuilder.isTopLevel()) { + flowOnNodes.clear() + variableStorage.reset() + graphBuilder.reset() + } + return graph + } + + // ----------------------------------- Property ----------------------------------- + + fun enterProperty(property: FirProperty) { + graphBuilder.enterProperty(property).mergeIncomingFlow() + } + + fun exitProperty(property: FirProperty): ControlFlowGraph { + val (node, graph) = graphBuilder.exitProperty(property) + node.mergeIncomingFlow() + return graph + } + + // ----------------------------------- Block ----------------------------------- + + fun enterBlock(block: FirBlock) { + graphBuilder.enterBlock(block)?.mergeIncomingFlow() + } + + fun exitBlock(block: FirBlock) { + graphBuilder.exitBlock(block).mergeIncomingFlow() + } + + // ----------------------------------- Operator call ----------------------------------- + + fun exitTypeOperatorCall(typeOperatorCall: FirTypeOperatorCall) { + val node = graphBuilder.exitTypeOperatorCall(typeOperatorCall).mergeIncomingFlow() + if (typeOperatorCall.operation !in FirOperation.TYPES) return + val type = typeOperatorCall.conversionTypeRef.coneTypeUnsafe() + val operandVariable = variableStorage.getOrCreateVariable(typeOperatorCall.argument) + val flow = node.flow + + when (val operation = typeOperatorCall.operation) { + FirOperation.IS, FirOperation.NOT_IS -> { + val expressionVariable = variableStorage.createSyntheticVariable(typeOperatorCall) + val isNotNullCheck = operation == FirOperation.IS && type.nullability == ConeNullability.NOT_NULL + if (operandVariable.isReal()) { + val hasTypeInfo = operandVariable has type + val hasNotTypeInfo = operandVariable hasNot type + + fun chooseInfo(trueBranch: Boolean) = + if ((typeOperatorCall.operation == FirOperation.IS) == trueBranch) hasTypeInfo else hasNotTypeInfo + + flow.addLogicStatement((expressionVariable eq true) implies chooseInfo(true)) + flow.addLogicStatement((expressionVariable eq false) implies chooseInfo(false)) + + if (operation == FirOperation.NOT_IS && type == nullableNothing) { + flow.addKnownInfo(operandVariable has any) + } + if (isNotNullCheck) { + flow.addLogicStatement((expressionVariable eq true) implies (operandVariable has any)) } + + } else { + if (isNotNullCheck) { + flow.addLogicStatement((expressionVariable eq true) implies (operandVariable notEq null)) + } + } + } + + FirOperation.AS -> { + if (operandVariable.isReal()) { + flow.addKnownInfo(operandVariable has type) + } else { + logicSystem.approveStatementsInsideFlow( + flow, + operandVariable notEq null, + shouldRemoveSynthetics = true, + shouldForkFlow = false + ) + } + } + + FirOperation.SAFE_AS -> { + val expressionVariable = variableStorage.createSyntheticVariable(typeOperatorCall) + if (operandVariable.isReal()) { + flow.addLogicStatement((expressionVariable notEq null) implies (operandVariable has type)) + flow.addLogicStatement((expressionVariable eq null) implies (operandVariable hasNot type)) + } else { + if (type.nullability == ConeNullability.NOT_NULL) { + flow.addLogicStatement((expressionVariable notEq null) implies (operandVariable notEq null)) + } + } + } + + else -> throw IllegalStateException() + } + node.flow = flow + } + + fun exitOperatorCall(operatorCall: FirOperatorCall) { + val node = graphBuilder.exitOperatorCall(operatorCall).mergeIncomingFlow() + when (val operation = operatorCall.operation) { + FirOperation.EQ, FirOperation.NOT_EQ, FirOperation.IDENTITY, FirOperation.NOT_IDENTITY -> { + val leftOperand = operatorCall.arguments[0] + val rightOperand = operatorCall.arguments[1] + + val leftConst = leftOperand as? FirConstExpression<*> + val rightConst = rightOperand as? FirConstExpression<*> + + when { + leftConst != null && rightConst != null -> return + leftConst?.kind == FirConstKind.Null -> processEqNull(node, rightOperand, operation) + rightConst?.kind == FirConstKind.Null -> processEqNull(node, leftOperand, operation) + leftConst != null -> processEqWithConst(node, rightOperand, leftConst, operation) + rightConst != null -> processEqWithConst(node, leftOperand, rightConst, operation) + else -> processEq(node, leftOperand, rightOperand, operation) + } + } + } + } + + // const != null + private fun processEqWithConst(node: OperatorCallNode, operand: FirExpression, const: FirConstExpression<*>, operation: FirOperation) { + val isEq = operation.isEq() + val expressionVariable = variableStorage.createSyntheticVariable(node.fir) + val flow = node.flow + val operandVariable = variableStorage.getOrCreateVariable(operand) + // expression == const -> expression != null + flow.addLogicStatement((expressionVariable eq isEq) implies (operandVariable notEq null)) + if (operandVariable is RealVariable) { + flow.addLogicStatement((expressionVariable eq isEq) implies (operandVariable has any)) + } + + // propagating facts for (... == true) and (... == false) + if (const.kind == FirConstKind.Boolean) { + val constValue = const.value as Boolean + val shouldInvert = isEq xor constValue + + logicSystem.translateConditionalVariableInStatements( + flow, + operandVariable, + expressionVariable, + shouldRemoveOriginalStatements = operandVariable.isSynthetic() + ) { + if (shouldInvert) (it.condition.invert()) implies (it.effect) + else it + } + } + } + + private fun processEq(node: OperatorCallNode, leftOperand: FirExpression, rightOperand: FirExpression, operation: FirOperation) { + val leftIsNullable = leftOperand.coneType?.isMarkedNullable ?: return + val rightIsNullable = rightOperand.coneType?.isMarkedNullable ?: return + // left == right && right not null -> left != null + when { + leftIsNullable && rightIsNullable -> return + leftIsNullable -> processEqNull(node, leftOperand, operation.invert()) + rightIsNullable -> processEqNull(node, rightOperand, operation.invert()) + } + } + + private fun processEqNull(node: OperatorCallNode, operand: FirExpression, operation: FirOperation) { + val flow = node.flow + val expressionVariable = variableStorage.createSyntheticVariable(node.fir) + val operandVariable = variableStorage.getOrCreateVariable(operand) + + val isEq = operation.isEq() + + val predicate = when (isEq) { + true -> operandVariable eq null + false -> operandVariable notEq null + } + + logicSystem.approvePredicate(flow, predicate).forEach { effect -> + flow.addLogicStatement((expressionVariable eq true) implies effect) + flow.addLogicStatement((expressionVariable eq false) implies effect.invert()) + } + + flow.addLogicStatement((expressionVariable eq isEq) implies (operandVariable eq null)) + flow.addLogicStatement((expressionVariable notEq isEq) implies (operandVariable notEq null)) + + if (operandVariable is RealVariable) { + flow.addLogicStatement((expressionVariable eq isEq) implies (operandVariable hasNot any)) + flow.addLogicStatement((expressionVariable notEq isEq) implies (operandVariable has any)) + +// TODO: design do we need casts to Nothing? +// flow.addLogicStatement((expressionVariable eq !isEq) implies (operandVariable has nullableNothing)) +// flow.addLogicStatement((expressionVariable notEq !isEq) implies (operandVariable hasNot nullableNothing)) + } + node.flow = flow + } + + // ----------------------------------- Jump ----------------------------------- + + fun exitJump(jump: FirJump<*>) { + graphBuilder.exitJump(jump).mergeIncomingFlow() + } + + // ----------------------------------- Check not null call ----------------------------------- + + fun exitCheckNotNullCall(checkNotNullCall: FirCheckNotNullCall) { + // Add `Any` to the set of possible types; the intersection type `T? & Any` will be reduced to `T` after smartcast. + val node = graphBuilder.exitCheckNotNullCall(checkNotNullCall).mergeIncomingFlow() + val argument = checkNotNullCall.argument + val operandVariable = variableStorage.getOrCreateRealVariable(argument.symbol, argument) ?: return + node.flow.addKnownInfo(operandVariable has any) + } + + // ----------------------------------- When ----------------------------------- + + fun enterWhenExpression(whenExpression: FirWhenExpression) { + graphBuilder.enterWhenExpression(whenExpression).mergeIncomingFlow() + } + + fun enterWhenBranchCondition(whenBranch: FirWhenBranch) { + val node = graphBuilder.enterWhenBranchCondition(whenBranch).mergeIncomingFlow() + val previousNode = node.previousNodes.single() + if (previousNode is WhenBranchConditionExitNode) { + val conditionVariable = variablesForWhenConditions.remove(previousNode)!! + node.flow = logicSystem.approveStatementsInsideFlow( + node.flow, + conditionVariable eq false, + shouldForkFlow = true, + shouldRemoveSynthetics = true + ) + } + } + + fun exitWhenBranchCondition(whenBranch: FirWhenBranch) { + val (conditionExitNode, branchEnterNode) = graphBuilder.exitWhenBranchCondition(whenBranch) + conditionExitNode.mergeIncomingFlow() + + val conditionVariable = variableStorage.getOrCreateVariable(whenBranch.condition) + variablesForWhenConditions[conditionExitNode] = conditionVariable + branchEnterNode.flow = logicSystem.approveStatementsInsideFlow( + conditionExitNode.flow, + conditionVariable eq true, + shouldForkFlow = true, + shouldRemoveSynthetics = false + ) + } + + fun exitWhenBranchResult(whenBranch: FirWhenBranch) { + graphBuilder.exitWhenBranchResult(whenBranch).mergeIncomingFlow() + } + + fun exitWhenExpression(whenExpression: FirWhenExpression) { + val (whenExitNode, syntheticElseNode) = graphBuilder.exitWhenExpression(whenExpression) + if (syntheticElseNode != null) { + syntheticElseNode.mergeIncomingFlow() + val previousConditionExitNode = syntheticElseNode.previousNodes.single() as? WhenBranchConditionExitNode + // previous node for syntheticElseNode can be not WhenBranchConditionExitNode in case of `when` without any branches + // in that case there will be when enter or subject access node + if (previousConditionExitNode != null) { + val conditionVariable = variablesForWhenConditions.remove(previousConditionExitNode)!! + syntheticElseNode.flow = logicSystem.approveStatementsInsideFlow( + syntheticElseNode.flow, + conditionVariable eq false, + shouldForkFlow = true, + shouldRemoveSynthetics = true + ) + } + } + val previousFlows = whenExitNode.alivePreviousNodes.map { it.flow } + val flow = logicSystem.joinFlow(previousFlows) + whenExitNode.flow = flow + // TODO: wtf? + // val subjectSymbol = whenExpression.subjectVariable?.symbol + // if (subjectSymbol != null) { + // variableStorage[subjectSymbol]?.let { flow = flow.removeVariable(it) } + // } + // node.flow = flow + } + + // ----------------------------------- While Loop ----------------------------------- + + fun enterWhileLoop(loop: FirLoop) { + val (loopEnterNode, loopConditionEnterNode) = graphBuilder.enterWhileLoop(loop) + loopEnterNode.mergeIncomingFlow() + loopConditionEnterNode.mergeIncomingFlow() + } + + fun exitWhileLoopCondition(loop: FirLoop) { + val (loopConditionExitNode, loopBlockEnterNode) = graphBuilder.exitWhileLoopCondition(loop) + loopConditionExitNode.mergeIncomingFlow() + loopBlockEnterNode.mergeIncomingFlow() + variableStorage[loop.condition]?.let { conditionVariable -> + loopBlockEnterNode.flow = logicSystem.approveStatementsInsideFlow( + loopBlockEnterNode.flow, + conditionVariable eq true, + shouldForkFlow = false, + shouldRemoveSynthetics = true + ) + } + } + + fun exitWhileLoop(loop: FirLoop) { + val (blockExitNode, exitNode) = graphBuilder.exitWhileLoop(loop) + blockExitNode.mergeIncomingFlow() + exitNode.mergeIncomingFlow() + } + + // ----------------------------------- Do while Loop ----------------------------------- + + fun enterDoWhileLoop(loop: FirLoop) { + val (loopEnterNode, loopBlockEnterNode) = graphBuilder.enterDoWhileLoop(loop) + loopEnterNode.mergeIncomingFlow() + loopBlockEnterNode.mergeIncomingFlow() + } + + fun enterDoWhileLoopCondition(loop: FirLoop) { + val (loopBlockExitNode, loopConditionEnterNode) = graphBuilder.enterDoWhileLoopCondition(loop) + loopBlockExitNode.mergeIncomingFlow() + loopConditionEnterNode.mergeIncomingFlow() + } + + fun exitDoWhileLoop(loop: FirLoop) { + val (loopConditionExitNode, loopExitNode) = graphBuilder.exitDoWhileLoop(loop) + loopConditionExitNode.mergeIncomingFlow() + loopExitNode.mergeIncomingFlow() + } + + // ----------------------------------- Try-catch-finally ----------------------------------- + + fun enterTryExpression(tryExpression: FirTryExpression) { + val (tryExpressionEnterNode, tryMainBlockEnterNode) = graphBuilder.enterTryExpression(tryExpression) + tryExpressionEnterNode.mergeIncomingFlow() + tryMainBlockEnterNode.mergeIncomingFlow() + } + + fun exitTryMainBlock(tryExpression: FirTryExpression) { + graphBuilder.exitTryMainBlock(tryExpression).mergeIncomingFlow() + } + + fun enterCatchClause(catch: FirCatch) { + graphBuilder.enterCatchClause(catch).mergeIncomingFlow() + } + + fun exitCatchClause(catch: FirCatch) { + graphBuilder.exitCatchClause(catch).mergeIncomingFlow() + } + + fun enterFinallyBlock(tryExpression: FirTryExpression) { + // TODO + graphBuilder.enterFinallyBlock(tryExpression).mergeIncomingFlow() + } + + fun exitFinallyBlock(tryExpression: FirTryExpression) { + // TODO + graphBuilder.exitFinallyBlock(tryExpression).mergeIncomingFlow() + } + + fun exitTryExpression(tryExpression: FirTryExpression) { + // TODO + graphBuilder.exitTryExpression(tryExpression).mergeIncomingFlow() + } + + // ----------------------------------- Resolvable call ----------------------------------- + + fun enterQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) { + enterSafeCall(qualifiedAccessExpression) + } + + private fun enterSafeCall(qualifiedAccess: FirQualifiedAccess) { + if (!qualifiedAccess.safe) return + val node = graphBuilder.enterSafeCall(qualifiedAccess).mergeIncomingFlow() + val previousNode = node.alivePreviousNodes.first() + val shouldFork: Boolean + var flow = if (previousNode is ExitSafeCallNode) { + shouldFork = false + previousNode.alivePreviousNodes.getOrNull(1)?.flow ?: node.flow + } else { + shouldFork = true + node.flow + } + qualifiedAccess.explicitReceiver?.let { receiver -> + val type = receiver.coneType + ?.takeIf { it.isMarkedNullable } + ?.withNullability(ConeNullability.NOT_NULL) + ?: return@let + + when (val variable = variableStorage.getOrCreateVariable(receiver)) { + is RealVariable -> { + if (shouldFork) { + flow = logicSystem.forkFlow(flow) + } + flow.addKnownInfo(variable has type) + } + is SyntheticVariable -> { + flow = logicSystem.approveStatementsInsideFlow( + flow, + variable notEq null, + shouldFork, + shouldRemoveSynthetics = true + ) + } + } + } + + node.flow = flow + } + + fun exitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) { + graphBuilder.exitQualifiedAccessExpression(qualifiedAccessExpression).mergeIncomingFlow() + exitSafeCall(qualifiedAccessExpression) + } + + fun exitFunctionCall(functionCall: FirFunctionCall) { + val node = graphBuilder.exitFunctionCall(functionCall).mergeIncomingFlow() + if (functionCall.isBooleanNot()) { + exitBooleanNot(functionCall, node) + } + processConditionalContract(functionCall) + if (functionCall.safe) { + exitSafeCall(functionCall) + } + } + + private fun exitSafeCall(qualifiedAccess: FirQualifiedAccess) { + if (!qualifiedAccess.safe) return + val node = graphBuilder.exitSafeCall(qualifiedAccess).mergeIncomingFlow() + val variable = variableStorage.getOrCreateVariable(qualifiedAccess) + val receiverVariable = when (variable) { + is RealVariable -> variable.explicitReceiverVariable!! + is SyntheticVariable -> variableStorage.getOrCreateVariable(qualifiedAccess.explicitReceiver!!) + } + logicSystem.addLogicStatement(node.flow, (variable notEq null) implies (receiverVariable notEq null)) + if (receiverVariable.isReal()) { + logicSystem.addLogicStatement(node.flow, (variable notEq null) implies (receiverVariable has any)) + } + } + + private fun processConditionalContract(functionCall: FirFunctionCall) { + val contractDescription = (functionCall.symbol as? FirNamedFunctionSymbol)?.fir?.contractDescription ?: return + val conditionalEffects = contractDescription.effects.filterIsInstance() + if (conditionalEffects.isEmpty()) return + val argumentsMapping = createArgumentsMapping(functionCall) ?: return + contractDescriptionVisitingMode = true + graphBuilder.enterContract(functionCall).mergeIncomingFlow() + val functionCallVariable = variableStorage.getOrCreateVariable(functionCall) + for (conditionalEffect in conditionalEffects) { + val fir = conditionalEffect.buildContractFir(argumentsMapping) ?: continue + val effect = conditionalEffect.effect as? ConeReturnsEffectDeclaration ?: continue + fir.transformSingle(components.transformer, ResolutionMode.ContextDependent) + val argumentVariable = variableStorage.getOrCreateVariable(fir) + val lastNode = graphBuilder.lastNode + when (val value = effect.value) { + ConeConstantReference.WILDCARD -> { + lastNode.flow = logicSystem.approveStatementsInsideFlow( + lastNode.flow, + argumentVariable eq true, + shouldForkFlow = false, + shouldRemoveSynthetics = true + ) + } + + is ConeBooleanConstantReference -> { + logicSystem.replaceConditionalVariableInStatements( + lastNode.flow, + argumentVariable, + functionCallVariable, + filter = { it.condition.condition == value.toCondition() } + ) + } + + ConeConstantReference.NOT_NULL, ConeConstantReference.NULL -> { + logicSystem.replaceConditionalVariableInStatements( + lastNode.flow, + argumentVariable, + functionCallVariable, + filter = { it.condition.condition == Condition.EqTrue }, + transform = { Predicate(it.condition.variable, value.toCondition()) implies it.effect } + ) + } + + else -> throw IllegalArgumentException("Unsupported constant reference: $value") + } + } + graphBuilder.exitContract(functionCall).mergeIncomingFlow() + contractDescriptionVisitingMode = true + } + + fun exitConstExpresion(constExpression: FirConstExpression<*>) { + if (constExpression.resultType is FirResolvedTypeRef && !contractDescriptionVisitingMode) return + graphBuilder.exitConstExpresion(constExpression).mergeIncomingFlow() + } + + fun exitVariableDeclaration(variable: FirProperty) { + val node = graphBuilder.exitVariableDeclaration(variable).mergeIncomingFlow() + val initializer = variable.initializer ?: return + exitVariableInitialization(node, initializer, variable, isVariableDeclaration = true) + } + + private fun exitVariableInitialization(node: CFGNode<*>, initializer: FirExpression, variable: FirProperty, isVariableDeclaration: Boolean) { + val propertyVariable = variableStorage.getOrCreateRealVariable(variable.symbol, variable) + if (!isVariableDeclaration) { + node.flow.removeAllAboutVariable(propertyVariable) + variableStorage.unboundPossiblyAliasedVariable(variable.symbol) + } + + variableStorage[initializer]?.safeAs()?.let { initializerVariable -> + /* + * That part is needed for cases like that: + * + * val b = x is String + * ... + * if (b) { + * x.length + * } + */ + logicSystem.replaceConditionalVariableInStatements(node.flow, initializerVariable, propertyVariable) + return + } + + variableStorage.getOrCreateRealVariable(initializer.symbol, initializer)?.let { initializerVariable -> + if (initializerVariable.isStable) { + variableStorage.attachSymbolToVariable(variable.symbol, initializerVariable) + } else { + node.flow.addLogicStatement((propertyVariable notEq null) implies (initializerVariable notEq null)) + } + } + + if (!isVariableDeclaration) { + node.flow.addKnownInfo(propertyVariable has initializer.typeRef.coneTypeUnsafe()) + } + } + + fun exitVariableAssignment(assignment: FirVariableAssignment) { + val node = graphBuilder.exitVariableAssignment(assignment).mergeIncomingFlow() + val property = (assignment.lValue as? FirResolvedNamedReference)?.resolvedSymbol?.fir as? FirProperty ?: return //error("left side of assignment should have symbol") + if (!property.isLocal) return + exitVariableInitialization(node, assignment.rValue, property, isVariableDeclaration = false) + } + + fun exitThrowExceptionNode(throwExpression: FirThrowExpression) { + graphBuilder.exitThrowExceptionNode(throwExpression).mergeIncomingFlow() + } + + // ----------------------------------- Boolean operators ----------------------------------- + + fun enterBinaryAnd(binaryLogicExpression: FirBinaryLogicExpression) { + graphBuilder.enterBinaryAnd(binaryLogicExpression).mergeIncomingFlow() + } + + fun exitLeftBinaryAndArgument(binaryLogicExpression: FirBinaryLogicExpression) { + val (leftNode, rightNode) = graphBuilder.exitLeftBinaryAndArgument(binaryLogicExpression) + exitLeftArgumentOfBinaryBooleanOperator(leftNode, rightNode, isAnd = true) + } + + fun exitBinaryAnd(binaryLogicExpression: FirBinaryLogicExpression) { + val node = graphBuilder.exitBinaryAnd(binaryLogicExpression) + exitBinaryBooleanOperator(binaryLogicExpression, node, isAnd = true) + } + + fun enterBinaryOr(binaryLogicExpression: FirBinaryLogicExpression) { + graphBuilder.enterBinaryOr(binaryLogicExpression).mergeIncomingFlow() + } + + fun exitLeftBinaryOrArgument(binaryLogicExpression: FirBinaryLogicExpression) { + val (leftNode, rightNode) = graphBuilder.exitLeftBinaryOrArgument(binaryLogicExpression) + exitLeftArgumentOfBinaryBooleanOperator(leftNode, rightNode, isAnd = false) + } + + fun exitBinaryOr(binaryLogicExpression: FirBinaryLogicExpression) { + val node = graphBuilder.exitBinaryOr(binaryLogicExpression) + exitBinaryBooleanOperator(binaryLogicExpression, node, isAnd = false) + } + + private fun exitLeftArgumentOfBinaryBooleanOperator(leftNode: CFGNode<*>, rightNode: CFGNode<*>, isAnd: Boolean) { + val parentFlow = leftNode.alivePreviousNodes.first().flow + leftNode.flow = logicSystem.forkFlow(parentFlow) + val leftOperandVariable = variableStorage.getOrCreateVariable(leftNode.previousNodes.first().fir) + rightNode.flow = logicSystem.approveStatementsInsideFlow( + parentFlow, + leftOperandVariable eq isAnd, + shouldForkFlow = true, + shouldRemoveSynthetics = false + ) + } + + private fun exitBinaryBooleanOperator( + binaryLogicExpression: FirBinaryLogicExpression, + node: AbstractBinaryExitNode<*>, + isAnd: Boolean + ) { + val bothEvaluated = isAnd + val onlyLeftEvaluated = !bothEvaluated + + // Naming for all variables was chosen in assumption that we processing && expression + val flowFromLeft = node.leftOperandNode.flow + val flowFromRight = node.rightOperandNode.flow + + val flow = node.mergeIncomingFlow().flow + + val leftVariable = variableStorage.getOrCreateVariable(binaryLogicExpression.leftOperand) + val rightVariable = variableStorage.getOrCreateVariable(binaryLogicExpression.rightOperand) + val operatorVariable = variableStorage.getOrCreateVariable(binaryLogicExpression) + + val (conditionalFromLeft, conditionalFromRight, approvedFromRight) = logicSystem.collectInfoForBooleanOperator( + flowFromLeft, + leftVariable, + flowFromRight, + rightVariable + ) + + // left && right == True + // left || right == False + val approvedIfTrue: MutableKnownFacts = mutableMapOf() + logicSystem.approvePredicateTo(approvedIfTrue, flowFromRight, leftVariable eq bothEvaluated, conditionalFromLeft) + logicSystem.approvePredicateTo(approvedIfTrue, flowFromRight, rightVariable eq bothEvaluated, conditionalFromRight) + approvedFromRight.forEach { (variable, info) -> + approvedIfTrue.addInfo(variable, info) + } + approvedIfTrue.values.forEach { info -> + flow.addLogicStatement((operatorVariable eq bothEvaluated) implies info) + } + + // left && right == False + // left || right == True + val approvedIfFalse: MutableKnownFacts = mutableMapOf() + val leftIsFalse = logicSystem.approvePredicate(flowFromLeft, leftVariable eq onlyLeftEvaluated, conditionalFromLeft) + val rightIsFalse = logicSystem.approvePredicate(flowFromRight, rightVariable eq onlyLeftEvaluated, conditionalFromRight) + approvedIfFalse.mergeInfo(logicSystem.orForVerifiedFacts(leftIsFalse, rightIsFalse)) + approvedIfFalse.values.forEach { info -> + flow.addLogicStatement((operatorVariable eq onlyLeftEvaluated) implies info) + } + + node.flow = flow + + variableStorage.removeSyntheticVariable(leftVariable) + variableStorage.removeSyntheticVariable(rightVariable) + } + + + private fun exitBooleanNot(functionCall: FirFunctionCall, node: FunctionCallNode) { + val booleanExpressionVariable = variableStorage.getOrCreateVariable(node.previousNodes.first().fir) + val variable = variableStorage.getOrCreateVariable(functionCall) + logicSystem.replaceConditionalVariableInStatements( + node.flow, + booleanExpressionVariable, + variable, + transform = { it.invertCondition() } + ) + } + + // ----------------------------------- Annotations ----------------------------------- + + fun enterAnnotationCall(annotationCall: FirAnnotationCall) { + graphBuilder.enterAnnotationCall(annotationCall).mergeIncomingFlow() + } + + fun exitAnnotationCall(annotationCall: FirAnnotationCall) { + graphBuilder.exitAnnotationCall(annotationCall).mergeIncomingFlow() + } + + // ----------------------------------- Init block ----------------------------------- + + fun enterInitBlock(initBlock: FirAnonymousInitializer) { + graphBuilder.enterInitBlock(initBlock).mergeIncomingFlow() + } + + fun exitInitBlock(initBlock: FirAnonymousInitializer) { + graphBuilder.exitInitBlock(initBlock).mergeIncomingFlow() + } + + // ------------------------------------------------------ Utils ------------------------------------------------------ + + private var CFGNode<*>.flow: FLOW + get() = flowOnNodes.getValue(this.origin) + set(value) { + flowOnNodes[this.origin] = value + } + + private val CFGNode<*>.origin: CFGNode<*> get() = if (this is StubNode) previousNodes.first() else this + + private fun > T.mergeIncomingFlow(): T = this.also { node -> + val previousFlows = node.alivePreviousNodes.map { it.flow } + node.flow = logicSystem.joinFlow(previousFlows) + } + + private fun FLOW.addLogicStatement(statement: LogicStatement) { + logicSystem.addLogicStatement(this, statement) + } + + private fun FLOW.addKnownInfo(info: DataFlowInfo) { + logicSystem.addKnownInfo(this, info) + } + + private fun FLOW.removeAllAboutVariable(variable: RealVariable?) { + if (variable == null) return + logicSystem.removeAllAboutVariable(this, variable) + } +} \ No newline at end of file diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/LogicSystem.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/LogicSystem.kt new file mode 100644 index 00000000000..39c6b19666d --- /dev/null +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/LogicSystem.kt @@ -0,0 +1,161 @@ +/* + * 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.new + +import org.jetbrains.kotlin.fir.resolve.calls.ConeInferenceContext +import org.jetbrains.kotlin.fir.types.ConeKotlinType + +abstract class Flow { + abstract fun getKnownInfo(variable: RealVariable): DataFlowInfo? + abstract fun getLogicStatements(variable: DataFlowVariable): Collection + abstract fun getVariablesInKnownInfos(): Collection + abstract fun removeConditions(variable: DataFlowVariable): Collection +} + +abstract class LogicSystem(protected val context: ConeInferenceContext) { + // ------------------------------- Flow operations ------------------------------- + + abstract fun createEmptyFlow(): FLOW + abstract fun forkFlow(flow: FLOW): FLOW + abstract fun joinFlow(flows: Collection): FLOW + + abstract fun addKnownInfo(flow: FLOW, info: DataFlowInfo) + + abstract fun addLogicStatement(flow: FLOW, statement: LogicStatement) + + abstract fun removeAllAboutVariable(flow: FLOW, variable: RealVariable) + + abstract fun translateConditionalVariableInStatements( + flow: FLOW, + originalVariable: DataFlowVariable, + newVariable: DataFlowVariable, + shouldRemoveOriginalStatements: Boolean, + filter: (LogicStatement) -> Boolean = { true }, + transform: (LogicStatement) -> LogicStatement = { it } + ) + + abstract fun approveStatementsInsideFlow( + flow: FLOW, + predicate: Predicate, + shouldForkFlow: Boolean, + shouldRemoveSynthetics: Boolean + ): FLOW + + protected abstract fun getLogicStatementsWithVariable(flow: FLOW, variable: DataFlowVariable): Collection + + // ------------------------------- Callbacks for updating implicit receiver stack ------------------------------- + + abstract fun processUpdatedReceiverVariable(flow: FLOW, variable: RealVariable) + abstract fun updateAllReceivers(flow: FLOW) + + // ------------------------------- Public DataFlowInfo util functions ------------------------------- + + data class InfoForBooleanOperator( + val conditionalFromLeft: Collection, + val conditionalFromRight: Collection, + val knownFromRight: KnownInfos + ) + + abstract fun collectInfoForBooleanOperator( + leftFlow: FLOW, + leftVariable: DataFlowVariable, + rightFlow: FLOW, + rightVariable: DataFlowVariable + ): InfoForBooleanOperator + + abstract fun approvePredicateTo( + destination: MutableKnownFacts, + flow: FLOW, + predicate: Predicate, + statements: Collection + ) + + /** + * Recursively collects all DataFlowInfos approved by [predicate] and all predicates + * that has been implied by it + * TODO: or not recursively? + */ + fun approvePredicate(flow: FLOW, predicate: Predicate): Collection { + val statements = getLogicStatementsWithVariable(flow, predicate.variable) + return approvePredicate(flow, predicate, statements).values + } + + fun orForVerifiedFacts( + left: KnownInfos, + right: KnownInfos + ): MutableKnownFacts { + if (left.isNullOrEmpty() || right.isNullOrEmpty()) return mutableMapOf() + val map = mutableMapOf() + for (variable in left.keys.intersect(right.keys)) { + val leftInfo = left.getValue(variable) + val rightInfo = right.getValue(variable) + map[variable] = or(listOf(leftInfo, rightInfo)) + } + return map + } + + // ------------------------------- Util functions ------------------------------- + + // TODO + protected fun Collection>.intersectSets(): Set { + if (isEmpty()) return emptySet() + val iterator = iterator() + val result = HashSet(iterator.next()) + while (iterator.hasNext()) { + result.retainAll(iterator.next()) + } + return result + } + + protected fun or(infos: Collection): MutableDataFlowInfo { + require(infos.isNotEmpty()) + infos.singleOrNull()?.let { return it as MutableDataFlowInfo } + val variable = infos.first().variable + assert(infos.all { it.variable == variable }) + val exactType = orTypes(infos.map { it.exactType }) + val exactNotType = orTypes(infos.map { it.exactNotType }) + return MutableDataFlowInfo(variable, exactType, exactNotType) + } + + private fun orTypes(types: Collection>): MutableSet { + if (types.any { it.isEmpty() }) return mutableSetOf() + val allTypes = types.flatMapTo(mutableSetOf()) { it } + val commonTypes = allTypes.toMutableSet() + types.forEach { commonTypes.retainAll(it) } + val differentTypes = allTypes - commonTypes + context.commonSuperTypeOrNull(differentTypes.toList())?.let { commonTypes += it } + return commonTypes + } +} + +fun LogicSystem.approvePredicate(flow: FLOW, predicate: Predicate, statements: Collection): MutableKnownFacts { + return mutableMapOf().apply { + approvePredicateTo(this, flow, predicate, statements) + } +} + +/* + * used for: + * 1. val b = x is String + * 2. b = x is String + * 3. !b | b.not() for Booleans + */ +fun LogicSystem.replaceConditionalVariableInStatements( + flow: F, + originalVariable: DataFlowVariable, + newVariable: DataFlowVariable, + filter: (LogicStatement) -> Boolean = { true }, + transform: (LogicStatement) -> LogicStatement = { it } +) { + translateConditionalVariableInStatements( + flow, + originalVariable, + newVariable, + shouldRemoveOriginalStatements = true, + filter, + transform + ) +} \ No newline at end of file diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/PersistentLogicSystem.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/PersistentLogicSystem.kt new file mode 100644 index 00000000000..8f81a924538 --- /dev/null +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/PersistentLogicSystem.kt @@ -0,0 +1,411 @@ +/* + * 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.new + +import com.google.common.collect.ArrayListMultimap +import kotlinx.collections.immutable.* +import org.jetbrains.kotlin.fir.resolve.calls.ConeInferenceContext +import org.jetbrains.kotlin.fir.types.ConeKotlinType +import java.util.* +import kotlin.NoSuchElementException +import kotlin.collections.HashSet + +data class PersistentDataFlowInfo( + override val variable: RealVariable, + override val exactType: PersistentSet, + override val exactNotType: PersistentSet +) : DataFlowInfo() { + override operator fun plus(other: DataFlowInfo): PersistentDataFlowInfo { + return PersistentDataFlowInfo( + variable, + exactType + other.exactType, + exactNotType + other.exactNotType + ) + } + + override val isEmpty: Boolean + get() = exactType.isEmpty() && exactNotType.isEmpty() + + override fun invert(): PersistentDataFlowInfo { + return PersistentDataFlowInfo(variable, exactNotType, exactType) + } +} + +typealias PersistentKnownFacts = PersistentMap +typealias PersistentLogicStatements = PersistentMap> + +class PersistentFlow : Flow { + val previousFlow: PersistentFlow? + var knownFacts: PersistentKnownFacts + var logicStatements: PersistentLogicStatements + val level: Int + var knownFactsDiff: PersistentKnownFacts = persistentHashMapOf() + + constructor(previousFlow: PersistentFlow) { + this.previousFlow = previousFlow + knownFacts = previousFlow.knownFacts + logicStatements = previousFlow.logicStatements + level = previousFlow.level + 1 + } + + constructor() { + previousFlow = null + knownFacts = persistentHashMapOf() + logicStatements = persistentHashMapOf() + level = 1 + } + + override fun getKnownInfo(variable: RealVariable): DataFlowInfo? { + return knownFacts[variable] + } + + override fun getLogicStatements(variable: DataFlowVariable): Collection { + return logicStatements[variable] ?: emptyList() + } + + override fun getVariablesInKnownInfos(): Collection { + return knownFacts.keys + } + + override fun removeConditions(variable: DataFlowVariable): Collection { + return getLogicStatements(variable).also { + if (it.isNotEmpty()) { + logicStatements -= variable + } + } + } +} + +abstract class PersistentLogicSystem(private val anyType: ConeKotlinType, context: ConeInferenceContext) : + LogicSystem(context) { + override fun createEmptyFlow(): PersistentFlow { + return PersistentFlow() + } + + override fun forkFlow(flow: PersistentFlow): PersistentFlow { + return PersistentFlow(flow) + } + + override fun joinFlow(flows: Collection): PersistentFlow { + if (flows.isEmpty()) return createEmptyFlow() + flows.singleOrNull()?.let { return it } + val commonFlow = flows.reduce(::lowestCommonFlow) + val commonVariables = flows.map { it.diffVariablesIterable(commonFlow).toList() } + .intersectSets() + .takeIf { it.isNotEmpty() } + ?: return commonFlow + + for (variable in commonVariables) { + val info = or(flows.map { it.getKnownFactsDiff(variable, commonFlow) }) + if (info.isEmpty) continue + commonFlow.knownFacts = commonFlow.knownFacts.addNewInfo(info) + if (commonFlow.previousFlow != null) { + commonFlow.knownFactsDiff = commonFlow.knownFactsDiff.addNewInfo(info) + } + } + + updateAllReceivers(commonFlow) + + return commonFlow + } + + private fun PersistentFlow.getKnownFactsDiff(variable: RealVariable, parentFlow: PersistentFlow): MutableDataFlowInfo { + var flow = this + val result = MutableDataFlowInfo(variable) + while (flow != parentFlow) { + flow.knownFactsDiff[variable]?.let { + result += it + } + flow = flow.previousFlow!! + } + return result + } + + /** + * This is an iterable over real variable that has known facts in flow range + * from [this] to [parentFlow] + */ + private fun PersistentFlow.diffVariablesIterable(parentFlow: PersistentFlow): Iterable = + object : DiffIterable(parentFlow, this) { + override fun extractIterator(flow: PersistentFlow): Iterator { + return flow.knownFactsDiff.keys.iterator() + } + } + + private abstract class DiffIterable(private val parentFlow: PersistentFlow, private var currentFlow: PersistentFlow) : Iterable { + private var currentIterator = extractIterator(currentFlow) + + abstract fun extractIterator(flow: PersistentFlow): Iterator + + override fun iterator(): Iterator { + return object : Iterator { + override fun hasNext(): Boolean { + if (currentIterator.hasNext()) return true + while (currentFlow != parentFlow) { + currentFlow = currentFlow.previousFlow!! + currentIterator = extractIterator(currentFlow) + if (currentIterator.hasNext()) return true + } + return false + } + + override fun next(): T { + if (!hasNext()) { + throw NoSuchElementException() + } + return currentIterator.next() + } + } + } + } + + override fun addKnownInfo(flow: PersistentFlow, info: DataFlowInfo) { + with(flow) { + knownFacts = knownFacts.addNewInfo(info) + if (previousFlow != null) { + knownFactsDiff = knownFactsDiff.addNewInfo(info) + } + if (info.variable.isThisReference) { + processUpdatedReceiverVariable(flow, info.variable) + } + } + } + + override fun addLogicStatement(flow: PersistentFlow, statement: LogicStatement) { + if (statement.condition == statement.effect) return + with(flow) { + val variable = statement.condition.variable + val existingFacts = logicStatements[variable] + logicStatements = if (existingFacts == null) { + logicStatements.put(variable, persistentListOf(statement)) + } else { + logicStatements.put(variable, existingFacts + statement) + } + } + } + + override fun removeAllAboutVariable(flow: PersistentFlow, variable: RealVariable) { + flow.knownFacts -= variable + flow.knownFactsDiff -= variable + // TODO: should we search variable in all logic statements? + } + + override fun translateConditionalVariableInStatements( + flow: PersistentFlow, + originalVariable: DataFlowVariable, + newVariable: DataFlowVariable, + shouldRemoveOriginalStatements: Boolean, + filter: (LogicStatement) -> Boolean, + transform: (LogicStatement) -> LogicStatement + ) { + with(flow) { + val statements = logicStatements[originalVariable]?.takeIf { it.isNotEmpty() } ?: return + val newStatements = statements.filter(filter).map { + val newStatement = Predicate(newVariable, it.condition.condition) implies it.effect + transform(newStatement) + }.toPersistentList() + if (shouldRemoveOriginalStatements) { + logicStatements -= originalVariable + } + logicStatements = logicStatements.put(newVariable, newStatements) + } + } + + override fun approveStatementsInsideFlow( + flow: PersistentFlow, + predicate: Predicate, + shouldForkFlow: Boolean, + shouldRemoveSynthetics: Boolean + ): PersistentFlow { + val approvedFacts = approvePredicatesInternal( + flow, + predicate, + initialStatements = null, + shouldRemoveSynthetics + ) + + val resultFlow = if (shouldForkFlow) forkFlow(flow) else flow + if (approvedFacts.isEmpty) return resultFlow + + val updatedReceivers = mutableSetOf() + approvedFacts.asMap().forEach { (variable, infos) -> + var resultInfo = PersistentDataFlowInfo(variable, persistentSetOf(), persistentSetOf()) + for (info in infos) { + resultInfo += info + } + if (variable.isThisReference) { + updatedReceivers += variable + } + addKnownInfo(resultFlow, resultInfo) + } + + updatedReceivers.forEach { + processUpdatedReceiverVariable(resultFlow, it) + } + + return resultFlow + } + + private fun approvePredicatesInternal( + flow: PersistentFlow, + predicate: Predicate, + initialStatements: Collection?, + shouldRemoveSynthetics: Boolean + ): ArrayListMultimap { + val approvedFacts: ArrayListMultimap = ArrayListMultimap.create() + val predicatesToApprove = LinkedList().apply { this += predicate } + approvePredicatesInternal(flow, predicatesToApprove, initialStatements, shouldRemoveSynthetics, approvedFacts) + return approvedFacts + } + + private fun approvePredicatesInternal( + flow: PersistentFlow, + predicatesToApprove: LinkedList, + initialStatements: Collection?, + shouldRemoveSynthetics: Boolean, + approvedFacts: ArrayListMultimap + ) { + if (predicatesToApprove.isEmpty()) return + val approvedVariables = mutableSetOf() + val approvedPredicates = mutableSetOf() + var firstIteration = true + while (predicatesToApprove.isNotEmpty()) { + @Suppress("NAME_SHADOWING") + val predicate: Predicate = predicatesToApprove.removeFirst() + // Defense from cycles in facts + if (!approvedPredicates.add(predicate)) { + continue + } + val statements = initialStatements?.takeIf { firstIteration } + ?: flow.logicStatements[predicate.variable]?.takeIf { it.isNotEmpty() } + ?: continue + if (shouldRemoveSynthetics && predicate.variable.isSynthetic()) { + flow.logicStatements -= predicate.variable + } + for (statement in statements) { + if (statement.condition == predicate) { + when (val effect = statement.effect) { + is Predicate -> predicatesToApprove += effect + is DataFlowInfo -> { + approvedFacts.put(effect.variable, effect) + approvedVariables += effect.variable + } + } + } + } + firstIteration = false + } + + val newPredicates = LinkedList() + for (approvedVariable in approvedVariables) { + var variable = approvedVariable + foo@ while (variable.explicitReceiverVariable != null && variable.isSafeCall) { + when (val receiver = variable.explicitReceiverVariable!!) { + is RealVariable -> { + approvedFacts.put(receiver, receiver has anyType) + variable = receiver + } + is SyntheticVariable -> { + newPredicates += receiver notEq null + break@foo + } + else -> throw IllegalStateException() + } + } + } + approvePredicatesInternal(flow, newPredicates, initialStatements = null, shouldRemoveSynthetics, approvedFacts) + } + + override fun approvePredicateTo( + destination: MutableKnownFacts, + flow: PersistentFlow, + predicate: Predicate, + statements: Collection + ) { + val approvePredicates = approvePredicatesInternal(flow, predicate, statements, shouldRemoveSynthetics = false) + approvePredicates.asMap().forEach { (variable, infos) -> + for (info in infos) { + val mutableInfo = info.asMutableInfo() + destination.put(variable, mutableInfo) { + it += mutableInfo + it + } + } + } + } + + override fun collectInfoForBooleanOperator( + leftFlow: PersistentFlow, + leftVariable: DataFlowVariable, + rightFlow: PersistentFlow, + rightVariable: DataFlowVariable + ): InfoForBooleanOperator { + return InfoForBooleanOperator( + leftFlow.logicStatements[leftVariable] ?: emptyList(), + rightFlow.logicStatements[rightVariable] ?: emptyList(), + rightFlow.knownFactsDiff + ) + } + + override fun getLogicStatementsWithVariable(flow: PersistentFlow, variable: DataFlowVariable): Collection { + return flow.logicStatements[variable] ?: emptyList() + } + + // --------------------------------------------------------------------\ +} + +private fun lowestCommonFlow(left: PersistentFlow, right: PersistentFlow): PersistentFlow { + val level = minOf(left.level, right.level) + + @Suppress("NAME_SHADOWING") + var left = left + while (left.level > level) { + left = left.previousFlow!! + } + @Suppress("NAME_SHADOWING") + var right = right + while (right.level > level) { + right = right.previousFlow!! + } + while (left != right) { + left = left.previousFlow!! + right = right.previousFlow!! + } + return left +} + +private fun Collection>.intersectSets(): Set { + if (isEmpty()) return emptySet() + val iterator = iterator() + val result = HashSet(iterator.next()) + while (iterator.hasNext()) { + result.retainAll(iterator.next()) + } + return result +} + +private fun PersistentKnownFacts.addNewInfo(info: DataFlowInfo): PersistentKnownFacts { + val variable = info.variable + val existingInfo = this[variable] + return if (existingInfo == null) { + val persistentInfo = if (info is PersistentDataFlowInfo) info else info.toPersistent() + put(variable, persistentInfo) + } else { + put(variable, existingInfo + info) + } +} + +private fun DataFlowInfo.toPersistent(): PersistentDataFlowInfo = PersistentDataFlowInfo( + variable, + exactType.toPersistentSet(), + exactNotType.toPersistentSet() +) + +fun DataFlowInfo.asMutableInfo(): MutableDataFlowInfo = when (this) { + is MutableDataFlowInfo -> this + is PersistentDataFlowInfo -> MutableDataFlowInfo(variable, exactType.toMutableSet(), exactNotType.toMutableSet()) + else -> throw IllegalArgumentException("Unknown DataFlowInfo type: ${this::class}") +} diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/VariableStorage.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/VariableStorage.kt new file mode 100644 index 00000000000..0d8c1c3ca46 --- /dev/null +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/VariableStorage.kt @@ -0,0 +1,152 @@ +/* + * 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.new + +import org.jetbrains.kotlin.fir.FirElement +import org.jetbrains.kotlin.fir.FirSymbolOwner +import org.jetbrains.kotlin.fir.expressions.* +import org.jetbrains.kotlin.fir.expressions.impl.FirNoReceiverExpression +import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference +import org.jetbrains.kotlin.fir.references.FirThisReference +import org.jetbrains.kotlin.fir.references.impl.FirExplicitThisReference +import org.jetbrains.kotlin.fir.resolve.calls.FirNamedReferenceWithCandidate +import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol + +class VariableStorage { + private var counter = 1 + private val realVariables: MutableMap = HashMap() + private val syntheticVariables: MutableMap = HashMap() + private val localVariableAliases: MutableMap, Identifier> = HashMap() + + fun getOrCreateRealVariable(symbol: AbstractFirBasedSymbol<*>, fir: FirElement): RealVariable { + val realFir = fir.unwrapElement() + val identifier = getIdentifierBySymbol(symbol, realFir) + return realVariables.getOrPut(identifier) { createRealVariableInternal(identifier, realFir) } + } + + private fun FirElement.unwrapElement(): FirElement = when (this) { + is FirWhenSubjectExpression -> whenSubject.whenExpression.let { it.subjectVariable ?: it.subject } ?: this + is FirExpressionWithSmartcast -> originalExpression.unwrapElement() + else -> this + } + + private fun getIdentifierBySymbol( + symbol: AbstractFirBasedSymbol<*>, + fir: FirElement, + ): Identifier { + return localVariableAliases[symbol] ?: run { + val expression = fir as? FirQualifiedAccessExpression + Identifier( + symbol, + expression?.dispatchReceiver?.takeIf { it != FirNoReceiverExpression }?.let(this::getOrCreateVariable), + expression?.extensionReceiver?.takeIf { it != FirNoReceiverExpression }?.let(this::getOrCreateVariable) + ) + } + } + + /** + * [originalFir] used for extracting expression under and extracting receiver + */ + private fun createRealVariableInternal(identifier: Identifier, originalFir: FirElement): RealVariable { + val receiver: FirExpression? + val isSafeCall: Boolean + val isThisReference: Boolean + val expression = when (originalFir) { + is FirQualifiedAccessExpression -> originalFir + is FirWhenSubjectExpression -> originalFir.whenSubject.whenExpression.subject as? FirQualifiedAccessExpression + else -> null + } + + if (expression != null) { + receiver = expression.explicitReceiver + isSafeCall = expression.safe + isThisReference = expression.calleeReference is FirThisReference + } else { + receiver = null + isSafeCall = false + isThisReference = false + } + + val receiverVariable = receiver?.let { getOrCreateVariable(it) } + return RealVariable(identifier, isThisReference, receiverVariable, isSafeCall, counter++) + } + + @JvmName("getOrCreateRealVariableOrNull") + fun getOrCreateRealVariable(symbol: AbstractFirBasedSymbol<*>?, fir: FirElement): RealVariable? = + symbol?.let { getOrCreateRealVariable(it, fir) } + + fun createSyntheticVariable(fir: FirElement): SyntheticVariable = + SyntheticVariable(fir, counter++).also { syntheticVariables[fir] = it } + + fun getOrCreateVariable(fir: FirElement): DataFlowVariable { + val realFir = fir.unwrapElement() + return when (val symbol = realFir.symbol) { + null -> syntheticVariables[realFir] ?: createSyntheticVariable(realFir) + else -> getOrCreateRealVariable(symbol, realFir) + } + } + + /** + * Also removes existing real variable for [varSymbol] if it exists + */ + fun attachSymbolToVariable(varSymbol: AbstractFirBasedSymbol<*>, targetVariable: RealVariable) { + localVariableAliases[varSymbol] = targetVariable.identifier + realVariables.remove(Identifier(varSymbol, null, null)) + } + + operator fun get(symbol: AbstractFirBasedSymbol<*>?, fir: FirElement): RealVariable? { + return symbol?.let { realVariables[getIdentifierBySymbol(it, fir.unwrapElement())] } + } + + operator fun get(fir: FirElement): DataFlowVariable? { + val realFir = fir.unwrapElement() + val symbol = realFir.symbol + return if (symbol != null) { + get(symbol, realFir) + } else { + syntheticVariables[realFir] + } + } + + fun removeRealVariable(symbol: AbstractFirBasedSymbol<*>) { + // TODO: this shit fails +// assert(!localVariableAliases.containsValue(symbol)) + realVariables.remove(Identifier(symbol, null, null)) + } + + fun unboundPossiblyAliasedVariable(symbol: AbstractFirBasedSymbol<*>) { + localVariableAliases.remove(symbol) + } + + fun removeSyntheticVariable(variable: DataFlowVariable) { + if (variable !is SyntheticVariable) return + syntheticVariables.remove(variable.fir) + } + + fun reset() { + counter = 0 + realVariables.clear() + syntheticVariables.clear() + localVariableAliases.clear() + } +} + +internal val FirElement.symbol: AbstractFirBasedSymbol<*>? + get() = when (this) { + is FirResolvable -> symbol + is FirSymbolOwner<*> -> symbol + is FirWhenSubjectExpression -> whenSubject.whenExpression.subject?.symbol + else -> null + }?.takeIf { this is FirThisReceiverExpression || it !is FirFunctionSymbol<*> } + +internal val FirResolvable.symbol: AbstractFirBasedSymbol<*>? + get() = when (val reference = calleeReference) { + is FirExplicitThisReference -> reference.boundSymbol + is FirResolvedNamedReference -> reference.resolvedSymbol + is FirNamedReferenceWithCandidate -> reference.candidateSymbol + else -> null + } \ No newline at end of file diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/model.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/model.kt new file mode 100644 index 00000000000..e2fa77b5653 --- /dev/null +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/model.kt @@ -0,0 +1,228 @@ +/* + * 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.new + +import com.google.common.collect.Multimap +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.fir.FirElement +import org.jetbrains.kotlin.fir.declarations.modality +import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier +import org.jetbrains.kotlin.fir.resolve.dfa.Condition +import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol +import org.jetbrains.kotlin.fir.types.ConeKotlinType + +// --------------------------------------- Variables --------------------------------------- + +class Identifier( + val symbol: AbstractFirBasedSymbol<*>, + val dispatchReceiver: DataFlowVariable?, + val extensionReceiver: DataFlowVariable? +) { + override fun toString(): String { + val callableId = (symbol as? FirCallableSymbol<*>)?.callableId + return "[$callableId, dispatchReceiver = $dispatchReceiver, extensionReceiver = $extensionReceiver]" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Identifier + + if (symbol !== other.symbol) return false + if (dispatchReceiver != other.dispatchReceiver) return false + if (extensionReceiver != other.extensionReceiver) return false + + return true + } + + override fun hashCode(): Int { + var result = symbol.hashCode() + result = 31 * result + (dispatchReceiver?.hashCode() ?: 0) + result = 31 * result + (extensionReceiver?.hashCode() ?: 0) + return result + } +} + +sealed class DataFlowVariable(private val variableIndexForDebug: Int) { + abstract val isStable: Boolean + + final override fun toString(): String { + return "d$variableIndexForDebug" + } +} + +class RealVariable( + val identifier: Identifier, + val isThisReference: Boolean, + val explicitReceiverVariable: DataFlowVariable?, + val isSafeCall: Boolean, + variableIndexForDebug: Int +) : DataFlowVariable(variableIndexForDebug) { + override val isStable: Boolean by lazy { + when (val symbol = identifier.symbol) { + is FirPropertySymbol -> { + val property = symbol.fir + when { + property.isLocal -> true + property.isVar -> false + property.modality != Modality.FINAL -> false + else -> true + } + } + else -> true + } + } + + override fun equals(other: Any?): Boolean { + return this === other + } + + private val _hashCode by lazy { + 31 * identifier.hashCode() + (explicitReceiverVariable?.hashCode() ?: 0) + } + + override fun hashCode(): Int { + return _hashCode + } +} + +class SyntheticVariable(val fir: FirElement, variableIndexForDebug: Int) : DataFlowVariable(variableIndexForDebug) { + override val isStable: Boolean get() = true + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as SyntheticVariable + + return fir isEqualsTo other.fir + } + + override fun hashCode(): Int { + return if (fir is FirResolvedQualifier) { + 31 * fir.packageFqName.hashCode() + fir.classId.hashCode() + } else { + fir.hashCode() + } + } +} + +private infix fun FirElement.isEqualsTo(other: FirElement): Boolean { + if (this !is FirResolvedQualifier || other !is FirResolvedQualifier) return this == other + if (packageFqName != other.packageFqName) return false + if (classId != other.classId) return false + return true +} + +// --------------------------------------- Facts --------------------------------------- + +sealed class PredicateEffect> { + abstract fun invert(): T +} + +data class Predicate(val variable: DataFlowVariable, val condition: Condition) : PredicateEffect() { + override fun invert(): Predicate { + return Predicate(variable, condition.invert()) + } + + override fun toString(): String { + return "$variable $condition" + } +} + +abstract class DataFlowInfo : PredicateEffect() { + abstract val variable: RealVariable + abstract val exactType: Set + abstract val exactNotType: Set + + abstract operator fun plus(other: DataFlowInfo): DataFlowInfo + abstract val isEmpty: Boolean + val isNotEmpty: Boolean get() = !isEmpty + + override fun toString(): String { + return "$variable: $exactType, $exactNotType" + } +} + +class MutableDataFlowInfo( + override val variable: RealVariable, + override val exactType: MutableSet = HashSet(), + override val exactNotType: MutableSet = HashSet() +) : DataFlowInfo() { + override fun plus(other: DataFlowInfo): MutableDataFlowInfo = MutableDataFlowInfo( + variable, + HashSet(exactType).apply { addAll(other.exactType) }, + HashSet(exactNotType).apply { addAll(other.exactNotType) } + ) + + override val isEmpty: Boolean + get() = exactType.isEmpty() && exactType.isEmpty() + + override fun invert(): DataFlowInfo { + return MutableDataFlowInfo( + variable, + HashSet(exactNotType), + HashSet(exactType) + ) + } + + operator fun plusAssign(info: DataFlowInfo) { + exactType += info.exactType + exactNotType += info.exactNotType + } + + fun copy(): MutableDataFlowInfo = MutableDataFlowInfo(variable, HashSet(exactType), HashSet(exactNotType)) +} + +class LogicStatement( + val condition: Predicate, + val effect: PredicateEffect<*> +) { + override fun toString(): String { + return "$condition -> $effect" + } +} + +fun LogicStatement.invertCondition(): LogicStatement = LogicStatement(condition.invert(), effect) + +// --------------------------------------- Aliases --------------------------------------- + +typealias KnownInfos = Map +typealias MutableKnownFacts = MutableMap +typealias LogicStatements = Multimap + +// --------------------------------------- DSL --------------------------------------- + +infix fun DataFlowVariable.eq(constant: Boolean?): Predicate { + val condition = when (constant) { + true -> Condition.EqTrue + false -> Condition.EqFalse + null -> Condition.EqNull + } + return Predicate(this, condition) +} + +infix fun DataFlowVariable.notEq(constant: Boolean?): Predicate { + val condition = when (constant) { + true -> Condition.EqFalse + false -> Condition.EqTrue + null -> Condition.NotEqNull + } + return Predicate(this, condition) +} + +infix fun Predicate.implies(effect: PredicateEffect<*>): LogicStatement = LogicStatement(this, effect) + +infix fun RealVariable.has(types: MutableSet): DataFlowInfo = MutableDataFlowInfo(this, types, HashSet()) +infix fun RealVariable.has(type: ConeKotlinType): DataFlowInfo = + MutableDataFlowInfo(this, HashSet().apply { this += type }, HashSet()) + +infix fun RealVariable.hasNot(types: MutableSet): DataFlowInfo = MutableDataFlowInfo(this, HashSet(), types) +infix fun RealVariable.hasNot(type: ConeKotlinType): DataFlowInfo = + MutableDataFlowInfo(this, HashSet(), HashSet().apply { this += type }) \ No newline at end of file diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/util.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/util.kt new file mode 100644 index 00000000000..4b8d353fe84 --- /dev/null +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/new/util.kt @@ -0,0 +1,117 @@ +/* + * 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.new + +import org.jetbrains.kotlin.fir.contracts.description.ConeBooleanConstantReference +import org.jetbrains.kotlin.fir.contracts.description.ConeConstantReference +import org.jetbrains.kotlin.fir.expressions.FirExpression +import org.jetbrains.kotlin.fir.expressions.FirFunctionCall +import org.jetbrains.kotlin.fir.expressions.FirOperation +import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference +import org.jetbrains.kotlin.fir.resolve.calls.ConeInferenceContext +import org.jetbrains.kotlin.fir.resolve.dfa.Condition +import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol +import org.jetbrains.kotlin.fir.types.ConeKotlinType +import org.jetbrains.kotlin.fir.types.ConeTypeIntersector +import org.jetbrains.kotlin.fir.types.coneTypeSafe +import org.jetbrains.kotlin.fir.types.coneTypeUnsafe +import org.jetbrains.kotlin.resolve.calls.NewCommonSuperTypeCalculator +import org.jetbrains.kotlin.utils.addToStdlib.safeAs +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +fun ConeInferenceContext.commonSuperTypeOrNull(types: List): ConeKotlinType? { + return when (types.size) { + 0 -> null + 1 -> types.first() + else -> with(NewCommonSuperTypeCalculator) { + commonSuperType(types) as ConeKotlinType + } + } +} + +fun ConeInferenceContext.intersectTypesOrNull(types: List): ConeKotlinType? { + return when (types.size) { + 0 -> null + 1 -> types.first() + else -> ConeTypeIntersector.intersectTypes(this, types) + } +} + +@UseExperimental(ExperimentalContracts::class) +fun DataFlowVariable.isSynthetic(): Boolean { + contract { + returns(true) implies (this@isSynthetic is SyntheticVariable) + returns(false) implies (this@isSynthetic is RealVariable) + } + return this is SyntheticVariable +} + +@UseExperimental(ExperimentalContracts::class) +fun DataFlowVariable.isReal(): Boolean { + contract { + returns(true) implies (this@isReal is RealVariable) + returns(false) implies (this@isReal is SyntheticVariable) + } + return this is RealVariable +} + +operator fun DataFlowInfo.plus(other: DataFlowInfo?): DataFlowInfo = other?.let { this + other } ?: this + +fun MutableKnownFacts.addInfo(variable: RealVariable, info: DataFlowInfo) { + put(variable, info.asMutableInfo()) { it.apply { this += info } } +} + +fun MutableKnownFacts.mergeInfo(other: Map) { + other.forEach { (variable, info) -> + addInfo(variable, info) + } +} + +@UseExperimental(ExperimentalContracts::class) +internal inline fun MutableMap.put(key: K, value: V, remappingFunction: (existing: V) -> V) { + contract { + callsInPlace(remappingFunction, InvocationKind.AT_MOST_ONCE) + } + val existing = this[key] + if (existing == null) { + put(key, value) + } else { + put(key, remappingFunction(existing)) + } +} + +internal val FirExpression.coneType: ConeKotlinType? get() = typeRef.coneTypeSafe() + +internal fun FirOperation.invert(): FirOperation = when (this) { + FirOperation.EQ -> FirOperation.NOT_EQ + FirOperation.NOT_EQ -> FirOperation.EQ + FirOperation.IDENTITY -> FirOperation.NOT_IDENTITY + FirOperation.NOT_IDENTITY -> FirOperation.IDENTITY + else -> throw IllegalArgumentException("$this can not be inverted") +} + +internal fun FirOperation.isEq(): Boolean { + return when (this) { + FirOperation.EQ, FirOperation.IDENTITY -> true + FirOperation.NOT_EQ, FirOperation.NOT_IDENTITY -> false + else -> throw IllegalArgumentException("$this should not be there") + } +} + +internal fun FirFunctionCall.isBooleanNot(): Boolean { + val symbol = calleeReference.safeAs()?.resolvedSymbol as? FirNamedFunctionSymbol ?: return false + return symbol.callableId == FirDataFlowAnalyzer.KOTLIN_BOOLEAN_NOT +} + +internal fun ConeConstantReference.toCondition(): Condition = when (this) { + ConeConstantReference.NULL -> Condition.EqNull + ConeConstantReference.NOT_NULL -> Condition.NotEqNull + ConeBooleanConstantReference.TRUE -> Condition.EqTrue + ConeBooleanConstantReference.FALSE -> Condition.EqFalse + else -> throw IllegalArgumentException("$this can not be transformed to Condition") +} \ No newline at end of file diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirAbstractBodyResolveTransformer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirAbstractBodyResolveTransformer.kt index 056474f5046..705b75e17a7 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirAbstractBodyResolveTransformer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirAbstractBodyResolveTransformer.kt @@ -15,7 +15,7 @@ import org.jetbrains.kotlin.fir.declarations.FirResolvePhase import org.jetbrains.kotlin.fir.resolve.* import org.jetbrains.kotlin.fir.resolve.calls.InferenceComponents import org.jetbrains.kotlin.fir.resolve.calls.ResolutionStageRunner -import org.jetbrains.kotlin.fir.resolve.dfa.FirDataFlowAnalyzer +import org.jetbrains.kotlin.fir.resolve.dfa.new.FirDataFlowAnalyzer import org.jetbrains.kotlin.fir.resolve.inference.FirCallCompleter import org.jetbrains.kotlin.fir.resolve.transformers.* import org.jetbrains.kotlin.fir.scopes.FirScope @@ -76,7 +76,7 @@ abstract class FirAbstractBodyResolveTransformer(phase: FirResolvePhase) : FirAb protected inline val samResolver: FirSamResolver get() = components.samResolver protected inline val typeResolverTransformer: FirSpecificTypeResolverTransformer get() = components.typeResolverTransformer protected inline val callCompleter: FirCallCompleter get() = components.callCompleter - protected inline val dataFlowAnalyzer: FirDataFlowAnalyzer get() = components.dataFlowAnalyzer + protected inline val dataFlowAnalyzer: FirDataFlowAnalyzer<*> get() = components.dataFlowAnalyzer protected inline val scopeSession: ScopeSession get() = components.scopeSession protected inline val file: FirFile get() = components.file protected inline val integerLiteralTypeApproximator: IntegerLiteralTypeApproximationTransformer get() = components.integerLiteralTypeApproximator @@ -123,7 +123,7 @@ abstract class FirAbstractBodyResolveTransformer(phase: FirResolvePhase) : FirAb FirTypeResolveScopeForBodyResolve(topLevelScopes, implicitReceiverStack, localScopes), session ) val callCompleter: FirCallCompleter = FirCallCompleter(transformer, this) - override val dataFlowAnalyzer: FirDataFlowAnalyzer = FirDataFlowAnalyzer(this) + override val dataFlowAnalyzer: FirDataFlowAnalyzer<*> = FirDataFlowAnalyzer.createFirDataFlowAnalyzer(this) override val syntheticCallGenerator: FirSyntheticCallGenerator = FirSyntheticCallGenerator(this, callCompleter) override val integerLiteralTypeApproximator: IntegerLiteralTypeApproximationTransformer = IntegerLiteralTypeApproximationTransformer(symbolProvider, inferenceComponents.ctx) diff --git a/compiler/fir/resolve/testData/resolve/cfg/jumps.dot b/compiler/fir/resolve/testData/resolve/cfg/jumps.dot index d4baacf4616..1607c2c3b4e 100644 --- a/compiler/fir/resolve/testData/resolve/cfg/jumps.dot +++ b/compiler/fir/resolve/testData/resolve/cfg/jumps.dot @@ -45,7 +45,9 @@ digraph jumps_kt { 22 [label="Variable declaration: lval y: R|kotlin/Int|"]; 23 [label="Access variable R|/y|"]; 24 [label="Function call: R|/y|.R|kotlin/Int.inc|()"]; - 25 [label="Exit function test_1" style="filled" fillcolor=red]; + 25 [label="Access variable R|/x|"]; + 26 [label="Function call: R|/x|.R|kotlin/Int.inc|()"]; + 27 [label="Exit function test_1" style="filled" fillcolor=red]; } 0 -> {1}; @@ -65,7 +67,7 @@ digraph jumps_kt { 14 -> {15}; 15 -> {16}; 16 -> {17}; - 17 -> {25}; + 17 -> {27}; 17 -> {18} [style=dotted]; 18 -> {19} [style=dotted]; 19 -> {20} [style=dotted]; @@ -74,66 +76,66 @@ digraph jumps_kt { 22 -> {23}; 23 -> {24}; 24 -> {25}; + 25 -> {26}; + 26 -> {27}; subgraph cluster_6 { color=red - 26 [label="Enter function test_2" style="filled" fillcolor=red]; + 28 [label="Enter function test_2" style="filled" fillcolor=red]; subgraph cluster_7 { color=blue - 27 [label="Enter when"]; + 29 [label="Enter when"]; subgraph cluster_8 { color=blue - 28 [label="Enter when branch condition "]; - 29 [label="Access variable R|/x|"]; - 30 [label="Const: Null(null)"]; - 31 [label="Operator =="]; - 32 [label="Exit when branch condition"]; + 30 [label="Enter when branch condition "]; + 31 [label="Access variable R|/x|"]; + 32 [label="Const: Null(null)"]; + 33 [label="Operator =="]; + 34 [label="Exit when branch condition"]; } subgraph cluster_9 { color=blue - 33 [label="Enter when branch condition else"]; - 34 [label="Exit when branch condition"]; + 35 [label="Enter when branch condition else"]; + 36 [label="Exit when branch condition"]; } - 35 [label="Enter when branch result"]; + 37 [label="Enter when branch result"]; subgraph cluster_10 { color=blue - 36 [label="Enter block"]; - 37 [label="Access variable R|/x|"]; - 38 [label="Exit block"]; + 38 [label="Enter block"]; + 39 [label="Access variable R|/x|"]; + 40 [label="Exit block"]; } - 39 [label="Exit when branch result"]; - 40 [label="Enter when branch result"]; + 41 [label="Exit when branch result"]; + 42 [label="Enter when branch result"]; subgraph cluster_11 { color=blue - 41 [label="Enter block"]; - 42 [label="Access variable R|/x|"]; - 43 [label="Exit block"]; + 43 [label="Enter block"]; + 44 [label="Access variable R|/x|"]; + 45 [label="Exit block"]; } - 44 [label="Exit when branch result"]; - 45 [label="Exit when"]; + 46 [label="Exit when branch result"]; + 47 [label="Exit when"]; } - 46 [label="Variable declaration: lval y: R|kotlin/Int?|"]; - 47 [label="Access variable R|/y|"]; - 48 [label="Function call: R|/y|.#()"]; - 49 [label="Exit function test_2" style="filled" fillcolor=red]; + 48 [label="Variable declaration: lval y: R|kotlin/Int?|"]; + 49 [label="Access variable R|/y|"]; + 50 [label="Function call: R|/y|.#()"]; + 51 [label="Exit function test_2" style="filled" fillcolor=red]; } - 26 -> {27}; - 27 -> {28}; 28 -> {29}; 29 -> {30}; 30 -> {31}; 31 -> {32}; - 32 -> {40 33}; + 32 -> {33}; 33 -> {34}; - 34 -> {35}; + 34 -> {42 35}; 35 -> {36}; 36 -> {37}; 37 -> {38}; 38 -> {39}; - 39 -> {45}; + 39 -> {40}; 40 -> {41}; - 41 -> {42}; + 41 -> {47}; 42 -> {43}; 43 -> {44}; 44 -> {45}; @@ -141,219 +143,221 @@ digraph jumps_kt { 46 -> {47}; 47 -> {48}; 48 -> {49}; + 49 -> {50}; + 50 -> {51}; subgraph cluster_12 { color=red - 50 [label="Enter function test_3" style="filled" fillcolor=red]; + 52 [label="Enter function test_3" style="filled" fillcolor=red]; subgraph cluster_13 { color=blue - 51 [label="Enter while loop"]; + 53 [label="Enter while loop"]; subgraph cluster_14 { color=blue - 52 [label="Enter loop condition"]; - 53 [label="Const: Boolean(true)"]; - 54 [label="Exit loop condition"]; + 54 [label="Enter loop condition"]; + 55 [label="Const: Boolean(true)"]; + 56 [label="Exit loop condition"]; } subgraph cluster_15 { color=blue - 55 [label="Enter loop block"]; + 57 [label="Enter loop block"]; subgraph cluster_16 { color=blue - 56 [label="Enter block"]; - 57 [label="Access variable R|/x|"]; - 58 [label="Type operator: x as Int"]; - 59 [label="Jump: break@@@[Boolean(true)] "]; - 60 [label="Stub" style="filled" fillcolor=gray]; - 61 [label="Exit block" style="filled" fillcolor=gray]; + 58 [label="Enter block"]; + 59 [label="Access variable R|/x|"]; + 60 [label="Type operator: x as Int"]; + 61 [label="Jump: break@@@[Boolean(true)] "]; + 62 [label="Stub" style="filled" fillcolor=gray]; + 63 [label="Exit block" style="filled" fillcolor=gray]; } - 62 [label="Exit loop block" style="filled" fillcolor=gray]; + 64 [label="Exit loop block" style="filled" fillcolor=gray]; } - 63 [label="Stub" style="filled" fillcolor=gray]; - 64 [label="Exit whileloop"]; + 65 [label="Stub" style="filled" fillcolor=gray]; + 66 [label="Exit whileloop"]; } - 65 [label="Access variable R|/x|"]; - 66 [label="Function call: R|/x|.R|kotlin/Int.inc|()"]; - 67 [label="Exit function test_3" style="filled" fillcolor=red]; + 67 [label="Access variable R|/x|"]; + 68 [label="Function call: R|/x|.R|kotlin/Int.inc|()"]; + 69 [label="Exit function test_3" style="filled" fillcolor=red]; } - 50 -> {51}; - 51 -> {52}; 52 -> {53}; 53 -> {54}; 54 -> {55}; - 54 -> {63} [style=dotted]; 55 -> {56}; 56 -> {57}; + 56 -> {65} [style=dotted]; 57 -> {58}; 58 -> {59}; - 59 -> {64}; - 59 -> {60} [style=dotted]; - 60 -> {61} [style=dotted]; + 59 -> {60}; + 60 -> {61}; + 61 -> {66}; 61 -> {62} [style=dotted]; - 62 -> {52} [style=dotted]; + 62 -> {63} [style=dotted]; 63 -> {64} [style=dotted]; - 64 -> {65}; - 65 -> {66}; + 64 -> {54} [style=dotted]; + 65 -> {66} [style=dotted]; 66 -> {67}; + 67 -> {68}; + 68 -> {69}; subgraph cluster_17 { color=red - 68 [label="Enter function test_4" style="filled" fillcolor=red]; + 70 [label="Enter function test_4" style="filled" fillcolor=red]; subgraph cluster_18 { color=blue - 69 [label="Enter do-while loop"]; + 71 [label="Enter do-while loop"]; subgraph cluster_19 { color=blue - 70 [label="Enter loop block"]; + 72 [label="Enter loop block"]; subgraph cluster_20 { color=blue - 71 [label="Enter block"]; - 72 [label="Access variable R|/x|"]; - 73 [label="Type operator: x as Int"]; - 74 [label="Jump: break@@@[Boolean(true)] "]; - 75 [label="Stub" style="filled" fillcolor=gray]; - 76 [label="Exit block" style="filled" fillcolor=gray]; + 73 [label="Enter block"]; + 74 [label="Access variable R|/x|"]; + 75 [label="Type operator: x as Int"]; + 76 [label="Jump: break@@@[Boolean(true)] "]; + 77 [label="Stub" style="filled" fillcolor=gray]; + 78 [label="Exit block" style="filled" fillcolor=gray]; } - 77 [label="Exit loop block" style="filled" fillcolor=gray]; + 79 [label="Exit loop block" style="filled" fillcolor=gray]; } subgraph cluster_21 { color=blue - 78 [label="Enter loop condition" style="filled" fillcolor=gray]; - 79 [label="Const: Boolean(true)" style="filled" fillcolor=gray]; - 80 [label="Exit loop condition" style="filled" fillcolor=gray]; + 80 [label="Enter loop condition" style="filled" fillcolor=gray]; + 81 [label="Const: Boolean(true)" style="filled" fillcolor=gray]; + 82 [label="Exit loop condition" style="filled" fillcolor=gray]; } - 81 [label="Stub" style="filled" fillcolor=gray]; - 82 [label="Exit do-whileloop"]; + 83 [label="Stub" style="filled" fillcolor=gray]; + 84 [label="Exit do-whileloop"]; } - 83 [label="Access variable R|/x|"]; - 84 [label="Function call: R|/x|.R|kotlin/Int.inc|()"]; - 85 [label="Exit function test_4" style="filled" fillcolor=red]; + 85 [label="Access variable R|/x|"]; + 86 [label="Function call: R|/x|.R|kotlin/Int.inc|()"]; + 87 [label="Exit function test_4" style="filled" fillcolor=red]; } - 68 -> {69}; - 69 -> {70}; 70 -> {71}; 71 -> {72}; 72 -> {73}; 73 -> {74}; - 74 -> {82}; - 74 -> {75} [style=dotted]; - 75 -> {76} [style=dotted]; + 74 -> {75}; + 75 -> {76}; + 76 -> {84}; 76 -> {77} [style=dotted]; 77 -> {78} [style=dotted]; 78 -> {79} [style=dotted]; 79 -> {80} [style=dotted]; - 80 -> {70 81} [style=dotted]; + 80 -> {81} [style=dotted]; 81 -> {82} [style=dotted]; - 82 -> {83}; - 83 -> {84}; + 82 -> {72 83} [style=dotted]; + 83 -> {84} [style=dotted]; 84 -> {85}; + 85 -> {86}; + 86 -> {87}; subgraph cluster_22 { color=red - 86 [label="Enter function test_5" style="filled" fillcolor=red]; + 88 [label="Enter function test_5" style="filled" fillcolor=red]; subgraph cluster_23 { color=blue - 87 [label="Enter while loop"]; + 89 [label="Enter while loop"]; subgraph cluster_24 { color=blue - 88 [label="Enter loop condition"]; - 89 [label="Access variable R|/b|"]; - 90 [label="Exit loop condition"]; + 90 [label="Enter loop condition"]; + 91 [label="Access variable R|/b|"]; + 92 [label="Exit loop condition"]; } subgraph cluster_25 { color=blue - 91 [label="Enter loop block"]; + 93 [label="Enter loop block"]; subgraph cluster_26 { color=blue - 92 [label="Enter block"]; + 94 [label="Enter block"]; subgraph cluster_27 { color=blue - 93 [label="Enter when"]; + 95 [label="Enter when"]; subgraph cluster_28 { color=blue - 94 [label="Enter when branch condition "]; - 95 [label="Access variable R|/b|"]; - 96 [label="Exit when branch condition"]; + 96 [label="Enter when branch condition "]; + 97 [label="Access variable R|/b|"]; + 98 [label="Exit when branch condition"]; } - 97 [label="Synthetic else branch"]; - 98 [label="Enter when branch result"]; + 99 [label="Synthetic else branch"]; + 100 [label="Enter when branch result"]; subgraph cluster_29 { color=blue - 99 [label="Enter block"]; - 100 [label="Jump: continue@@@[R|/b|] "]; - 101 [label="Stub" style="filled" fillcolor=gray]; - 102 [label="Exit block" style="filled" fillcolor=gray]; + 101 [label="Enter block"]; + 102 [label="Jump: continue@@@[R|/b|] "]; + 103 [label="Stub" style="filled" fillcolor=gray]; + 104 [label="Exit block" style="filled" fillcolor=gray]; } - 103 [label="Exit when branch result" style="filled" fillcolor=gray]; - 104 [label="Exit when"]; + 105 [label="Exit when branch result" style="filled" fillcolor=gray]; + 106 [label="Exit when"]; } - 105 [label="Exit block"]; + 107 [label="Exit block"]; } - 106 [label="Exit loop block"]; + 108 [label="Exit loop block"]; } - 107 [label="Exit whileloop"]; + 109 [label="Exit whileloop"]; } - 108 [label="Exit function test_5" style="filled" fillcolor=red]; + 110 [label="Exit function test_5" style="filled" fillcolor=red]; } - 86 -> {87}; - 87 -> {88}; 88 -> {89}; 89 -> {90}; - 90 -> {107 91}; + 90 -> {91}; 91 -> {92}; - 92 -> {93}; + 92 -> {109 93}; 93 -> {94}; 94 -> {95}; 95 -> {96}; - 96 -> {98 97}; - 97 -> {104}; - 98 -> {99}; - 99 -> {100}; - 100 -> {87}; - 100 -> {101} [style=dotted]; - 101 -> {102} [style=dotted]; + 96 -> {97}; + 97 -> {98}; + 98 -> {100 99}; + 99 -> {106}; + 100 -> {101}; + 101 -> {102}; + 102 -> {89}; 102 -> {103} [style=dotted]; 103 -> {104} [style=dotted]; - 104 -> {105}; - 105 -> {106}; - 106 -> {88}; + 104 -> {105} [style=dotted]; + 105 -> {106} [style=dotted]; + 106 -> {107}; 107 -> {108}; + 108 -> {90}; + 109 -> {110}; subgraph cluster_30 { color=red - 109 [label="Enter function run" style="filled" fillcolor=red]; - 110 [label="Function call: R|/block|.R|FakeOverride|()"]; - 111 [label="Exit function run" style="filled" fillcolor=red]; + 111 [label="Enter function run" style="filled" fillcolor=red]; + 112 [label="Function call: R|/block|.R|FakeOverride|()"]; + 113 [label="Exit function run" style="filled" fillcolor=red]; } - 109 -> {110}; - 110 -> {111}; + 111 -> {112}; + 112 -> {113}; subgraph cluster_31 { color=red - 112 [label="Enter function test_6" style="filled" fillcolor=red]; + 114 [label="Enter function test_6" style="filled" fillcolor=red]; subgraph cluster_32 { color=blue - 113 [label="Enter function anonymousFunction"]; - 114 [label="Jump: ^@run Unit"]; - 115 [label="Stub" style="filled" fillcolor=gray]; - 116 [label="Exit function anonymousFunction"]; + 115 [label="Enter function anonymousFunction"]; + 116 [label="Jump: ^@run Unit"]; + 117 [label="Stub" style="filled" fillcolor=gray]; + 118 [label="Exit function anonymousFunction"]; } - 117 [label="Function call: R|/run|( = run@fun (): R|kotlin/Unit| { + 119 [label="Function call: R|/run|( = run@fun (): R|kotlin/Unit| { ^@run Unit } )"]; - 118 [label="Exit function test_6" style="filled" fillcolor=red]; + 120 [label="Exit function test_6" style="filled" fillcolor=red]; } - 112 -> {113}; - 113 -> {116 114}; - 114 -> {116}; - 114 -> {115} [style=dotted]; - 115 -> {116} [style=dotted]; - 116 -> {113 117}; - 117 -> {118}; + 114 -> {115}; + 115 -> {118 116}; + 116 -> {118}; + 116 -> {117} [style=dotted]; + 117 -> {118} [style=dotted]; + 118 -> {115 119}; + 119 -> {120}; } diff --git a/compiler/fir/resolve/testData/resolve/cfg/jumps.kt b/compiler/fir/resolve/testData/resolve/cfg/jumps.kt index eb6f69999cd..a950e9593b3 100644 --- a/compiler/fir/resolve/testData/resolve/cfg/jumps.kt +++ b/compiler/fir/resolve/testData/resolve/cfg/jumps.kt @@ -5,6 +5,7 @@ fun test_1(x: Int?) { x } y.inc() + x.inc() } fun test_2(x: Int?) { diff --git a/compiler/fir/resolve/testData/resolve/cfg/jumps.txt b/compiler/fir/resolve/testData/resolve/cfg/jumps.txt index 1a5901309e6..d6289a9fbab 100644 --- a/compiler/fir/resolve/testData/resolve/cfg/jumps.txt +++ b/compiler/fir/resolve/testData/resolve/cfg/jumps.txt @@ -10,6 +10,7 @@ FILE: jumps.kt } R|/y|.R|kotlin/Int.inc|() + R|/x|.R|kotlin/Int.inc|() } public final fun test_2(x: R|kotlin/Int?|): R|kotlin/Unit| { lval y: R|kotlin/Int?| = when () { diff --git a/compiler/fir/resolve/testData/resolve/smartcasts/anotherBoundSmartcasts.dot b/compiler/fir/resolve/testData/resolve/smartcasts/anotherBoundSmartcasts.dot index 0c5fd2130f7..d99d613c9b2 100644 --- a/compiler/fir/resolve/testData/resolve/smartcasts/anotherBoundSmartcasts.dot +++ b/compiler/fir/resolve/testData/resolve/smartcasts/anotherBoundSmartcasts.dot @@ -5,222 +5,459 @@ digraph anotherBoundSmartcasts_kt { subgraph cluster_0 { color=red - 0 [label="Enter function foo" style="filled" fillcolor=red]; - 1 [label="Exit function foo" style="filled" fillcolor=red]; + 0 [label="Enter function " style="filled" fillcolor=red]; + 1 [label="Exit function " style="filled" fillcolor=red]; } 0 -> {1}; subgraph cluster_1 { color=red - 2 [label="Enter function getter" style="filled" fillcolor=red]; - 3 [label="Exit function getter" style="filled" fillcolor=red]; + 2 [label="Enter function foo" style="filled" fillcolor=red]; + 3 [label="Const: Int(1)"]; + 4 [label="Jump: ^foo Int(1)"]; + 5 [label="Stub" style="filled" fillcolor=gray]; + 6 [label="Exit function foo" style="filled" fillcolor=red]; } 2 -> {3}; + 3 -> {4}; + 4 -> {6}; + 4 -> {5} [style=dotted]; + 5 -> {6} [style=dotted]; subgraph cluster_2 { color=red - 4 [label="Enter property" style="filled" fillcolor=red]; - 5 [label="Exit property" style="filled" fillcolor=red]; + 7 [label="Enter function getter" style="filled" fillcolor=red]; + 8 [label="Exit function getter" style="filled" fillcolor=red]; } - 4 -> {5}; + 7 -> {8}; subgraph cluster_3 { color=red - 6 [label="Enter function bar" style="filled" fillcolor=red]; - 7 [label="Exit function bar" style="filled" fillcolor=red]; + 9 [label="Enter property" style="filled" fillcolor=red]; + 10 [label="Const: Int(1)"]; + 11 [label="Exit property" style="filled" fillcolor=red]; } - 6 -> {7}; + 9 -> {10}; + 10 -> {11}; subgraph cluster_4 { color=red - 8 [label="Enter function test_1" style="filled" fillcolor=red]; - 9 [label="Access variable R|/a|"]; - 10 [label="Enter safe call"]; - 11 [label="Access variable R|/A.x|"]; - 12 [label="Exit safe call"]; - 13 [label="Variable declaration: lval x: R|kotlin/Int?|"]; - subgraph cluster_5 { - color=blue - 14 [label="Enter when"]; - subgraph cluster_6 { - color=blue - 15 [label="Enter when branch condition "]; - 16 [label="Access variable R|/x|"]; - 17 [label="Const: Null(null)"]; - 18 [label="Operator !="]; - 19 [label="Exit when branch condition"]; - } - 20 [label="Synthetic else branch"]; - 21 [label="Enter when branch result"]; - subgraph cluster_7 { - color=blue - 22 [label="Enter block"]; - 23 [label="Access variable R|/a|"]; - 24 [label="Function call: R|/a|.#()"]; - 25 [label="Exit block"]; - } - 26 [label="Exit when branch result"]; - 27 [label="Exit when"]; - } - 28 [label="Exit function test_1" style="filled" fillcolor=red]; + 12 [label="Enter function bar" style="filled" fillcolor=red]; + 13 [label="Exit function bar" style="filled" fillcolor=red]; } - 8 -> {9}; - 9 -> {10 12}; - 10 -> {11}; - 11 -> {12}; 12 -> {13}; - 13 -> {14}; + + subgraph cluster_5 { + color=red + 14 [label="Enter function test_1" style="filled" fillcolor=red]; + 15 [label="Access variable R|/a|"]; + 16 [label="Enter safe call"]; + 17 [label="Access variable R|/A.x|"]; + 18 [label="Exit safe call"]; + 19 [label="Variable declaration: lval x: R|kotlin/Int?|"]; + subgraph cluster_6 { + color=blue + 20 [label="Enter when"]; + subgraph cluster_7 { + color=blue + 21 [label="Enter when branch condition "]; + 22 [label="Access variable R|/x|"]; + 23 [label="Const: Null(null)"]; + 24 [label="Operator !="]; + 25 [label="Exit when branch condition"]; + } + 26 [label="Synthetic else branch"]; + 27 [label="Enter when branch result"]; + subgraph cluster_8 { + color=blue + 28 [label="Enter block"]; + 29 [label="Access variable R|/a|"]; + 30 [label="Function call: R|/a|.R|/A.bar|()"]; + 31 [label="Exit block"]; + } + 32 [label="Exit when branch result"]; + 33 [label="Exit when"]; + } + 34 [label="Exit function test_1" style="filled" fillcolor=red]; + } + 14 -> {15}; - 15 -> {16}; + 15 -> {16 18}; 16 -> {17}; 17 -> {18}; 18 -> {19}; - 19 -> {21 20}; - 20 -> {27}; + 19 -> {20}; + 20 -> {21}; 21 -> {22}; 22 -> {23}; 23 -> {24}; 24 -> {25}; - 25 -> {26}; - 26 -> {27}; + 25 -> {27 26}; + 26 -> {33}; 27 -> {28}; - - subgraph cluster_8 { - color=red - 29 [label="Enter function test_2" style="filled" fillcolor=red]; - 30 [label="Access variable R|/a|"]; - 31 [label="Enter safe call"]; - 32 [label="Function call: R|/a|?.R|/A.foo|()"]; - 33 [label="Exit safe call"]; - 34 [label="Variable declaration: lval x: R|kotlin/Int?|"]; - subgraph cluster_9 { - color=blue - 35 [label="Enter when"]; - subgraph cluster_10 { - color=blue - 36 [label="Enter when branch condition "]; - 37 [label="Access variable R|/x|"]; - 38 [label="Const: Null(null)"]; - 39 [label="Operator !="]; - 40 [label="Exit when branch condition"]; - } - 41 [label="Synthetic else branch"]; - 42 [label="Enter when branch result"]; - subgraph cluster_11 { - color=blue - 43 [label="Enter block"]; - 44 [label="Access variable R|/a|"]; - 45 [label="Function call: R|/a|.#()"]; - 46 [label="Exit block"]; - } - 47 [label="Exit when branch result"]; - 48 [label="Exit when"]; - } - 49 [label="Exit function test_2" style="filled" fillcolor=red]; - } - + 28 -> {29}; 29 -> {30}; - 30 -> {31 33}; + 30 -> {31}; 31 -> {32}; 32 -> {33}; 33 -> {34}; - 34 -> {35}; + + subgraph cluster_9 { + color=red + 35 [label="Enter function test_2" style="filled" fillcolor=red]; + 36 [label="Access variable R|/a|"]; + 37 [label="Enter safe call"]; + 38 [label="Function call: R|/a|?.R|/A.foo|()"]; + 39 [label="Exit safe call"]; + 40 [label="Variable declaration: lval x: R|kotlin/Int?|"]; + subgraph cluster_10 { + color=blue + 41 [label="Enter when"]; + subgraph cluster_11 { + color=blue + 42 [label="Enter when branch condition "]; + 43 [label="Access variable R|/x|"]; + 44 [label="Const: Null(null)"]; + 45 [label="Operator !="]; + 46 [label="Exit when branch condition"]; + } + 47 [label="Synthetic else branch"]; + 48 [label="Enter when branch result"]; + subgraph cluster_12 { + color=blue + 49 [label="Enter block"]; + 50 [label="Access variable R|/a|"]; + 51 [label="Function call: R|/a|.R|/A.bar|()"]; + 52 [label="Exit block"]; + } + 53 [label="Exit when branch result"]; + 54 [label="Exit when"]; + } + 55 [label="Exit function test_2" style="filled" fillcolor=red]; + } + 35 -> {36}; - 36 -> {37}; + 36 -> {37 39}; 37 -> {38}; 38 -> {39}; 39 -> {40}; - 40 -> {42 41}; - 41 -> {48}; + 40 -> {41}; + 41 -> {42}; 42 -> {43}; 43 -> {44}; 44 -> {45}; 45 -> {46}; - 46 -> {47}; - 47 -> {48}; + 46 -> {48 47}; + 47 -> {54}; 48 -> {49}; - - subgraph cluster_12 { - color=red - 50 [label="Enter function test_3" style="filled" fillcolor=red]; - subgraph cluster_13 { - color=blue - 51 [label="Enter when"]; - 52 [label="Access variable R|/x|"]; - 53 [label="Type operator: x as? A"]; - 54 [label="Variable declaration: lval : R|A?|"]; - subgraph cluster_14 { - color=blue - 55 [label="Enter when branch condition "]; - 56 [label="Const: Null(null)"]; - 57 [label="Operator =="]; - 58 [label="Exit when branch condition"]; - } - subgraph cluster_15 { - color=blue - 59 [label="Enter when branch condition else"]; - 60 [label="Exit when branch condition"]; - } - 61 [label="Enter when branch result"]; - subgraph cluster_16 { - color=blue - 62 [label="Enter block"]; - 63 [label="Access variable R|/|"]; - 64 [label="Exit block"]; - } - 65 [label="Exit when branch result"]; - 66 [label="Enter when branch result"]; - subgraph cluster_17 { - color=blue - 67 [label="Enter block"]; - 68 [label="Jump: ^test_3 Unit"]; - 69 [label="Stub" style="filled" fillcolor=gray]; - 70 [label="Exit block" style="filled" fillcolor=gray]; - } - 71 [label="Exit when branch result" style="filled" fillcolor=gray]; - 72 [label="Exit when"]; - } - 73 [label="Variable declaration: lval a: R|A|"]; - 74 [label="Access variable R|/a|"]; - 75 [label="Function call: R|/a|.R|/A.foo|()"]; - 76 [label="Access variable R|/x|"]; - 77 [label="Function call: R|/x|.#()"]; - 78 [label="Exit function test_3" style="filled" fillcolor=red]; - } - + 49 -> {50}; 50 -> {51}; 51 -> {52}; 52 -> {53}; 53 -> {54}; 54 -> {55}; - 55 -> {56}; + + subgraph cluster_13 { + color=red + 56 [label="Enter function test_3" style="filled" fillcolor=red]; + subgraph cluster_14 { + color=blue + 57 [label="Enter when"]; + 58 [label="Access variable R|/x|"]; + 59 [label="Type operator: x as? A"]; + 60 [label="Variable declaration: lval : R|A?|"]; + subgraph cluster_15 { + color=blue + 61 [label="Enter when branch condition "]; + 62 [label="Const: Null(null)"]; + 63 [label="Operator =="]; + 64 [label="Exit when branch condition"]; + } + subgraph cluster_16 { + color=blue + 65 [label="Enter when branch condition else"]; + 66 [label="Exit when branch condition"]; + } + 67 [label="Enter when branch result"]; + subgraph cluster_17 { + color=blue + 68 [label="Enter block"]; + 69 [label="Access variable R|/|"]; + 70 [label="Exit block"]; + } + 71 [label="Exit when branch result"]; + 72 [label="Enter when branch result"]; + subgraph cluster_18 { + color=blue + 73 [label="Enter block"]; + 74 [label="Jump: ^test_3 Unit"]; + 75 [label="Stub" style="filled" fillcolor=gray]; + 76 [label="Exit block" style="filled" fillcolor=gray]; + } + 77 [label="Exit when branch result" style="filled" fillcolor=gray]; + 78 [label="Exit when"]; + } + 79 [label="Variable declaration: lval a: R|A|"]; + 80 [label="Access variable R|/a|"]; + 81 [label="Function call: R|/a|.R|/A.foo|()"]; + 82 [label="Access variable R|/x|"]; + 83 [label="Function call: R|/x|.R|/A.foo|()"]; + 84 [label="Exit function test_3" style="filled" fillcolor=red]; + } + 56 -> {57}; 57 -> {58}; - 58 -> {66 59}; + 58 -> {59}; 59 -> {60}; 60 -> {61}; 61 -> {62}; 62 -> {63}; 63 -> {64}; - 64 -> {65}; - 65 -> {72}; + 64 -> {72 65}; + 65 -> {66}; 66 -> {67}; 67 -> {68}; - 68 -> {78}; - 68 -> {69} [style=dotted]; - 69 -> {70} [style=dotted]; - 70 -> {71} [style=dotted]; - 71 -> {72} [style=dotted]; + 68 -> {69}; + 69 -> {70}; + 70 -> {71}; + 71 -> {78}; 72 -> {73}; 73 -> {74}; - 74 -> {75}; - 75 -> {76}; - 76 -> {77}; - 77 -> {78}; + 74 -> {84}; + 74 -> {75} [style=dotted]; + 75 -> {76} [style=dotted]; + 76 -> {77} [style=dotted]; + 77 -> {78} [style=dotted]; + 78 -> {79}; + 79 -> {80}; + 80 -> {81}; + 81 -> {82}; + 82 -> {83}; + 83 -> {84}; + + subgraph cluster_19 { + color=red + 85 [label="Enter function foo" style="filled" fillcolor=red]; + 86 [label="Exit function foo" style="filled" fillcolor=red]; + } + + 85 -> {86}; + + subgraph cluster_20 { + color=red + 87 [label="Enter function getter" style="filled" fillcolor=red]; + 88 [label="Exit function getter" style="filled" fillcolor=red]; + } + + 87 -> {88}; + + subgraph cluster_21 { + color=red + 89 [label="Enter property" style="filled" fillcolor=red]; + 90 [label="Exit property" style="filled" fillcolor=red]; + } + + 89 -> {90}; + + subgraph cluster_22 { + color=red + 91 [label="Enter function bar" style="filled" fillcolor=red]; + 92 [label="Exit function bar" style="filled" fillcolor=red]; + } + + 91 -> {92}; + + subgraph cluster_23 { + color=red + 93 [label="Enter function test_1" style="filled" fillcolor=red]; + 94 [label="Access variable R|/a|"]; + 95 [label="Enter safe call"]; + 96 [label="Access variable R|/B.x|"]; + 97 [label="Exit safe call"]; + 98 [label="Variable declaration: lval x: R|kotlin/Int?|"]; + subgraph cluster_24 { + color=blue + 99 [label="Enter when"]; + subgraph cluster_25 { + color=blue + 100 [label="Enter when branch condition "]; + 101 [label="Access variable R|/x|"]; + 102 [label="Const: Null(null)"]; + 103 [label="Operator !="]; + 104 [label="Exit when branch condition"]; + } + 105 [label="Synthetic else branch"]; + 106 [label="Enter when branch result"]; + subgraph cluster_26 { + color=blue + 107 [label="Enter block"]; + 108 [label="Access variable R|/a|"]; + 109 [label="Function call: R|/a|.R|/B.bar|()"]; + 110 [label="Exit block"]; + } + 111 [label="Exit when branch result"]; + 112 [label="Exit when"]; + } + 113 [label="Exit function test_1" style="filled" fillcolor=red]; + } + + 93 -> {94}; + 94 -> {95 97}; + 95 -> {96}; + 96 -> {97}; + 97 -> {98}; + 98 -> {99}; + 99 -> {100}; + 100 -> {101}; + 101 -> {102}; + 102 -> {103}; + 103 -> {104}; + 104 -> {106 105}; + 105 -> {112}; + 106 -> {107}; + 107 -> {108}; + 108 -> {109}; + 109 -> {110}; + 110 -> {111}; + 111 -> {112}; + 112 -> {113}; + + subgraph cluster_27 { + color=red + 114 [label="Enter function test_2" style="filled" fillcolor=red]; + 115 [label="Access variable R|/a|"]; + 116 [label="Enter safe call"]; + 117 [label="Function call: R|/a|?.R|/B.foo|()"]; + 118 [label="Exit safe call"]; + 119 [label="Variable declaration: lval x: R|kotlin/Int?|"]; + subgraph cluster_28 { + color=blue + 120 [label="Enter when"]; + subgraph cluster_29 { + color=blue + 121 [label="Enter when branch condition "]; + 122 [label="Access variable R|/x|"]; + 123 [label="Const: Null(null)"]; + 124 [label="Operator !="]; + 125 [label="Exit when branch condition"]; + } + 126 [label="Synthetic else branch"]; + 127 [label="Enter when branch result"]; + subgraph cluster_30 { + color=blue + 128 [label="Enter block"]; + 129 [label="Access variable R|/a|"]; + 130 [label="Function call: R|/a|.R|/B.bar|()"]; + 131 [label="Exit block"]; + } + 132 [label="Exit when branch result"]; + 133 [label="Exit when"]; + } + 134 [label="Exit function test_2" style="filled" fillcolor=red]; + } + + 114 -> {115}; + 115 -> {116 118}; + 116 -> {117}; + 117 -> {118}; + 118 -> {119}; + 119 -> {120}; + 120 -> {121}; + 121 -> {122}; + 122 -> {123}; + 123 -> {124}; + 124 -> {125}; + 125 -> {127 126}; + 126 -> {133}; + 127 -> {128}; + 128 -> {129}; + 129 -> {130}; + 130 -> {131}; + 131 -> {132}; + 132 -> {133}; + 133 -> {134}; + + subgraph cluster_31 { + color=red + 135 [label="Enter function test_3" style="filled" fillcolor=red]; + subgraph cluster_32 { + color=blue + 136 [label="Enter when"]; + 137 [label="Access variable R|/x|"]; + 138 [label="Type operator: x as? B"]; + 139 [label="Variable declaration: lval : R|B?|"]; + subgraph cluster_33 { + color=blue + 140 [label="Enter when branch condition "]; + 141 [label="Const: Null(null)"]; + 142 [label="Operator =="]; + 143 [label="Exit when branch condition"]; + } + subgraph cluster_34 { + color=blue + 144 [label="Enter when branch condition else"]; + 145 [label="Exit when branch condition"]; + } + 146 [label="Enter when branch result"]; + subgraph cluster_35 { + color=blue + 147 [label="Enter block"]; + 148 [label="Access variable R|/|"]; + 149 [label="Exit block"]; + } + 150 [label="Exit when branch result"]; + 151 [label="Enter when branch result"]; + subgraph cluster_36 { + color=blue + 152 [label="Enter block"]; + 153 [label="Jump: ^test_3 Unit"]; + 154 [label="Stub" style="filled" fillcolor=gray]; + 155 [label="Exit block" style="filled" fillcolor=gray]; + } + 156 [label="Exit when branch result" style="filled" fillcolor=gray]; + 157 [label="Exit when"]; + } + 158 [label="Variable declaration: lval a: R|B|"]; + 159 [label="Access variable R|/a|"]; + 160 [label="Function call: R|/a|.R|/B.foo|()"]; + 161 [label="Access variable R|/x|"]; + 162 [label="Function call: R|/x|.R|/B.foo|()"]; + 163 [label="Exit function test_3" style="filled" fillcolor=red]; + } + + 135 -> {136}; + 136 -> {137}; + 137 -> {138}; + 138 -> {139}; + 139 -> {140}; + 140 -> {141}; + 141 -> {142}; + 142 -> {143}; + 143 -> {151 144}; + 144 -> {145}; + 145 -> {146}; + 146 -> {147}; + 147 -> {148}; + 148 -> {149}; + 149 -> {150}; + 150 -> {157}; + 151 -> {152}; + 152 -> {153}; + 153 -> {163}; + 153 -> {154} [style=dotted]; + 154 -> {155} [style=dotted]; + 155 -> {156} [style=dotted]; + 156 -> {157} [style=dotted]; + 157 -> {158}; + 158 -> {159}; + 159 -> {160}; + 160 -> {161}; + 161 -> {162}; + 162 -> {163}; } diff --git a/compiler/fir/resolve/testData/resolve/smartcasts/anotherBoundSmartcasts.kt b/compiler/fir/resolve/testData/resolve/smartcasts/anotherBoundSmartcasts.kt index 39ce8b2c02c..84f83a3b5d8 100644 --- a/compiler/fir/resolve/testData/resolve/smartcasts/anotherBoundSmartcasts.kt +++ b/compiler/fir/resolve/testData/resolve/smartcasts/anotherBoundSmartcasts.kt @@ -1,4 +1,36 @@ -interface A { +// ----------------- Stable ----------------- + +class A { + fun foo(): Int = 1 + + val x: Int = 1 + + fun bar() {} +} + +fun test_1(a: A?) { + val x = a?.x + if (x != null) { + a.bar() // Should be OK + } +} + +fun test_2(a: A?) { + val x = a?.foo() + if (x != null) { + a.bar() // Should be OK + } +} + +fun test_3(x: Any?) { + val a = x as? A ?: return + a.foo() // Should be OK + x.foo() // Should be OK +} + +// ----------------- Unstable ----------------- + +interface B { fun foo(): Int val x: Int @@ -6,24 +38,22 @@ interface A { fun bar() } -fun test_1(a: A?) { +fun test_1(a: B?) { val x = a?.x if (x != null) { - a.bar() + a.bar() // Should be OK } } -fun test_2(a: A?) { +fun test_2(a: B?) { val x = a?.foo() if (x != null) { - a.bar() + a.bar() // Should be OK } } -// ---------------------------------------------------------------- - fun test_3(x: Any?) { - val a = x as? A ?: return - a.foo() - x.foo() + val a = x as? B ?: return + a.foo() // Should be OK + x.foo() // Should be OK } \ No newline at end of file diff --git a/compiler/fir/resolve/testData/resolve/smartcasts/anotherBoundSmartcasts.txt b/compiler/fir/resolve/testData/resolve/smartcasts/anotherBoundSmartcasts.txt index d2042571b59..03dbb21d101 100644 --- a/compiler/fir/resolve/testData/resolve/smartcasts/anotherBoundSmartcasts.txt +++ b/compiler/fir/resolve/testData/resolve/smartcasts/anotherBoundSmartcasts.txt @@ -1,18 +1,25 @@ FILE: anotherBoundSmartcasts.kt - public abstract interface A : R|kotlin/Any| { - public abstract fun foo(): R|kotlin/Int| + public final class A : R|kotlin/Any| { + public constructor(): R|A| { + super() + } - public abstract val x: R|kotlin/Int| + public final fun foo(): R|kotlin/Int| { + ^foo Int(1) + } + + public final val x: R|kotlin/Int| = Int(1) public get(): R|kotlin/Int| - public abstract fun bar(): R|kotlin/Unit| + public final fun bar(): R|kotlin/Unit| { + } } public final fun test_1(a: R|A?|): R|kotlin/Unit| { lval x: R|kotlin/Int?| = R|/a|?.R|/A.x| when () { !=(R|/x|, Null(null)) -> { - R|/a|.#() + R|/a|.R|/A.bar|() } } @@ -21,7 +28,7 @@ FILE: anotherBoundSmartcasts.kt lval x: R|kotlin/Int?| = R|/a|?.R|/A.foo|() when () { !=(R|/x|, Null(null)) -> { - R|/a|.#() + R|/a|.R|/A.bar|() } } @@ -37,5 +44,45 @@ FILE: anotherBoundSmartcasts.kt } R|/a|.R|/A.foo|() - R|/x|.#() + R|/x|.R|/A.foo|() + } + public abstract interface B : R|kotlin/Any| { + public abstract fun foo(): R|kotlin/Int| + + public abstract val x: R|kotlin/Int| + public get(): R|kotlin/Int| + + public abstract fun bar(): R|kotlin/Unit| + + } + public final fun test_1(a: R|B?|): R|kotlin/Unit| { + lval x: R|kotlin/Int?| = R|/a|?.R|/B.x| + when () { + !=(R|/x|, Null(null)) -> { + R|/a|.R|/B.bar|() + } + } + + } + public final fun test_2(a: R|B?|): R|kotlin/Unit| { + lval x: R|kotlin/Int?| = R|/a|?.R|/B.foo|() + when () { + !=(R|/x|, Null(null)) -> { + R|/a|.R|/B.bar|() + } + } + + } + public final fun test_3(x: R|kotlin/Any?|): R|kotlin/Unit| { + lval a: R|B| = when (lval : R|B?| = (R|/x| as? R|B|)) { + ==($subj$, Null(null)) -> { + ^test_3 Unit + } + else -> { + R|/| + } + } + + R|/a|.R|/B.foo|() + R|/x|.R|/B.foo|() } diff --git a/compiler/fir/resolve/testData/resolve/smartcasts/booleanOperators.dot b/compiler/fir/resolve/testData/resolve/smartcasts/booleanOperators.dot index 2fd49e2cc48..f901a823488 100644 --- a/compiler/fir/resolve/testData/resolve/smartcasts/booleanOperators.dot +++ b/compiler/fir/resolve/testData/resolve/smartcasts/booleanOperators.dot @@ -455,4 +455,49 @@ digraph booleanOperators_kt { 160 -> {161}; 161 -> {162}; + subgraph cluster_38 { + color=red + 163 [label="Enter function test_8" style="filled" fillcolor=red]; + subgraph cluster_39 { + color=blue + 164 [label="Enter when"]; + subgraph cluster_40 { + color=blue + 165 [label="Enter when branch condition "]; + 166 [label="Access variable R|/x|"]; + 167 [label="Type operator: x !is A"]; + 168 [label="Function call: (R|/x| !is R|A|).R|kotlin/Boolean.not|()"]; + 169 [label="Exit when branch condition"]; + } + 170 [label="Synthetic else branch"]; + 171 [label="Enter when branch result"]; + subgraph cluster_41 { + color=blue + 172 [label="Enter block"]; + 173 [label="Access variable R|/x|"]; + 174 [label="Function call: R|/x|.R|/A.foo|()"]; + 175 [label="Exit block"]; + } + 176 [label="Exit when branch result"]; + 177 [label="Exit when"]; + } + 178 [label="Exit function test_8" style="filled" fillcolor=red]; + } + + 163 -> {164}; + 164 -> {165}; + 165 -> {166}; + 166 -> {167}; + 167 -> {168}; + 168 -> {169}; + 169 -> {171 170}; + 170 -> {177}; + 171 -> {172}; + 172 -> {173}; + 173 -> {174}; + 174 -> {175}; + 175 -> {176}; + 176 -> {177}; + 177 -> {178}; + } diff --git a/compiler/fir/resolve/testData/resolve/smartcasts/booleanOperators.kt b/compiler/fir/resolve/testData/resolve/smartcasts/booleanOperators.kt index af00bb5ef9c..8b1a8978f2e 100644 --- a/compiler/fir/resolve/testData/resolve/smartcasts/booleanOperators.kt +++ b/compiler/fir/resolve/testData/resolve/smartcasts/booleanOperators.kt @@ -57,4 +57,10 @@ fun test_7(x: Any) { if (x is A && x.bool()) { x.foo() } +} + +fun test_8(x: Any) { + if ((x !is A).not()) { + x.foo() + } } \ No newline at end of file diff --git a/compiler/fir/resolve/testData/resolve/smartcasts/booleanOperators.txt b/compiler/fir/resolve/testData/resolve/smartcasts/booleanOperators.txt index d84f5618cef..5a415532983 100644 --- a/compiler/fir/resolve/testData/resolve/smartcasts/booleanOperators.txt +++ b/compiler/fir/resolve/testData/resolve/smartcasts/booleanOperators.txt @@ -74,3 +74,11 @@ FILE: booleanOperators.kt } } + public final fun test_8(x: R|kotlin/Any|): R|kotlin/Unit| { + when () { + (R|/x| !is R|A|).R|kotlin/Boolean.not|() -> { + R|/x|.R|/A.foo|() + } + } + + } diff --git a/compiler/fir/resolve/testData/resolve/smartcasts/boundSmartcasts.dot b/compiler/fir/resolve/testData/resolve/smartcasts/boundSmartcasts.dot index bae3abbaaf6..c312defece1 100644 --- a/compiler/fir/resolve/testData/resolve/smartcasts/boundSmartcasts.dot +++ b/compiler/fir/resolve/testData/resolve/smartcasts/boundSmartcasts.dot @@ -166,13 +166,15 @@ digraph boundSmartcasts_kt { color=blue 67 [label="Enter block"]; 68 [label="Access variable R|/z|"]; - 69 [label="Function call: R|/z|.#()"]; - 70 [label="Exit block"]; + 69 [label="Function call: R|/z|.#()"]; + 70 [label="Access variable R|/z|"]; + 71 [label="Function call: R|/z|.R|/B.bar|()"]; + 72 [label="Exit block"]; } - 71 [label="Exit when branch result"]; - 72 [label="Exit when"]; + 73 [label="Exit when branch result"]; + 74 [label="Exit when"]; } - 73 [label="Exit function test_3" style="filled" fillcolor=red]; + 75 [label="Exit function test_3" style="filled" fillcolor=red]; } 42 -> {43}; @@ -198,7 +200,7 @@ digraph boundSmartcasts_kt { 62 -> {63}; 63 -> {64}; 64 -> {66 65}; - 65 -> {72}; + 65 -> {74}; 66 -> {67}; 67 -> {68}; 68 -> {69}; @@ -206,49 +208,49 @@ digraph boundSmartcasts_kt { 70 -> {71}; 71 -> {72}; 72 -> {73}; + 73 -> {74}; + 74 -> {75}; subgraph cluster_17 { color=red - 74 [label="Enter function test_4" style="filled" fillcolor=red]; - 75 [label="Const: Int(1)"]; - 76 [label="Variable declaration: lvar x: R|kotlin/Any|"]; - 77 [label="Access variable R|/x|"]; - 78 [label="Type operator: x as Int"]; + 76 [label="Enter function test_4" style="filled" fillcolor=red]; + 77 [label="Const: Int(1)"]; + 78 [label="Variable declaration: lvar x: R|kotlin/Any|"]; 79 [label="Access variable R|/x|"]; - 80 [label="Function call: R|/x|.R|kotlin/Int.inc|()"]; - 81 [label="Access variable R|/y|"]; - 82 [label="Assignmenet: R|/x|"]; - 83 [label="Access variable R|/x|"]; - 84 [label="Function call: R|/x|.R|kotlin/Int.inc|()"]; + 80 [label="Type operator: x as Int"]; + 81 [label="Access variable R|/x|"]; + 82 [label="Function call: R|/x|.R|kotlin/Int.inc|()"]; + 83 [label="Access variable R|/y|"]; + 84 [label="Assignmenet: R|/x|"]; + 85 [label="Access variable R|/x|"]; + 86 [label="Function call: R|/x|.#()"]; subgraph cluster_18 { color=blue - 85 [label="Enter when"]; + 87 [label="Enter when"]; subgraph cluster_19 { color=blue - 86 [label="Enter when branch condition "]; - 87 [label="Access variable R|/y|"]; - 88 [label="Type operator: y is A"]; - 89 [label="Exit when branch condition"]; + 88 [label="Enter when branch condition "]; + 89 [label="Access variable R|/y|"]; + 90 [label="Type operator: y is A"]; + 91 [label="Exit when branch condition"]; } - 90 [label="Synthetic else branch"]; - 91 [label="Enter when branch result"]; + 92 [label="Synthetic else branch"]; + 93 [label="Enter when branch result"]; subgraph cluster_20 { color=blue - 92 [label="Enter block"]; - 93 [label="Access variable R|/x|"]; - 94 [label="Function call: R|/x|.#()"]; - 95 [label="Access variable R|/y|"]; - 96 [label="Function call: R|/y|.R|/A.foo|()"]; - 97 [label="Exit block"]; + 94 [label="Enter block"]; + 95 [label="Access variable R|/x|"]; + 96 [label="Function call: R|/x|.R|/A.foo|()"]; + 97 [label="Access variable R|/y|"]; + 98 [label="Function call: R|/y|.R|/A.foo|()"]; + 99 [label="Exit block"]; } - 98 [label="Exit when branch result"]; - 99 [label="Exit when"]; + 100 [label="Exit when branch result"]; + 101 [label="Exit when"]; } - 100 [label="Exit function test_4" style="filled" fillcolor=red]; + 102 [label="Exit function test_4" style="filled" fillcolor=red]; } - 74 -> {75}; - 75 -> {76}; 76 -> {77}; 77 -> {78}; 78 -> {79}; @@ -262,10 +264,10 @@ digraph boundSmartcasts_kt { 86 -> {87}; 87 -> {88}; 88 -> {89}; - 89 -> {91 90}; - 90 -> {99}; - 91 -> {92}; - 92 -> {93}; + 89 -> {90}; + 90 -> {91}; + 91 -> {93 92}; + 92 -> {101}; 93 -> {94}; 94 -> {95}; 95 -> {96}; @@ -273,5 +275,7 @@ digraph boundSmartcasts_kt { 97 -> {98}; 98 -> {99}; 99 -> {100}; + 100 -> {101}; + 101 -> {102}; } diff --git a/compiler/fir/resolve/testData/resolve/smartcasts/boundSmartcasts.kt b/compiler/fir/resolve/testData/resolve/smartcasts/boundSmartcasts.kt index accfa527131..fa075a6123c 100644 --- a/compiler/fir/resolve/testData/resolve/smartcasts/boundSmartcasts.kt +++ b/compiler/fir/resolve/testData/resolve/smartcasts/boundSmartcasts.kt @@ -29,7 +29,8 @@ fun test_3(x: Any, y: Any) { } z = y if (y is B) { - z.bar() + z.foo() + z.bar() } } @@ -38,9 +39,9 @@ fun test_4(y: Any) { x as Int x.inc() x = y - x.inc() + x.inc() if (y is A) { - x.foo() + x.foo() y.foo() } } \ No newline at end of file diff --git a/compiler/fir/resolve/testData/resolve/smartcasts/boundSmartcasts.txt b/compiler/fir/resolve/testData/resolve/smartcasts/boundSmartcasts.txt index 9ae34e8bdad..76cf4cda2a5 100644 --- a/compiler/fir/resolve/testData/resolve/smartcasts/boundSmartcasts.txt +++ b/compiler/fir/resolve/testData/resolve/smartcasts/boundSmartcasts.txt @@ -38,7 +38,8 @@ FILE: boundSmartcasts.kt R|/z| = R|/y| when () { (R|/y| is R|B|) -> { - R|/z|.#() + R|/z|.#() + R|/z|.R|/B.bar|() } } @@ -48,10 +49,10 @@ FILE: boundSmartcasts.kt (R|/x| as R|kotlin/Int|) R|/x|.R|kotlin/Int.inc|() R|/x| = R|/y| - R|/x|.R|kotlin/Int.inc|() + R|/x|.#() when () { (R|/y| is R|A|) -> { - R|/x|.#() + R|/x|.R|/A.foo|() R|/y|.R|/A.foo|() } } diff --git a/compiler/fir/resolve/testData/resolve/smartcasts/notBoundSmartcasts.dot b/compiler/fir/resolve/testData/resolve/smartcasts/notBoundSmartcasts.dot index ac1750807be..af9ad509e67 100644 --- a/compiler/fir/resolve/testData/resolve/smartcasts/notBoundSmartcasts.dot +++ b/compiler/fir/resolve/testData/resolve/smartcasts/notBoundSmartcasts.dot @@ -164,119 +164,88 @@ digraph notBoundSmartcasts_kt { subgraph cluster_12 { color=red - 57 [label="Enter function test_2" style="filled" fillcolor=red]; - subgraph cluster_13 { - color=blue - 58 [label="Enter when"]; - 59 [label="Access variable R|/d1|"]; - 60 [label="Access variable R|/D.any|"]; - 61 [label="Variable declaration: lval : R|kotlin/Any?|"]; - subgraph cluster_14 { - color=blue - 62 [label="Enter when branch condition "]; - 63 [label="Const: Null(null)"]; - 64 [label="Operator =="]; - 65 [label="Exit when branch condition"]; - } - subgraph cluster_15 { - color=blue - 66 [label="Enter when branch condition else"]; - 67 [label="Exit when branch condition"]; - } - 68 [label="Enter when branch result"]; - subgraph cluster_16 { - color=blue - 69 [label="Enter block"]; - 70 [label="Access variable R|/|"]; - 71 [label="Exit block"]; - } - 72 [label="Exit when branch result"]; - 73 [label="Enter when branch result"]; - subgraph cluster_17 { - color=blue - 74 [label="Enter block"]; - 75 [label="Jump: ^test_2 Unit"]; - 76 [label="Stub" style="filled" fillcolor=gray]; - 77 [label="Exit block" style="filled" fillcolor=gray]; - } - 78 [label="Exit when branch result" style="filled" fillcolor=gray]; - 79 [label="Exit when"]; - } - 80 [label="Variable declaration: lval a: R|kotlin/Any|"]; - subgraph cluster_18 { - color=blue - 81 [label="Enter when"]; - 82 [label="Access variable R|/d2|"]; - 83 [label="Access variable R|/D.any|"]; - 84 [label="Variable declaration: lval : R|kotlin/Any|"]; - subgraph cluster_19 { - color=blue - 85 [label="Enter when branch condition "]; - 86 [label="Const: Null(null)"]; - 87 [label="Operator =="]; - 88 [label="Exit when branch condition"]; - } - subgraph cluster_20 { - color=blue - 89 [label="Enter when branch condition else"]; - 90 [label="Exit when branch condition"]; - } - 91 [label="Enter when branch result"]; - subgraph cluster_21 { - color=blue - 92 [label="Enter block"]; - 93 [label="Access variable R|/|"]; - 94 [label="Exit block"]; - } - 95 [label="Exit when branch result"]; - 96 [label="Enter when branch result"]; - subgraph cluster_22 { - color=blue - 97 [label="Enter block"]; - 98 [label="Jump: ^test_2 Unit"]; - 99 [label="Stub" style="filled" fillcolor=gray]; - 100 [label="Exit block" style="filled" fillcolor=gray]; - } - 101 [label="Exit when branch result" style="filled" fillcolor=gray]; - 102 [label="Exit when"]; - } - 103 [label="Variable declaration: lval b: R|kotlin/Any|"]; - 104 [label="Access variable R|/a|"]; - 105 [label="Type operator: a as A"]; - 106 [label="Access variable R|/a|"]; - 107 [label="Function call: R|/a|.R|/A.foo|()"]; - 108 [label="Access variable R|/b|"]; - 109 [label="Type operator: b as B"]; - 110 [label="Access variable R|/b|"]; - 111 [label="Function call: R|/b|.R|/B.foo|()"]; - 112 [label="Exit function test_2" style="filled" fillcolor=red]; + 57 [label="Enter function baz" style="filled" fillcolor=red]; + 58 [label="Exit function baz" style="filled" fillcolor=red]; } 57 -> {58}; - 58 -> {59}; + + subgraph cluster_13 { + color=red + 59 [label="Enter function test_2" style="filled" fillcolor=red]; + subgraph cluster_14 { + color=blue + 60 [label="Enter when"]; + 61 [label="Access variable R|/d|"]; + 62 [label="Access variable R|/D.any|"]; + 63 [label="Variable declaration: lval : R|kotlin/Any?|"]; + subgraph cluster_15 { + color=blue + 64 [label="Enter when branch condition "]; + 65 [label="Const: Null(null)"]; + 66 [label="Operator =="]; + 67 [label="Exit when branch condition"]; + } + subgraph cluster_16 { + color=blue + 68 [label="Enter when branch condition else"]; + 69 [label="Exit when branch condition"]; + } + 70 [label="Enter when branch result"]; + subgraph cluster_17 { + color=blue + 71 [label="Enter block"]; + 72 [label="Access variable R|/|"]; + 73 [label="Exit block"]; + } + 74 [label="Exit when branch result"]; + 75 [label="Enter when branch result"]; + subgraph cluster_18 { + color=blue + 76 [label="Enter block"]; + 77 [label="Jump: ^test_2 Unit"]; + 78 [label="Stub" style="filled" fillcolor=gray]; + 79 [label="Exit block" style="filled" fillcolor=gray]; + } + 80 [label="Exit when branch result" style="filled" fillcolor=gray]; + 81 [label="Exit when"]; + } + 82 [label="Variable declaration: lval a: R|kotlin/Any|"]; + 83 [label="Access variable R|/a|"]; + 84 [label="Function call: R|/a|.R|/baz|()"]; + 85 [label="Access variable R|/d|"]; + 86 [label="Access variable R|/D.any|"]; + 87 [label="Function call: R|/d|.R|/D.any|.R|/baz|()"]; + 88 [label="Access variable R|/a|"]; + 89 [label="Type operator: a as A"]; + 90 [label="Access variable R|/a|"]; + 91 [label="Function call: R|/a|.R|/A.foo|()"]; + 92 [label="Exit function test_2" style="filled" fillcolor=red]; + } + 59 -> {60}; 60 -> {61}; 61 -> {62}; 62 -> {63}; 63 -> {64}; 64 -> {65}; - 65 -> {73 66}; + 65 -> {66}; 66 -> {67}; - 67 -> {68}; + 67 -> {75 68}; 68 -> {69}; 69 -> {70}; 70 -> {71}; 71 -> {72}; - 72 -> {79}; + 72 -> {73}; 73 -> {74}; - 74 -> {75}; - 75 -> {112}; - 75 -> {76} [style=dotted]; - 76 -> {77} [style=dotted]; + 74 -> {81}; + 75 -> {76}; + 76 -> {77}; + 77 -> {92}; 77 -> {78} [style=dotted]; 78 -> {79} [style=dotted]; - 79 -> {80}; - 80 -> {81}; + 79 -> {80} [style=dotted]; + 80 -> {81} [style=dotted]; 81 -> {82}; 82 -> {83}; 83 -> {84}; @@ -284,63 +253,81 @@ digraph notBoundSmartcasts_kt { 85 -> {86}; 86 -> {87}; 87 -> {88}; - 88 -> {96 89}; + 88 -> {89}; 89 -> {90}; 90 -> {91}; 91 -> {92}; - 92 -> {93}; + + subgraph cluster_19 { + color=red + 93 [label="Enter function test_3" style="filled" fillcolor=red]; + 94 [label="Access variable R|/d1|"]; + 95 [label="Access variable R|/D.any|"]; + 96 [label="Variable declaration: lval a: R|kotlin/Any?|"]; + 97 [label="Access variable R|/a|"]; + 98 [label="Type operator: a as A"]; + 99 [label="Access variable R|/a|"]; + 100 [label="Function call: R|/a|.R|/A.foo|()"]; + 101 [label="Access variable R|/d1|"]; + 102 [label="Access variable R|/D.any|"]; + 103 [label="Function call: R|/d1|.R|/D.any|.R|/A.foo|()"]; + 104 [label="Access variable R|/d1|"]; + 105 [label="Access variable R|/D.any|"]; + 106 [label="Function call: R|/d1|.R|/D.any|.R|/baz|()"]; + 107 [label="Exit function test_3" style="filled" fillcolor=red]; + } + 93 -> {94}; 94 -> {95}; - 95 -> {102}; + 95 -> {96}; 96 -> {97}; 97 -> {98}; - 98 -> {112}; - 98 -> {99} [style=dotted]; - 99 -> {100} [style=dotted]; - 100 -> {101} [style=dotted]; - 101 -> {102} [style=dotted]; + 98 -> {99}; + 99 -> {100}; + 100 -> {101}; + 101 -> {102}; 102 -> {103}; 103 -> {104}; 104 -> {105}; 105 -> {106}; 106 -> {107}; - 107 -> {108}; - 108 -> {109}; - 109 -> {110}; - 110 -> {111}; - 111 -> {112}; - subgraph cluster_23 { + subgraph cluster_20 { color=red - 113 [label="Enter function test_3" style="filled" fillcolor=red]; - 114 [label="Access variable R|/d1|"]; + 108 [label="Enter function test_4" style="filled" fillcolor=red]; + 109 [label="Access variable R|/d1|"]; + 110 [label="Enter safe call"]; + 111 [label="Access variable R|/D.any|"]; + 112 [label="Exit safe call"]; + 113 [label="Variable declaration: lval a: R|kotlin/Any?|"]; + 114 [label="Access variable R|/d2|"]; 115 [label="Enter safe call"]; 116 [label="Access variable R|/D.any|"]; 117 [label="Exit safe call"]; - 118 [label="Variable declaration: lval a: R|kotlin/Any?|"]; - 119 [label="Access variable R|/d2|"]; - 120 [label="Enter safe call"]; - 121 [label="Access variable R|/D.any|"]; - 122 [label="Exit safe call"]; - 123 [label="Variable declaration: lval b: R|kotlin/Any?|"]; - 124 [label="Access variable R|/a|"]; - 125 [label="Type operator: a as A"]; - 126 [label="Access variable R|/a|"]; - 127 [label="Function call: R|/a|.R|/A.foo|()"]; - 128 [label="Access variable R|/b|"]; - 129 [label="Type operator: b as B"]; - 130 [label="Access variable R|/b|"]; - 131 [label="Function call: R|/b|.#()"]; - 132 [label="Exit function test_3" style="filled" fillcolor=red]; + 118 [label="Variable declaration: lval b: R|kotlin/Any?|"]; + 119 [label="Access variable R|/a|"]; + 120 [label="Type operator: a as A"]; + 121 [label="Access variable R|/a|"]; + 122 [label="Function call: R|/a|.R|/A.foo|()"]; + 123 [label="Access variable R|/b|"]; + 124 [label="Type operator: b as B"]; + 125 [label="Access variable R|/b|"]; + 126 [label="Function call: R|/b|.R|/B.foo|()"]; + 127 [label="Exit function test_4" style="filled" fillcolor=red]; } + 108 -> {109}; + 109 -> {110 112}; + 110 -> {111}; + 111 -> {112}; + 112 -> {113}; 113 -> {114}; 114 -> {115 117}; 115 -> {116}; 116 -> {117}; 117 -> {118}; 118 -> {119}; - 119 -> {120 122}; + 119 -> {120}; 120 -> {121}; 121 -> {122}; 122 -> {123}; @@ -348,10 +335,5 @@ digraph notBoundSmartcasts_kt { 124 -> {125}; 125 -> {126}; 126 -> {127}; - 127 -> {128}; - 128 -> {129}; - 129 -> {130}; - 130 -> {131}; - 131 -> {132}; } diff --git a/compiler/fir/resolve/testData/resolve/smartcasts/notBoundSmartcasts.kt b/compiler/fir/resolve/testData/resolve/smartcasts/notBoundSmartcasts.kt index da9e4fb6d79..5d823ed732e 100644 --- a/compiler/fir/resolve/testData/resolve/smartcasts/notBoundSmartcasts.kt +++ b/compiler/fir/resolve/testData/resolve/smartcasts/notBoundSmartcasts.kt @@ -29,23 +29,30 @@ fun test_1() { class D(val any: Any?) -fun test_2(d1: D, d2: D) { +fun Any.baz() {} + +fun test_2(d: D) { // Elvis operator is converted into == function call - val a = d1.any ?: return - val b = d2.any ?: return + val a = d.any ?: return + a.baz() + d.any.baz() a as A a.foo() - b as B - b.foo() } -// TODO: Fix this -- see comment in FirDataFlowAnalyzer.getRealVariablesForSafeCallChain() -fun test_3(d1: D, d2: D) { +fun test_3(d1: D) { + val a = d1.any + a as A + a.foo() + d1.any.foo() + d1.any.baz() +} + +fun test_4(d1: D, d2: D) { val a = d1?.any val b = d2?.any a as A a.foo() b as B - // Issue: b incorrectly smartcasted to (A & B) - b.foo() // should be OK + b.foo() // should be OK } diff --git a/compiler/fir/resolve/testData/resolve/smartcasts/notBoundSmartcasts.txt b/compiler/fir/resolve/testData/resolve/smartcasts/notBoundSmartcasts.txt index d5c76a4096b..b722bb9a361 100644 --- a/compiler/fir/resolve/testData/resolve/smartcasts/notBoundSmartcasts.txt +++ b/compiler/fir/resolve/testData/resolve/smartcasts/notBoundSmartcasts.txt @@ -38,17 +38,10 @@ FILE: notBoundSmartcasts.kt public get(): R|kotlin/Any?| } - public final fun test_2(d1: R|D|, d2: R|D|): R|kotlin/Unit| { - lval a: R|kotlin/Any| = when (lval : R|kotlin/Any?| = R|/d1|.R|/D.any|) { - ==($subj$, Null(null)) -> { - ^test_2 Unit - } - else -> { - R|/| - } - } - - lval b: R|kotlin/Any| = when (lval : R|kotlin/Any| = R|/d2|.R|/D.any|) { + public final fun R|kotlin/Any|.baz(): R|kotlin/Unit| { + } + public final fun test_2(d: R|D|): R|kotlin/Unit| { + lval a: R|kotlin/Any| = when (lval : R|kotlin/Any?| = R|/d|.R|/D.any|) { ==($subj$, Null(null)) -> { ^test_2 Unit } @@ -57,16 +50,23 @@ FILE: notBoundSmartcasts.kt } } + R|/a|.R|/baz|() + R|/d|.R|/D.any|.R|/baz|() (R|/a| as R|A|) R|/a|.R|/A.foo|() - (R|/b| as R|B|) - R|/b|.R|/B.foo|() } - public final fun test_3(d1: R|D|, d2: R|D|): R|kotlin/Unit| { + public final fun test_3(d1: R|D|): R|kotlin/Unit| { + lval a: R|kotlin/Any?| = R|/d1|.R|/D.any| + (R|/a| as R|A|) + R|/a|.R|/A.foo|() + R|/d1|.R|/D.any|.R|/A.foo|() + R|/d1|.R|/D.any|.R|/baz|() + } + public final fun test_4(d1: R|D|, d2: R|D|): R|kotlin/Unit| { lval a: R|kotlin/Any?| = R|/d1|?.R|/D.any| lval b: R|kotlin/Any?| = R|/d2|?.R|/D.any| (R|/a| as R|A|) R|/a|.R|/A.foo|() (R|/b| as R|B|) - R|/b|.#() + R|/b|.R|/B.foo|() } diff --git a/compiler/fir/resolve/testData/resolve/smartcasts/nullability.dot b/compiler/fir/resolve/testData/resolve/smartcasts/nullability.dot index d4a70dacef9..e8aa094c0ab 100644 --- a/compiler/fir/resolve/testData/resolve/smartcasts/nullability.dot +++ b/compiler/fir/resolve/testData/resolve/smartcasts/nullability.dot @@ -1153,11 +1153,11 @@ digraph nullability_kt { 434 [label="Access variable R|/QImpl.data|"]; 435 [label="Access variable R|/q2|"]; 436 [label="Access variable R|/QImpl.data|"]; - 437 [label="Access variable R|/MyData.s|"]; + 437 [label="Access variable #"]; 438 [label="Access variable R|/q2|"]; 439 [label="Access variable R|/QImpl.data|"]; - 440 [label="Access variable R|/MyData.s|"]; - 441 [label="Function call: R|/q2|.R|/QImpl.data|.R|/MyData.s|.R|kotlin/Int.inc|()"]; + 440 [label="Access variable #"]; + 441 [label="Function call: R|/q2|.R|/QImpl.data|.#.#()"]; subgraph cluster_91 { color=blue 442 [label="Enter when"]; diff --git a/compiler/fir/resolve/testData/resolve/smartcasts/nullability.kt b/compiler/fir/resolve/testData/resolve/smartcasts/nullability.kt index 0f8dac369c5..a12dc4d94e6 100644 --- a/compiler/fir/resolve/testData/resolve/smartcasts/nullability.kt +++ b/compiler/fir/resolve/testData/resolve/smartcasts/nullability.kt @@ -1,3 +1,5 @@ +// CONTAINS ERRORS + interface A { fun foo() fun getA(): A @@ -148,8 +150,8 @@ fun test_11(q: QImpl?, q2: QImpl) { // Smartcasting of `q.data` should have no effect on `q2.data`. // Issue: Smartcasting of QImpl.data affects all instances q2.data - q2.data.s // should be bad - q2.data.s.inc() // should be bad + q2.data.s // should be bad + q2.data.s.inc() // should be bad if (q2.data != null) { q2.data.s diff --git a/compiler/fir/resolve/testData/resolve/smartcasts/nullability.txt b/compiler/fir/resolve/testData/resolve/smartcasts/nullability.txt index fb12eda7067..f6684da3000 100644 --- a/compiler/fir/resolve/testData/resolve/smartcasts/nullability.txt +++ b/compiler/fir/resolve/testData/resolve/smartcasts/nullability.txt @@ -215,8 +215,8 @@ FILE: nullability.kt R|/q|.R|/QImpl.data|.R|/MyData.s| R|/q|.R|/QImpl.data|.R|/MyData.s|.R|kotlin/Int.inc|() R|/q2|.R|/QImpl.data| - R|/q2|.R|/QImpl.data|.R|/MyData.s| - R|/q2|.R|/QImpl.data|.R|/MyData.s|.R|kotlin/Int.inc|() + R|/q2|.R|/QImpl.data|.# + R|/q2|.R|/QImpl.data|.#.#() when () { !=(R|/q2|.R|/QImpl.data|, Null(null)) -> { R|/q2|.R|/QImpl.data|.R|/MyData.s| diff --git a/compiler/fir/resolve/testData/resolve/smartcasts/smartcastAfterReassignment.dot b/compiler/fir/resolve/testData/resolve/smartcasts/smartcastAfterReassignment.dot index 5e485c303a8..65b357603e5 100644 --- a/compiler/fir/resolve/testData/resolve/smartcasts/smartcastAfterReassignment.dot +++ b/compiler/fir/resolve/testData/resolve/smartcasts/smartcastAfterReassignment.dot @@ -88,9 +88,8 @@ digraph smartcastAfterReassignment_kt { 35 [label="Const: Null(null)"]; 36 [label="Assignmenet: R|/x|"]; 37 [label="Access variable R|/x|"]; - 38 [label="Stub" style="filled" fillcolor=gray]; - 39 [label="Access variable #" style="filled" fillcolor=gray]; - 40 [label="Exit function test_3" style="filled" fillcolor=red]; + 38 [label="Access variable #"]; + 39 [label="Exit function test_3" style="filled" fillcolor=red]; } 28 -> {29}; @@ -102,9 +101,7 @@ digraph smartcastAfterReassignment_kt { 34 -> {35}; 35 -> {36}; 36 -> {37}; - 37 -> {40}; - 37 -> {38} [style=dotted]; - 38 -> {39} [style=dotted]; - 39 -> {40} [style=dotted]; + 37 -> {38}; + 38 -> {39}; }