[FIR] Implement new data flow analysis
This commit is contained in:
@@ -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.**
|
||||
|
||||
@@ -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.**
|
||||
|
||||
@@ -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) }
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+266
@@ -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<ConeKotlinType>,
|
||||
// override val exactNotType: PersistentSet<ConeKotlinType>
|
||||
//) : 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<RealDataFlowVariable, PersistentFirDataFlowInfo>
|
||||
//private typealias PersistentConditionalInfos = PersistentMap<DataFlowVariable, PersistentList<ConditionalFirDataFlowInfo>>
|
||||
//
|
||||
//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<ConditionalFirDataFlowInfo> {
|
||||
// return conditionalInfos[variable] ?: emptyList()
|
||||
// }
|
||||
//
|
||||
// override fun getVariablesInApprovedInfos(): Collection<RealDataFlowVariable> {
|
||||
// return approvedInfos.keys
|
||||
// }
|
||||
//
|
||||
// override fun removeConditionalInfos(variable: DataFlowVariable): Collection<ConditionalFirDataFlowInfo> {
|
||||
// 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>): Flow {
|
||||
// if (flows.isEmpty()) return createEmptyFlow()
|
||||
// flows.singleOrNull()?.let { return it }
|
||||
//
|
||||
// @Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
|
||||
// val flows = flows as Collection<PersistentFlow>
|
||||
// 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<RealDataFlowVariable> =
|
||||
// object : DiffIterable<RealDataFlowVariable>(parentFlow, this) {
|
||||
// override fun extractIterator(flow: PersistentFlow): Iterator<RealDataFlowVariable> {
|
||||
// return flow.approvedInfosDiff.keys.iterator()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private abstract class DiffIterable<T>(private val parentFlow: PersistentFlow, private var currentFlow: PersistentFlow) : Iterable<T> {
|
||||
// private var currentIterator = extractIterator(currentFlow)
|
||||
//
|
||||
// abstract fun extractIterator(flow: PersistentFlow): Iterator<T>
|
||||
//
|
||||
// override fun iterator(): Iterator<T> {
|
||||
// return object : Iterator<T> {
|
||||
// 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
|
||||
// }
|
||||
//}
|
||||
+813
@@ -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<FLOW : Flow>(
|
||||
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<PersistentFlow>(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<FLOW>
|
||||
private val context: ConeInferenceContext = components.inferenceComponents.ctx
|
||||
|
||||
private val graphBuilder = ControlFlowGraphBuilder()
|
||||
protected val variableStorage = VariableStorage()
|
||||
private val flowOnNodes = mutableMapOf<CFGNode<*>, FLOW>()
|
||||
|
||||
private val variablesForWhenConditions = mutableMapOf<WhenBranchConditionExitNode, DataFlowVariable>()
|
||||
|
||||
private var contractDescriptionVisitingMode = false
|
||||
|
||||
protected val any = components.session.builtinTypes.anyType.coneTypeUnsafe<ConeKotlinType>()
|
||||
private val nullableNothing = components.session.builtinTypes.nullableNothingType.coneTypeUnsafe<ConeKotlinType>()
|
||||
|
||||
// ----------------------------------- Requests -----------------------------------
|
||||
|
||||
fun getTypeUsingSmartcastInfo(qualifiedAccessExpression: FirQualifiedAccessExpression): Collection<ConeKotlinType>? {
|
||||
/*
|
||||
* 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<FirStatement> {
|
||||
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<ConeKotlinType>()
|
||||
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<ConeConditionalEffectDeclaration>()
|
||||
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<SyntheticVariable>()?.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<ConeKotlinType>())
|
||||
}
|
||||
}
|
||||
|
||||
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 : CFGNode<*>> 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)
|
||||
}
|
||||
}
|
||||
@@ -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<LogicStatement>
|
||||
abstract fun getVariablesInKnownInfos(): Collection<RealVariable>
|
||||
abstract fun removeConditions(variable: DataFlowVariable): Collection<LogicStatement>
|
||||
}
|
||||
|
||||
abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceContext) {
|
||||
// ------------------------------- Flow operations -------------------------------
|
||||
|
||||
abstract fun createEmptyFlow(): FLOW
|
||||
abstract fun forkFlow(flow: FLOW): FLOW
|
||||
abstract fun joinFlow(flows: Collection<FLOW>): 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<LogicStatement>
|
||||
|
||||
// ------------------------------- 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<LogicStatement>,
|
||||
val conditionalFromRight: Collection<LogicStatement>,
|
||||
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<LogicStatement>
|
||||
)
|
||||
|
||||
/**
|
||||
* 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<DataFlowInfo> {
|
||||
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<RealVariable, MutableDataFlowInfo>()
|
||||
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 <E> Collection<Collection<E>>.intersectSets(): Set<E> {
|
||||
if (isEmpty()) return emptySet()
|
||||
val iterator = iterator()
|
||||
val result = HashSet<E>(iterator.next())
|
||||
while (iterator.hasNext()) {
|
||||
result.retainAll(iterator.next())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
protected fun or(infos: Collection<DataFlowInfo>): 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<Set<ConeKotlinType>>): MutableSet<ConeKotlinType> {
|
||||
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 <FLOW : Flow> LogicSystem<FLOW>.approvePredicate(flow: FLOW, predicate: Predicate, statements: Collection<LogicStatement>): MutableKnownFacts {
|
||||
return mutableMapOf<RealVariable, MutableDataFlowInfo>().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 <F : Flow> LogicSystem<F>.replaceConditionalVariableInStatements(
|
||||
flow: F,
|
||||
originalVariable: DataFlowVariable,
|
||||
newVariable: DataFlowVariable,
|
||||
filter: (LogicStatement) -> Boolean = { true },
|
||||
transform: (LogicStatement) -> LogicStatement = { it }
|
||||
) {
|
||||
translateConditionalVariableInStatements(
|
||||
flow,
|
||||
originalVariable,
|
||||
newVariable,
|
||||
shouldRemoveOriginalStatements = true,
|
||||
filter,
|
||||
transform
|
||||
)
|
||||
}
|
||||
+411
@@ -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<ConeKotlinType>,
|
||||
override val exactNotType: PersistentSet<ConeKotlinType>
|
||||
) : 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<RealVariable, PersistentDataFlowInfo>
|
||||
typealias PersistentLogicStatements = PersistentMap<DataFlowVariable, PersistentList<LogicStatement>>
|
||||
|
||||
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<LogicStatement> {
|
||||
return logicStatements[variable] ?: emptyList()
|
||||
}
|
||||
|
||||
override fun getVariablesInKnownInfos(): Collection<RealVariable> {
|
||||
return knownFacts.keys
|
||||
}
|
||||
|
||||
override fun removeConditions(variable: DataFlowVariable): Collection<LogicStatement> {
|
||||
return getLogicStatements(variable).also {
|
||||
if (it.isNotEmpty()) {
|
||||
logicStatements -= variable
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class PersistentLogicSystem(private val anyType: ConeKotlinType, context: ConeInferenceContext) :
|
||||
LogicSystem<PersistentFlow>(context) {
|
||||
override fun createEmptyFlow(): PersistentFlow {
|
||||
return PersistentFlow()
|
||||
}
|
||||
|
||||
override fun forkFlow(flow: PersistentFlow): PersistentFlow {
|
||||
return PersistentFlow(flow)
|
||||
}
|
||||
|
||||
override fun joinFlow(flows: Collection<PersistentFlow>): 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<RealVariable> =
|
||||
object : DiffIterable<RealVariable>(parentFlow, this) {
|
||||
override fun extractIterator(flow: PersistentFlow): Iterator<RealVariable> {
|
||||
return flow.knownFactsDiff.keys.iterator()
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class DiffIterable<T>(private val parentFlow: PersistentFlow, private var currentFlow: PersistentFlow) : Iterable<T> {
|
||||
private var currentIterator = extractIterator(currentFlow)
|
||||
|
||||
abstract fun extractIterator(flow: PersistentFlow): Iterator<T>
|
||||
|
||||
override fun iterator(): Iterator<T> {
|
||||
return object : Iterator<T> {
|
||||
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<RealVariable>()
|
||||
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<LogicStatement>?,
|
||||
shouldRemoveSynthetics: Boolean
|
||||
): ArrayListMultimap<RealVariable, DataFlowInfo> {
|
||||
val approvedFacts: ArrayListMultimap<RealVariable, DataFlowInfo> = ArrayListMultimap.create()
|
||||
val predicatesToApprove = LinkedList<Predicate>().apply { this += predicate }
|
||||
approvePredicatesInternal(flow, predicatesToApprove, initialStatements, shouldRemoveSynthetics, approvedFacts)
|
||||
return approvedFacts
|
||||
}
|
||||
|
||||
private fun approvePredicatesInternal(
|
||||
flow: PersistentFlow,
|
||||
predicatesToApprove: LinkedList<Predicate>,
|
||||
initialStatements: Collection<LogicStatement>?,
|
||||
shouldRemoveSynthetics: Boolean,
|
||||
approvedFacts: ArrayListMultimap<RealVariable, DataFlowInfo>
|
||||
) {
|
||||
if (predicatesToApprove.isEmpty()) return
|
||||
val approvedVariables = mutableSetOf<RealVariable>()
|
||||
val approvedPredicates = mutableSetOf<Predicate>()
|
||||
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<Predicate>()
|
||||
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<LogicStatement>
|
||||
) {
|
||||
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<LogicStatement> {
|
||||
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 <E> Collection<Collection<E>>.intersectSets(): Set<E> {
|
||||
if (isEmpty()) return emptySet()
|
||||
val iterator = iterator()
|
||||
val result = HashSet<E>(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}")
|
||||
}
|
||||
@@ -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<Identifier, RealVariable> = HashMap()
|
||||
private val syntheticVariables: MutableMap<FirElement, SyntheticVariable> = HashMap()
|
||||
private val localVariableAliases: MutableMap<AbstractFirBasedSymbol<*>, 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 <when_subject> 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
|
||||
}
|
||||
@@ -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<T : PredicateEffect<T>> {
|
||||
abstract fun invert(): T
|
||||
}
|
||||
|
||||
data class Predicate(val variable: DataFlowVariable, val condition: Condition) : PredicateEffect<Predicate>() {
|
||||
override fun invert(): Predicate {
|
||||
return Predicate(variable, condition.invert())
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "$variable $condition"
|
||||
}
|
||||
}
|
||||
|
||||
abstract class DataFlowInfo : PredicateEffect<DataFlowInfo>() {
|
||||
abstract val variable: RealVariable
|
||||
abstract val exactType: Set<ConeKotlinType>
|
||||
abstract val exactNotType: Set<ConeKotlinType>
|
||||
|
||||
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<ConeKotlinType> = HashSet(),
|
||||
override val exactNotType: MutableSet<ConeKotlinType> = 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<RealVariable, DataFlowInfo>
|
||||
typealias MutableKnownFacts = MutableMap<RealVariable, MutableDataFlowInfo>
|
||||
typealias LogicStatements = Multimap<Predicate, LogicStatement>
|
||||
|
||||
// --------------------------------------- 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<ConeKotlinType>): DataFlowInfo = MutableDataFlowInfo(this, types, HashSet())
|
||||
infix fun RealVariable.has(type: ConeKotlinType): DataFlowInfo =
|
||||
MutableDataFlowInfo(this, HashSet<ConeKotlinType>().apply { this += type }, HashSet())
|
||||
|
||||
infix fun RealVariable.hasNot(types: MutableSet<ConeKotlinType>): DataFlowInfo = MutableDataFlowInfo(this, HashSet(), types)
|
||||
infix fun RealVariable.hasNot(type: ConeKotlinType): DataFlowInfo =
|
||||
MutableDataFlowInfo(this, HashSet(), HashSet<ConeKotlinType>().apply { this += type })
|
||||
@@ -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>): ConeKotlinType? {
|
||||
return when (types.size) {
|
||||
0 -> null
|
||||
1 -> types.first()
|
||||
else -> with(NewCommonSuperTypeCalculator) {
|
||||
commonSuperType(types) as ConeKotlinType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ConeInferenceContext.intersectTypesOrNull(types: List<ConeKotlinType>): 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<RealVariable, DataFlowInfo>) {
|
||||
other.forEach { (variable, info) ->
|
||||
addInfo(variable, info)
|
||||
}
|
||||
}
|
||||
|
||||
@UseExperimental(ExperimentalContracts::class)
|
||||
internal inline fun <K, V> MutableMap<K, V>.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<FirResolvedNamedReference>()?.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")
|
||||
}
|
||||
+3
-3
@@ -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)
|
||||
|
||||
+145
-141
@@ -45,7 +45,9 @@ digraph jumps_kt {
|
||||
22 [label="Variable declaration: lval y: R|kotlin/Int|"];
|
||||
23 [label="Access variable R|<local>/y|"];
|
||||
24 [label="Function call: R|<local>/y|.R|kotlin/Int.inc|()"];
|
||||
25 [label="Exit function test_1" style="filled" fillcolor=red];
|
||||
25 [label="Access variable R|<local>/x|"];
|
||||
26 [label="Function call: R|<local>/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|<local>/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|<local>/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|<local>/x|"];
|
||||
38 [label="Exit block"];
|
||||
38 [label="Enter block"];
|
||||
39 [label="Access variable R|<local>/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|<local>/x|"];
|
||||
43 [label="Exit block"];
|
||||
43 [label="Enter block"];
|
||||
44 [label="Access variable R|<local>/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|<local>/y|"];
|
||||
48 [label="Function call: R|<local>/y|.<Ambiguity: inc, [kotlin/inc, kotlin/inc]>#()"];
|
||||
49 [label="Exit function test_2" style="filled" fillcolor=red];
|
||||
48 [label="Variable declaration: lval y: R|kotlin/Int?|"];
|
||||
49 [label="Access variable R|<local>/y|"];
|
||||
50 [label="Function call: R|<local>/y|.<Ambiguity: inc, [kotlin/inc, kotlin/inc]>#()"];
|
||||
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|<local>/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|<local>/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|<local>/x|"];
|
||||
66 [label="Function call: R|<local>/x|.R|kotlin/Int.inc|()"];
|
||||
67 [label="Exit function test_3" style="filled" fillcolor=red];
|
||||
67 [label="Access variable R|<local>/x|"];
|
||||
68 [label="Function call: R|<local>/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|<local>/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|<local>/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|<local>/x|"];
|
||||
84 [label="Function call: R|<local>/x|.R|kotlin/Int.inc|()"];
|
||||
85 [label="Exit function test_4" style="filled" fillcolor=red];
|
||||
85 [label="Access variable R|<local>/x|"];
|
||||
86 [label="Function call: R|<local>/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|<local>/b|"];
|
||||
90 [label="Exit loop condition"];
|
||||
90 [label="Enter loop condition"];
|
||||
91 [label="Access variable R|<local>/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|<local>/b|"];
|
||||
96 [label="Exit when branch condition"];
|
||||
96 [label="Enter when branch condition "];
|
||||
97 [label="Access variable R|<local>/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|<local>/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|<local>/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|<local>/block|.R|FakeOverride<kotlin/Function0.invoke: R|kotlin/Unit|>|()"];
|
||||
111 [label="Exit function run" style="filled" fillcolor=red];
|
||||
111 [label="Enter function run" style="filled" fillcolor=red];
|
||||
112 [label="Function call: R|<local>/block|.R|FakeOverride<kotlin/Function0.invoke: R|kotlin/Unit|>|()"];
|
||||
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|(<L> = run@fun <anonymous>(): R|kotlin/Unit| <kind=UNKNOWN> {
|
||||
119 [label="Function call: R|/run|(<L> = run@fun <anonymous>(): R|kotlin/Unit| <kind=UNKNOWN> {
|
||||
^@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};
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ fun test_1(x: Int?) {
|
||||
x
|
||||
}
|
||||
y.inc()
|
||||
x.inc()
|
||||
}
|
||||
|
||||
fun test_2(x: Int?) {
|
||||
|
||||
@@ -10,6 +10,7 @@ FILE: jumps.kt
|
||||
}
|
||||
|
||||
R|<local>/y|.R|kotlin/Int.inc|()
|
||||
R|<local>/x|.R|kotlin/Int.inc|()
|
||||
}
|
||||
public final fun test_2(x: R|kotlin/Int?|): R|kotlin/Unit| {
|
||||
lval y: R|kotlin/Int?| = when () {
|
||||
|
||||
+391
-154
@@ -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 <init>" style="filled" fillcolor=red];
|
||||
1 [label="Exit function <init>" 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|<local>/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|<local>/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|<local>/a|"];
|
||||
24 [label="Function call: R|<local>/a|.<Inapplicable(WRONG_RECEIVER): [/A.bar]>#()"];
|
||||
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|<local>/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|<local>/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|<local>/a|"];
|
||||
30 [label="Function call: R|<local>/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|<local>/a|"];
|
||||
31 [label="Enter safe call"];
|
||||
32 [label="Function call: R|<local>/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|<local>/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|<local>/a|"];
|
||||
45 [label="Function call: R|<local>/a|.<Inapplicable(WRONG_RECEIVER): [/A.bar]>#()"];
|
||||
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|<local>/a|"];
|
||||
37 [label="Enter safe call"];
|
||||
38 [label="Function call: R|<local>/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|<local>/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|<local>/a|"];
|
||||
51 [label="Function call: R|<local>/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|<local>/x|"];
|
||||
53 [label="Type operator: x as? A"];
|
||||
54 [label="Variable declaration: lval <elvis>: 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|<local>/<elvis>|"];
|
||||
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|<local>/a|"];
|
||||
75 [label="Function call: R|<local>/a|.R|/A.foo|()"];
|
||||
76 [label="Access variable R|<local>/x|"];
|
||||
77 [label="Function call: R|<local>/x|.<Unresolved name: foo>#()"];
|
||||
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|<local>/x|"];
|
||||
59 [label="Type operator: x as? A"];
|
||||
60 [label="Variable declaration: lval <elvis>: 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|<local>/<elvis>|"];
|
||||
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|<local>/a|"];
|
||||
81 [label="Function call: R|<local>/a|.R|/A.foo|()"];
|
||||
82 [label="Access variable R|<local>/x|"];
|
||||
83 [label="Function call: R|<local>/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|<local>/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|<local>/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|<local>/a|"];
|
||||
109 [label="Function call: R|<local>/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|<local>/a|"];
|
||||
116 [label="Enter safe call"];
|
||||
117 [label="Function call: R|<local>/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|<local>/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|<local>/a|"];
|
||||
130 [label="Function call: R|<local>/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|<local>/x|"];
|
||||
138 [label="Type operator: x as? B"];
|
||||
139 [label="Variable declaration: lval <elvis>: 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|<local>/<elvis>|"];
|
||||
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|<local>/a|"];
|
||||
160 [label="Function call: R|<local>/a|.R|/B.foo|()"];
|
||||
161 [label="Access variable R|<local>/x|"];
|
||||
162 [label="Function call: R|<local>/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};
|
||||
|
||||
}
|
||||
|
||||
+40
-10
@@ -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.<!INAPPLICABLE_CANDIDATE!>bar<!>()
|
||||
a.bar() // Should be OK
|
||||
}
|
||||
}
|
||||
|
||||
fun test_2(a: A?) {
|
||||
fun test_2(a: B?) {
|
||||
val x = a?.foo()
|
||||
if (x != null) {
|
||||
a.<!INAPPLICABLE_CANDIDATE!>bar<!>()
|
||||
a.bar() // Should be OK
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
fun test_3(x: Any?) {
|
||||
val a = x as? A ?: return
|
||||
a.foo()
|
||||
x.<!UNRESOLVED_REFERENCE!>foo<!>()
|
||||
val a = x as? B ?: return
|
||||
a.foo() // Should be OK
|
||||
x.foo() // Should be OK
|
||||
}
|
||||
+54
-7
@@ -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<R|kotlin/Any|>()
|
||||
}
|
||||
|
||||
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|<local>/a|?.R|/A.x|
|
||||
when () {
|
||||
!=(R|<local>/x|, Null(null)) -> {
|
||||
R|<local>/a|.<Inapplicable(WRONG_RECEIVER): [/A.bar]>#()
|
||||
R|<local>/a|.R|/A.bar|()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +28,7 @@ FILE: anotherBoundSmartcasts.kt
|
||||
lval x: R|kotlin/Int?| = R|<local>/a|?.R|/A.foo|()
|
||||
when () {
|
||||
!=(R|<local>/x|, Null(null)) -> {
|
||||
R|<local>/a|.<Inapplicable(WRONG_RECEIVER): [/A.bar]>#()
|
||||
R|<local>/a|.R|/A.bar|()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,5 +44,45 @@ FILE: anotherBoundSmartcasts.kt
|
||||
}
|
||||
|
||||
R|<local>/a|.R|/A.foo|()
|
||||
R|<local>/x|.<Unresolved name: foo>#()
|
||||
R|<local>/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|<local>/a|?.R|/B.x|
|
||||
when () {
|
||||
!=(R|<local>/x|, Null(null)) -> {
|
||||
R|<local>/a|.R|/B.bar|()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public final fun test_2(a: R|B?|): R|kotlin/Unit| {
|
||||
lval x: R|kotlin/Int?| = R|<local>/a|?.R|/B.foo|()
|
||||
when () {
|
||||
!=(R|<local>/x|, Null(null)) -> {
|
||||
R|<local>/a|.R|/B.bar|()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public final fun test_3(x: R|kotlin/Any?|): R|kotlin/Unit| {
|
||||
lval a: R|B| = when (lval <elvis>: R|B?| = (R|<local>/x| as? R|B|)) {
|
||||
==($subj$, Null(null)) -> {
|
||||
^test_3 Unit
|
||||
}
|
||||
else -> {
|
||||
R|<local>/<elvis>|
|
||||
}
|
||||
}
|
||||
|
||||
R|<local>/a|.R|/B.foo|()
|
||||
R|<local>/x|.R|/B.foo|()
|
||||
}
|
||||
|
||||
@@ -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|<local>/x|"];
|
||||
167 [label="Type operator: x !is A"];
|
||||
168 [label="Function call: (R|<local>/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|<local>/x|"];
|
||||
174 [label="Function call: R|<local>/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};
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -74,3 +74,11 @@ FILE: booleanOperators.kt
|
||||
}
|
||||
|
||||
}
|
||||
public final fun test_8(x: R|kotlin/Any|): R|kotlin/Unit| {
|
||||
when () {
|
||||
(R|<local>/x| !is R|A|).R|kotlin/Boolean.not|() -> {
|
||||
R|<local>/x|.R|/A.foo|()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -166,13 +166,15 @@ digraph boundSmartcasts_kt {
|
||||
color=blue
|
||||
67 [label="Enter block"];
|
||||
68 [label="Access variable R|<local>/z|"];
|
||||
69 [label="Function call: R|<local>/z|.<Unresolved name: bar>#()"];
|
||||
70 [label="Exit block"];
|
||||
69 [label="Function call: R|<local>/z|.<Unresolved name: foo>#()"];
|
||||
70 [label="Access variable R|<local>/z|"];
|
||||
71 [label="Function call: R|<local>/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|<local>/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|<local>/x|"];
|
||||
80 [label="Function call: R|<local>/x|.R|kotlin/Int.inc|()"];
|
||||
81 [label="Access variable R|<local>/y|"];
|
||||
82 [label="Assignmenet: R|<local>/x|"];
|
||||
83 [label="Access variable R|<local>/x|"];
|
||||
84 [label="Function call: R|<local>/x|.R|kotlin/Int.inc|()"];
|
||||
80 [label="Type operator: x as Int"];
|
||||
81 [label="Access variable R|<local>/x|"];
|
||||
82 [label="Function call: R|<local>/x|.R|kotlin/Int.inc|()"];
|
||||
83 [label="Access variable R|<local>/y|"];
|
||||
84 [label="Assignmenet: R|<local>/x|"];
|
||||
85 [label="Access variable R|<local>/x|"];
|
||||
86 [label="Function call: R|<local>/x|.<Ambiguity: inc, [kotlin/inc, kotlin/inc]>#()"];
|
||||
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|<local>/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|<local>/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|<local>/x|"];
|
||||
94 [label="Function call: R|<local>/x|.<Unresolved name: foo>#()"];
|
||||
95 [label="Access variable R|<local>/y|"];
|
||||
96 [label="Function call: R|<local>/y|.R|/A.foo|()"];
|
||||
97 [label="Exit block"];
|
||||
94 [label="Enter block"];
|
||||
95 [label="Access variable R|<local>/x|"];
|
||||
96 [label="Function call: R|<local>/x|.R|/A.foo|()"];
|
||||
97 [label="Access variable R|<local>/y|"];
|
||||
98 [label="Function call: R|<local>/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};
|
||||
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@ fun test_3(x: Any, y: Any) {
|
||||
}
|
||||
z = y
|
||||
if (y is B) {
|
||||
z.<!UNRESOLVED_REFERENCE!>bar<!>()
|
||||
z.<!UNRESOLVED_REFERENCE!>foo<!>()
|
||||
z.bar()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,9 +39,9 @@ fun test_4(y: Any) {
|
||||
x as Int
|
||||
x.inc()
|
||||
x = y
|
||||
x.inc()
|
||||
x.<!AMBIGUITY!>inc<!>()
|
||||
if (y is A) {
|
||||
x.<!UNRESOLVED_REFERENCE!>foo<!>()
|
||||
x.foo()
|
||||
y.foo()
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,8 @@ FILE: boundSmartcasts.kt
|
||||
R|<local>/z| = R|<local>/y|
|
||||
when () {
|
||||
(R|<local>/y| is R|B|) -> {
|
||||
R|<local>/z|.<Unresolved name: bar>#()
|
||||
R|<local>/z|.<Unresolved name: foo>#()
|
||||
R|<local>/z|.R|/B.bar|()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,10 +49,10 @@ FILE: boundSmartcasts.kt
|
||||
(R|<local>/x| as R|kotlin/Int|)
|
||||
R|<local>/x|.R|kotlin/Int.inc|()
|
||||
R|<local>/x| = R|<local>/y|
|
||||
R|<local>/x|.R|kotlin/Int.inc|()
|
||||
R|<local>/x|.<Ambiguity: inc, [kotlin/inc, kotlin/inc]>#()
|
||||
when () {
|
||||
(R|<local>/y| is R|A|) -> {
|
||||
R|<local>/x|.<Unresolved name: foo>#()
|
||||
R|<local>/x|.R|/A.foo|()
|
||||
R|<local>/y|.R|/A.foo|()
|
||||
}
|
||||
}
|
||||
|
||||
+115
-133
@@ -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|<local>/d1|"];
|
||||
60 [label="Access variable R|/D.any|"];
|
||||
61 [label="Variable declaration: lval <elvis>: 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|<local>/<elvis>|"];
|
||||
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|<local>/d2|"];
|
||||
83 [label="Access variable R|/D.any|"];
|
||||
84 [label="Variable declaration: lval <elvis>: 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|<local>/<elvis>|"];
|
||||
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|<local>/a|"];
|
||||
105 [label="Type operator: a as A"];
|
||||
106 [label="Access variable R|<local>/a|"];
|
||||
107 [label="Function call: R|<local>/a|.R|/A.foo|()"];
|
||||
108 [label="Access variable R|<local>/b|"];
|
||||
109 [label="Type operator: b as B"];
|
||||
110 [label="Access variable R|<local>/b|"];
|
||||
111 [label="Function call: R|<local>/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|<local>/d|"];
|
||||
62 [label="Access variable R|/D.any|"];
|
||||
63 [label="Variable declaration: lval <elvis>: 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|<local>/<elvis>|"];
|
||||
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|<local>/a|"];
|
||||
84 [label="Function call: R|<local>/a|.R|/baz|()"];
|
||||
85 [label="Access variable R|<local>/d|"];
|
||||
86 [label="Access variable R|/D.any|"];
|
||||
87 [label="Function call: R|<local>/d|.R|/D.any|.R|/baz|()"];
|
||||
88 [label="Access variable R|<local>/a|"];
|
||||
89 [label="Type operator: a as A"];
|
||||
90 [label="Access variable R|<local>/a|"];
|
||||
91 [label="Function call: R|<local>/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|<local>/d1|"];
|
||||
95 [label="Access variable R|/D.any|"];
|
||||
96 [label="Variable declaration: lval a: R|kotlin/Any?|"];
|
||||
97 [label="Access variable R|<local>/a|"];
|
||||
98 [label="Type operator: a as A"];
|
||||
99 [label="Access variable R|<local>/a|"];
|
||||
100 [label="Function call: R|<local>/a|.R|/A.foo|()"];
|
||||
101 [label="Access variable R|<local>/d1|"];
|
||||
102 [label="Access variable R|/D.any|"];
|
||||
103 [label="Function call: R|<local>/d1|.R|/D.any|.R|/A.foo|()"];
|
||||
104 [label="Access variable R|<local>/d1|"];
|
||||
105 [label="Access variable R|/D.any|"];
|
||||
106 [label="Function call: R|<local>/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|<local>/d1|"];
|
||||
108 [label="Enter function test_4" style="filled" fillcolor=red];
|
||||
109 [label="Access variable R|<local>/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|<local>/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|<local>/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|<local>/a|"];
|
||||
125 [label="Type operator: a as A"];
|
||||
126 [label="Access variable R|<local>/a|"];
|
||||
127 [label="Function call: R|<local>/a|.R|/A.foo|()"];
|
||||
128 [label="Access variable R|<local>/b|"];
|
||||
129 [label="Type operator: b as B"];
|
||||
130 [label="Access variable R|<local>/b|"];
|
||||
131 [label="Function call: R|<local>/b|.<Ambiguity: foo, [/A.foo, /B.foo]>#()"];
|
||||
132 [label="Exit function test_3" style="filled" fillcolor=red];
|
||||
118 [label="Variable declaration: lval b: R|kotlin/Any?|"];
|
||||
119 [label="Access variable R|<local>/a|"];
|
||||
120 [label="Type operator: a as A"];
|
||||
121 [label="Access variable R|<local>/a|"];
|
||||
122 [label="Function call: R|<local>/a|.R|/A.foo|()"];
|
||||
123 [label="Access variable R|<local>/b|"];
|
||||
124 [label="Type operator: b as B"];
|
||||
125 [label="Access variable R|<local>/b|"];
|
||||
126 [label="Function call: R|<local>/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};
|
||||
|
||||
}
|
||||
|
||||
@@ -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.<!AMBIGUITY!>foo<!>() // should be OK
|
||||
b.foo() // should be OK
|
||||
}
|
||||
|
||||
@@ -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 <elvis>: R|kotlin/Any?| = R|<local>/d1|.R|/D.any|) {
|
||||
==($subj$, Null(null)) -> {
|
||||
^test_2 Unit
|
||||
}
|
||||
else -> {
|
||||
R|<local>/<elvis>|
|
||||
}
|
||||
}
|
||||
|
||||
lval b: R|kotlin/Any| = when (lval <elvis>: R|kotlin/Any| = R|<local>/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 <elvis>: R|kotlin/Any?| = R|<local>/d|.R|/D.any|) {
|
||||
==($subj$, Null(null)) -> {
|
||||
^test_2 Unit
|
||||
}
|
||||
@@ -57,16 +50,23 @@ FILE: notBoundSmartcasts.kt
|
||||
}
|
||||
}
|
||||
|
||||
R|<local>/a|.R|/baz|()
|
||||
R|<local>/d|.R|/D.any|.R|/baz|()
|
||||
(R|<local>/a| as R|A|)
|
||||
R|<local>/a|.R|/A.foo|()
|
||||
(R|<local>/b| as R|B|)
|
||||
R|<local>/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|<local>/d1|.R|/D.any|
|
||||
(R|<local>/a| as R|A|)
|
||||
R|<local>/a|.R|/A.foo|()
|
||||
R|<local>/d1|.R|/D.any|.R|/A.foo|()
|
||||
R|<local>/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|<local>/d1|?.R|/D.any|
|
||||
lval b: R|kotlin/Any?| = R|<local>/d2|?.R|/D.any|
|
||||
(R|<local>/a| as R|A|)
|
||||
R|<local>/a|.R|/A.foo|()
|
||||
(R|<local>/b| as R|B|)
|
||||
R|<local>/b|.<Ambiguity: foo, [/A.foo, /B.foo]>#()
|
||||
R|<local>/b|.R|/B.foo|()
|
||||
}
|
||||
|
||||
@@ -1153,11 +1153,11 @@ digraph nullability_kt {
|
||||
434 [label="Access variable R|/QImpl.data|"];
|
||||
435 [label="Access variable R|<local>/q2|"];
|
||||
436 [label="Access variable R|/QImpl.data|"];
|
||||
437 [label="Access variable R|/MyData.s|"];
|
||||
437 [label="Access variable <Inapplicable(WRONG_RECEIVER): [/MyData.s]>#"];
|
||||
438 [label="Access variable R|<local>/q2|"];
|
||||
439 [label="Access variable R|/QImpl.data|"];
|
||||
440 [label="Access variable R|/MyData.s|"];
|
||||
441 [label="Function call: R|<local>/q2|.R|/QImpl.data|.R|/MyData.s|.R|kotlin/Int.inc|()"];
|
||||
440 [label="Access variable <Inapplicable(WRONG_RECEIVER): [/MyData.s]>#"];
|
||||
441 [label="Function call: R|<local>/q2|.R|/QImpl.data|.<Inapplicable(WRONG_RECEIVER): [/MyData.s]>#.<Ambiguity: inc, [kotlin/inc, kotlin/inc]>#()"];
|
||||
subgraph cluster_91 {
|
||||
color=blue
|
||||
442 [label="Enter when"];
|
||||
|
||||
@@ -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.<!INAPPLICABLE_CANDIDATE!>s<!> // should be bad
|
||||
q2.data.<!INAPPLICABLE_CANDIDATE!>s<!>.<!AMBIGUITY!>inc<!>() // should be bad
|
||||
|
||||
if (q2.data != null) {
|
||||
q2.data.s
|
||||
|
||||
@@ -215,8 +215,8 @@ FILE: nullability.kt
|
||||
R|<local>/q|.R|/QImpl.data|.R|/MyData.s|
|
||||
R|<local>/q|.R|/QImpl.data|.R|/MyData.s|.R|kotlin/Int.inc|()
|
||||
R|<local>/q2|.R|/QImpl.data|
|
||||
R|<local>/q2|.R|/QImpl.data|.R|/MyData.s|
|
||||
R|<local>/q2|.R|/QImpl.data|.R|/MyData.s|.R|kotlin/Int.inc|()
|
||||
R|<local>/q2|.R|/QImpl.data|.<Inapplicable(WRONG_RECEIVER): [/MyData.s]>#
|
||||
R|<local>/q2|.R|/QImpl.data|.<Inapplicable(WRONG_RECEIVER): [/MyData.s]>#.<Ambiguity: inc, [kotlin/inc, kotlin/inc]>#()
|
||||
when () {
|
||||
!=(R|<local>/q2|.R|/QImpl.data|, Null(null)) -> {
|
||||
R|<local>/q2|.R|/QImpl.data|.R|/MyData.s|
|
||||
|
||||
+4
-7
@@ -88,9 +88,8 @@ digraph smartcastAfterReassignment_kt {
|
||||
35 [label="Const: Null(null)"];
|
||||
36 [label="Assignmenet: R|<local>/x|"];
|
||||
37 [label="Access variable R|<local>/x|"];
|
||||
38 [label="Stub" style="filled" fillcolor=gray];
|
||||
39 [label="Access variable <Unresolved name: length>#" style="filled" fillcolor=gray];
|
||||
40 [label="Exit function test_3" style="filled" fillcolor=red];
|
||||
38 [label="Access variable <Unresolved name: length>#"];
|
||||
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};
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user