FIR DFA: do not generate nodes (and flows) for contracts on calls
The code was already duplicated between FirDataFlowAnalyzer and FirReturnsImpliesAnalyzer, so might as well use the latter to slightly speed up the former.
This commit is contained in:
@@ -28,7 +28,6 @@ abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceCont
|
||||
originalVariable: DataFlowVariable,
|
||||
newVariable: DataFlowVariable,
|
||||
shouldRemoveOriginalStatements: Boolean = originalVariable.isSynthetic(),
|
||||
filter: (Implication) -> Boolean = { true },
|
||||
transform: (Implication) -> Implication? = { it },
|
||||
)
|
||||
|
||||
|
||||
+1
-2
@@ -237,12 +237,11 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
|
||||
originalVariable: DataFlowVariable,
|
||||
newVariable: DataFlowVariable,
|
||||
shouldRemoveOriginalStatements: Boolean,
|
||||
filter: (Implication) -> Boolean,
|
||||
transform: (Implication) -> Implication?
|
||||
) {
|
||||
with(flow) {
|
||||
val statements = logicStatements[originalVariable]?.takeIf { it.isNotEmpty() } ?: return
|
||||
val newStatements = statements.filter(filter).mapNotNull {
|
||||
val newStatements = statements.mapNotNull {
|
||||
val newStatement = OperationStatement(newVariable, it.condition.operation) implies it.effect
|
||||
transform(newStatement)
|
||||
}.toPersistentList()
|
||||
|
||||
@@ -761,19 +761,6 @@ class VariableAssignmentNode(owner: ControlFlowGraph, override val fir: FirVaria
|
||||
}
|
||||
}
|
||||
|
||||
class EnterContractNode(owner: ControlFlowGraph, override val fir: FirQualifiedAccess, level: Int, id: Int) : CFGNode<FirQualifiedAccess>(owner, level, id),
|
||||
EnterNodeMarker {
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitEnterContractNode(this, data)
|
||||
}
|
||||
}
|
||||
class ExitContractNode(owner: ControlFlowGraph, override val fir: FirQualifiedAccess, level: Int, id: Int) : CFGNode<FirQualifiedAccess>(owner, level, id),
|
||||
ExitNodeMarker {
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitExitContractNode(this, data)
|
||||
}
|
||||
}
|
||||
|
||||
class EnterSafeCallNode(owner: ControlFlowGraph, override val fir: FirSafeCallExpression, level: Int, id: Int) : CFGNode<FirSafeCallExpression>(owner, level, id) {
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitEnterSafeCallNode(this, data)
|
||||
|
||||
@@ -92,9 +92,6 @@ fun CFGNode<*>.render(): String =
|
||||
is AnnotationEnterNode -> "Enter annotation"
|
||||
is AnnotationExitNode -> "Exit annotation"
|
||||
|
||||
is EnterContractNode -> "Enter contract"
|
||||
is ExitContractNode -> "Exit contract"
|
||||
|
||||
is EnterSafeCallNode -> "Enter safe call"
|
||||
is ExitSafeCallNode -> "Exit safe call"
|
||||
|
||||
|
||||
-8
@@ -339,14 +339,6 @@ abstract class ControlFlowGraphVisitor<out R, in D> {
|
||||
return visitNode(node, data)
|
||||
}
|
||||
|
||||
open fun visitEnterContractNode(node: EnterContractNode, data: D): R {
|
||||
return visitNode(node, data)
|
||||
}
|
||||
|
||||
open fun visitExitContractNode(node: ExitContractNode, data: D): R {
|
||||
return visitNode(node, data)
|
||||
}
|
||||
|
||||
open fun visitEnterSafeCallNode(node: EnterSafeCallNode, data: D): R {
|
||||
return visitNode(node, data)
|
||||
}
|
||||
|
||||
-16
@@ -273,14 +273,6 @@ abstract class ControlFlowGraphVisitorVoid : ControlFlowGraphVisitor<Unit, Nothi
|
||||
visitNode(node)
|
||||
}
|
||||
|
||||
open fun visitEnterContractNode(node: EnterContractNode) {
|
||||
visitNode(node)
|
||||
}
|
||||
|
||||
open fun visitExitContractNode(node: ExitContractNode) {
|
||||
visitNode(node)
|
||||
}
|
||||
|
||||
open fun visitEnterSafeCallNode(node: EnterSafeCallNode) {
|
||||
visitNode(node)
|
||||
}
|
||||
@@ -570,14 +562,6 @@ abstract class ControlFlowGraphVisitorVoid : ControlFlowGraphVisitor<Unit, Nothi
|
||||
visitVariableAssignmentNode(node)
|
||||
}
|
||||
|
||||
final override fun visitEnterContractNode(node: EnterContractNode, data: Nothing?) {
|
||||
visitEnterContractNode(node)
|
||||
}
|
||||
|
||||
final override fun visitExitContractNode(node: ExitContractNode, data: Nothing?) {
|
||||
visitExitContractNode(node)
|
||||
}
|
||||
|
||||
final override fun visitEnterSafeCallNode(node: EnterSafeCallNode, data: Nothing?) {
|
||||
visitEnterSafeCallNode(node)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2010-2022 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 org.jetbrains.kotlin.fir.contracts.description.*
|
||||
import org.jetbrains.kotlin.fir.expressions.LogicOperationKind
|
||||
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
|
||||
fun ConeConstantReference.toOperation(): Operation? = when (this) {
|
||||
ConeConstantReference.WILDCARD -> null
|
||||
ConeConstantReference.NULL -> Operation.EqNull
|
||||
ConeConstantReference.NOT_NULL -> Operation.NotEqNull
|
||||
ConeBooleanConstantReference.TRUE -> Operation.EqTrue
|
||||
ConeBooleanConstantReference.FALSE -> Operation.EqFalse
|
||||
else -> throw IllegalArgumentException("$this can not be transformed to Operation")
|
||||
}
|
||||
|
||||
// Returns `null` if the statement is always false.
|
||||
fun <F : Flow> LogicSystem<F>.approveContractStatement(
|
||||
flow: F,
|
||||
statement: ConeBooleanExpression,
|
||||
arguments: Array<out DataFlowVariable?>, // 0 = receiver (null if doesn't exist)
|
||||
substitutor: ConeSubstitutor?,
|
||||
removeApprovedOrImpossible: Boolean = false,
|
||||
): TypeStatements? {
|
||||
fun OperationStatement.approve() =
|
||||
approveOperationStatement(flow, this, removeApprovedOrImpossible)
|
||||
|
||||
fun DataFlowVariable.processEqNull(isEq: Boolean): TypeStatements =
|
||||
OperationStatement(this, if (isEq) Operation.EqNull else Operation.NotEqNull).approve()
|
||||
|
||||
fun ConeBooleanExpression.visit(inverted: Boolean): TypeStatements? = when (this) {
|
||||
is ConeBooleanConstantReference ->
|
||||
if (inverted == (this == ConeBooleanConstantReference.TRUE)) null else mapOf()
|
||||
is ConeLogicalNot -> arg.visit(inverted = !inverted)
|
||||
is ConeIsInstancePredicate ->
|
||||
arguments.getOrNull(arg.parameterIndex + 1)?.let {
|
||||
val isType = inverted == isNegated
|
||||
val substitutedType = substitutor?.substituteOrNull(type) ?: type
|
||||
when {
|
||||
substitutedType.isAny -> it.processEqNull(!isType)
|
||||
substitutedType.isNullableNothing -> it.processEqNull(isType)
|
||||
else -> {
|
||||
// x is (T & Any) => x != null
|
||||
// TODO? (KT-22996) x !is T? => x != null: change `&&` to `==`
|
||||
val fromNullability = if (isType && !type.canBeNull) it.processEqNull(false) else mapOf()
|
||||
if (isType && it is RealVariable) {
|
||||
andForTypeStatements(fromNullability, mapOf(it to (it typeEq substitutedType)))
|
||||
} else {
|
||||
fromNullability
|
||||
}
|
||||
}
|
||||
}
|
||||
} ?: mapOf()
|
||||
is ConeIsNullPredicate ->
|
||||
arguments.getOrNull(arg.parameterIndex + 1)?.processEqNull(inverted == isNegated) ?: mapOf()
|
||||
is ConeBooleanValueParameterReference ->
|
||||
arguments.getOrNull(parameterIndex + 1)?.let {
|
||||
OperationStatement(it, if (inverted) Operation.EqFalse else Operation.EqTrue).approve()
|
||||
} ?: mapOf()
|
||||
is ConeBinaryLogicExpression -> {
|
||||
val a = left.visit(inverted)
|
||||
val b = right.visit(inverted)
|
||||
val isAnd = inverted != (kind == LogicOperationKind.AND)
|
||||
when {
|
||||
a == null -> b.takeIf { !isAnd } // false || b == b; false && b = false
|
||||
b == null -> a.takeIf { !isAnd } // a || false == a; a && false = false
|
||||
isAnd -> andForTypeStatements(a, b)
|
||||
else -> orForTypeStatements(a, b)
|
||||
}
|
||||
}
|
||||
else -> mapOf()
|
||||
}
|
||||
|
||||
return statement.visit(inverted = false)
|
||||
}
|
||||
@@ -7,8 +7,6 @@ package org.jetbrains.kotlin.fir.resolve.dfa
|
||||
|
||||
import kotlinx.collections.immutable.PersistentMap
|
||||
import org.jetbrains.kotlin.fir.FirElement
|
||||
import org.jetbrains.kotlin.fir.contracts.description.ConeBooleanConstantReference
|
||||
import org.jetbrains.kotlin.fir.contracts.description.ConeConstantReference
|
||||
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
|
||||
import org.jetbrains.kotlin.fir.expressions.*
|
||||
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
||||
@@ -16,12 +14,8 @@ import org.jetbrains.kotlin.fir.references.FirThisReference
|
||||
import org.jetbrains.kotlin.fir.references.impl.FirSimpleNamedReference
|
||||
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirSyntheticPropertySymbol
|
||||
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
||||
import org.jetbrains.kotlin.fir.types.ConeTypeContext
|
||||
import org.jetbrains.kotlin.fir.types.coneType
|
||||
import org.jetbrains.kotlin.name.StandardClassIds
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
@@ -58,9 +52,9 @@ internal inline fun <K, V> PersistentMap<K, V>.put(
|
||||
}
|
||||
}
|
||||
|
||||
fun Set<ConeKotlinType>?.intersectWith(context: ConeTypeContext, originalType: ConeKotlinType): ConeKotlinType =
|
||||
if (!isNullOrEmpty()) {
|
||||
context.intersectTypes(toMutableList().also { it += originalType })
|
||||
fun TypeStatement?.smartCastedType(context: ConeTypeContext, originalType: ConeKotlinType): ConeKotlinType =
|
||||
if (this != null && exactType.isNotEmpty()) {
|
||||
context.intersectTypes(exactType.toMutableList().also { it += originalType })
|
||||
} else {
|
||||
originalType
|
||||
}
|
||||
@@ -74,19 +68,6 @@ fun FirOperation.isEq(): Boolean {
|
||||
}
|
||||
}
|
||||
|
||||
fun FirFunctionCall.isBooleanNot(): Boolean {
|
||||
val symbol = (calleeReference as? FirResolvedNamedReference)?.resolvedSymbol as? FirNamedFunctionSymbol ?: return false
|
||||
return symbol.callableId == StandardClassIds.Callables.not
|
||||
}
|
||||
|
||||
fun ConeConstantReference.toOperation(): Operation = when (this) {
|
||||
ConeConstantReference.NULL -> Operation.EqNull
|
||||
ConeConstantReference.NOT_NULL -> Operation.NotEqNull
|
||||
ConeBooleanConstantReference.TRUE -> Operation.EqTrue
|
||||
ConeBooleanConstantReference.FALSE -> Operation.EqFalse
|
||||
else -> throw IllegalArgumentException("$this can not be transformed to Operation")
|
||||
}
|
||||
|
||||
@DfaInternals
|
||||
val FirExpression.coneType: ConeKotlinType
|
||||
get() = typeRef.coneType
|
||||
|
||||
Reference in New Issue
Block a user