[FIR] Split :compiler:fir:resolve module into three different modules

Those modules are:
- :compiler:fir:providers, which contains Fir and Symbol providers,
    scopes, and different utilities used by them
- :compiler:fir:semantics, which contains different abstractions and
    entities which are used in resolution and in checkers
- :compiler:fir:resolve, which contains all stuff related to resolution
    and inference

There are two pros of this change:
1. It may increase gradle build, because it allows to compile :fir:resolve
  and :fir:checkers modules in parallel
2. Logic of working FIR (scopes, providers, DFA logic system, etc) is
  now separated from logic of resolution phases, so for example checkers,
  which are depend on scopes physically will not be able to run resolve
  in any way
This commit is contained in:
Dmitriy Novozhilov
2021-10-14 13:53:40 +03:00
parent 6995ee9334
commit bee44c6e0f
219 changed files with 1836 additions and 1581 deletions
+17
View File
@@ -0,0 +1,17 @@
plugins {
kotlin("jvm")
id("jps-compatible")
}
dependencies {
api(project(":compiler:fir:providers"))
implementation(project(":core:util.runtime"))
compileOnly(project(":kotlin-reflect-api"))
compileOnly(intellijCoreDep()) { includeJars("guava", rootProject = rootProject) }
}
sourceSets {
"main" { projectDefault() }
"test" { none() }
}
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2021 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
import org.jetbrains.kotlin.descriptors.EffectiveVisibility
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
fun Visibility.toEffectiveVisibility(
ownerSymbol: FirClassLikeSymbol<*>?,
forClass: Boolean = false,
checkPublishedApi: Boolean = false
): EffectiveVisibility {
return toEffectiveVisibility(ownerSymbol?.toLookupTag(), forClass, checkPublishedApi)
}
fun Visibility.toEffectiveVisibility(
owner: ConeClassLikeLookupTag?,
forClass: Boolean = false,
ownerIsPublishedApi: Boolean = false
): EffectiveVisibility {
customEffectiveVisibility()?.let { return it }
return when (this.normalize()) {
Visibilities.PrivateToThis, Visibilities.InvisibleFake -> EffectiveVisibility.PrivateInClass
Visibilities.Private -> if (owner == null && forClass) EffectiveVisibility.PrivateInFile else EffectiveVisibility.PrivateInClass
Visibilities.Protected -> EffectiveVisibility.Protected(owner)
Visibilities.Internal -> when (ownerIsPublishedApi) {
true -> EffectiveVisibility.Public
false -> EffectiveVisibility.Internal
}
Visibilities.Public -> EffectiveVisibility.Public
Visibilities.Local -> EffectiveVisibility.Local
else -> error("Unknown visibility: $this")
}
}
@@ -0,0 +1,61 @@
/*
* Copyright 2010-2021 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
import org.jetbrains.kotlin.fir.resolve.calls.AbstractCallInfo
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.SmartList
abstract class FirLookupTrackerComponent : FirSessionComponent {
abstract fun recordLookup(name: Name, inScopes: List<String>, source: FirSourceElement?, fileSource: FirSourceElement?)
abstract fun recordLookup(name: Name, inScope: String, source: FirSourceElement?, fileSource: FirSourceElement?)
}
fun FirLookupTrackerComponent.recordCallLookup(callInfo: AbstractCallInfo, inType: ConeKotlinType) {
if (inType.classId?.isLocal == true) return
val scopes = SmartList(inType.render().replace('/', '.'))
if (inType.classId?.shortClassName?.asString() == "Companion") {
scopes.add(inType.classId!!.outerClassId!!.asString().replace('/', '.'))
}
recordLookup(callInfo.name, scopes, callInfo.callSite.source, callInfo.containingFile.source)
}
fun FirLookupTrackerComponent.recordCallLookup(callInfo: AbstractCallInfo, inScopes: List<String>) {
recordLookup(callInfo.name, inScopes, callInfo.callSite.source, callInfo.containingFile.source)
}
fun FirLookupTrackerComponent.recordTypeLookup(typeRef: FirTypeRef, inScopes: List<String>, fileSource: FirSourceElement?) {
if (typeRef is FirUserTypeRef) recordLookup(typeRef.qualifier.first().name, inScopes, typeRef.source, fileSource)
}
fun FirLookupTrackerComponent.recordTypeResolveAsLookup(typeRef: FirTypeRef, source: FirSourceElement?, fileSource: FirSourceElement?) {
if (typeRef !is FirResolvedTypeRef) return // TODO: check if this is the correct behavior
if (source == null && fileSource == null) return // TODO: investigate all cases
fun recordIfValid(type: ConeKotlinType) {
if (type is ConeKotlinErrorType) return // TODO: investigate whether some cases should be recorded, e.g. unresolved
type.classId?.let {
if (!it.isLocal) {
if (it.shortClassName.asString() != "Companion") {
recordLookup(it.shortClassName, it.packageFqName.asString(), source, fileSource)
} else {
recordLookup(it.outerClassId!!.shortClassName, it.outerClassId!!.packageFqName.asString(), source, fileSource)
}
}
}
type.typeArguments.forEach {
if (it is ConeKotlinType) recordIfValid(it)
}
}
recordIfValid(typeRef.type)
}
val FirSession.lookupTracker: FirLookupTrackerComponent? by FirSession.nullableSessionComponentAccessor()
@@ -0,0 +1,20 @@
/*
* Copyright 2010-2021 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
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.name.ClassId
abstract class FirNameConflictsTrackerComponent : FirSessionComponent {
abstract fun registerClassifierRedeclaration(
classId: ClassId,
newSymbol: FirClassLikeSymbol<*>, newSymbolFile: FirFile,
prevSymbol: FirClassLikeSymbol<*>, prevSymbolFile: FirFile?
)
}
val FirSession.nameConflictsTracker: FirNameConflictsTrackerComponent? by FirSession.nullableSessionComponentAccessor()
@@ -0,0 +1,38 @@
/*
* Copyright 2010-2021 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
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
import org.jetbrains.kotlin.fir.declarations.utils.visibility
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.scopes.ProcessorAction
import org.jetbrains.kotlin.fir.scopes.processOverriddenFunctions
import org.jetbrains.kotlin.fir.scopes.unsubstitutedScope
/**
* Returns the list of functions that overridden by given
*/
fun FirSimpleFunction.lowestVisibilityAmongOverrides(
containingClass: FirClass,
session: FirSession,
scopeSession: ScopeSession
): Visibility {
val firTypeScope = containingClass.unsubstitutedScope(session, scopeSession, withForcedTypeCalculator = false)
var visibility = visibility
// required; otherwise processOverriddenFunctions()
// will process nothing
firTypeScope.processFunctionsByName(symbol.fir.name) { }
firTypeScope.processOverriddenFunctions(symbol) {
visibility = it.fir.visibility
ProcessorAction.NEXT
}
return visibility
}
@@ -0,0 +1,12 @@
/*
* Copyright 2010-2020 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.declarations
import org.jetbrains.kotlin.metadata.deserialization.VersionRequirementTable
object FirVersionRequirementsTableKey : FirDeclarationDataKey()
var FirDeclaration.versionRequirementsTable: VersionRequirementTable? by FirDeclarationDataRegistry.data(FirVersionRequirementsTableKey)
@@ -0,0 +1,201 @@
/*
* Copyright 2010-2021 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.declarations
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import org.jetbrains.kotlin.fir.resolve.*
import org.jetbrains.kotlin.fir.resolve.calls.ImplicitDispatchReceiverValue
import org.jetbrains.kotlin.fir.resolve.calls.ImplicitExtensionReceiverValue
import org.jetbrains.kotlin.fir.resolve.calls.ImplicitReceiverValue
import org.jetbrains.kotlin.fir.scopes.FirScope
import org.jetbrains.kotlin.fir.scopes.impl.FirLocalScope
import org.jetbrains.kotlin.fir.scopes.impl.wrapNestedClassifierScopeWithSubstitutionForSuperType
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.addIfNotNull
fun SessionHolder.collectImplicitReceivers(
type: ConeKotlinType?,
owner: FirDeclaration
): ImplicitReceivers {
if (type == null) return ImplicitReceivers(null, emptyList())
val implicitCompanionValues = mutableListOf<ImplicitReceiverValue<*>>()
val implicitReceiverValue = when (owner) {
is FirClass -> {
val towerElementsForClass = collectTowerDataElementsForClass(owner, type)
implicitCompanionValues.addAll(towerElementsForClass.implicitCompanionValues)
towerElementsForClass.thisReceiver
}
is FirFunction -> {
ImplicitExtensionReceiverValue(owner.symbol, type, session, scopeSession)
}
is FirVariable -> {
ImplicitExtensionReceiverValue(owner.symbol, type, session, scopeSession)
}
else -> {
throw IllegalArgumentException("Incorrect label & receiver owner: ${owner.javaClass}")
}
}
return ImplicitReceivers(implicitReceiverValue, implicitCompanionValues)
}
data class ImplicitReceivers(
val implicitReceiverValue: ImplicitReceiverValue<*>?,
val implicitCompanionValues: List<ImplicitReceiverValue<*>>
)
fun SessionHolder.collectTowerDataElementsForClass(owner: FirClass, defaultType: ConeKotlinType): TowerElementsForClass {
val allImplicitCompanionValues = mutableListOf<ImplicitReceiverValue<*>>()
val companionObject = (owner as? FirRegularClass)?.companionObject
val companionReceiver = companionObject?.let { companion ->
ImplicitDispatchReceiverValue(
companion.symbol, session, scopeSession
)
}
allImplicitCompanionValues.addIfNotNull(companionReceiver)
val superClassesStaticsAndCompanionReceivers = mutableListOf<FirTowerDataElement>()
for (superType in lookupSuperTypes(owner, lookupInterfaces = false, deep = true, useSiteSession = session)) {
val expandedType = superType.fullyExpandedType(session)
val superClass = expandedType.lookupTag.toSymbol(session)?.fir as? FirRegularClass ?: continue
superClass.staticScope(this)
?.wrapNestedClassifierScopeWithSubstitutionForSuperType(expandedType, session)
?.asTowerDataElement(isLocal = false)
?.let(superClassesStaticsAndCompanionReceivers::add)
(superClass as? FirRegularClass)?.companionObject?.let { companion ->
val superCompanionReceiver = ImplicitDispatchReceiverValue(
companion.symbol, session, scopeSession
)
superClassesStaticsAndCompanionReceivers += superCompanionReceiver.asTowerDataElement()
allImplicitCompanionValues += superCompanionReceiver
}
}
val thisReceiver = ImplicitDispatchReceiverValue(owner.symbol, defaultType, session, scopeSession)
return TowerElementsForClass(
thisReceiver,
owner.staticScope(this),
companionReceiver,
companionObject?.staticScope(this),
superClassesStaticsAndCompanionReceivers.asReversed(),
allImplicitCompanionValues.asReversed()
)
}
class TowerElementsForClass(
val thisReceiver: ImplicitReceiverValue<*>,
val staticScope: FirScope?,
val companionReceiver: ImplicitReceiverValue<*>?,
val companionStaticScope: FirScope?,
// Ordered from inner scopes to outer scopes.
val superClassesStaticsAndCompanionReceivers: List<FirTowerDataElement>,
// Ordered from inner scopes to outer scopes.
val implicitCompanionValues: List<ImplicitReceiverValue<*>>
)
class FirTowerDataContext private constructor(
val towerDataElements: PersistentList<FirTowerDataElement>,
// These properties are effectively redundant, their content should be consistent with `towerDataElements`,
// i.e. implicitReceiverStack == towerDataElements.mapNotNull { it.receiver }
// i.e. localScopes == towerDataElements.mapNotNull { it.scope?.takeIf { it.isLocal } }
val implicitReceiverStack: PersistentImplicitReceiverStack,
val localScopes: FirLocalScopes,
val nonLocalTowerDataElements: PersistentList<FirTowerDataElement>
) {
constructor() : this(
persistentListOf(),
PersistentImplicitReceiverStack(),
persistentListOf(),
persistentListOf()
)
fun setLastLocalScope(newLastScope: FirLocalScope): FirTowerDataContext {
val oldLastScope = localScopes.last()
val indexOfLastLocalScope = towerDataElements.indexOfLast { it.scope === oldLastScope }
return FirTowerDataContext(
towerDataElements.set(indexOfLastLocalScope, newLastScope.asTowerDataElement(isLocal = true)),
implicitReceiverStack,
localScopes.set(localScopes.lastIndex, newLastScope),
nonLocalTowerDataElements
)
}
fun addNonLocalTowerDataElements(newElements: List<FirTowerDataElement>): FirTowerDataContext {
return FirTowerDataContext(
towerDataElements.addAll(newElements),
implicitReceiverStack.addAll(newElements.mapNotNull { it.implicitReceiver }),
localScopes,
nonLocalTowerDataElements.addAll(newElements)
)
}
fun addLocalScope(localScope: FirLocalScope): FirTowerDataContext {
return FirTowerDataContext(
towerDataElements.add(localScope.asTowerDataElement(isLocal = true)),
implicitReceiverStack,
localScopes.add(localScope),
nonLocalTowerDataElements
)
}
fun addReceiver(name: Name?, implicitReceiverValue: ImplicitReceiverValue<*>): FirTowerDataContext {
val element = implicitReceiverValue.asTowerDataElement()
return FirTowerDataContext(
towerDataElements.add(element),
implicitReceiverStack.add(name, implicitReceiverValue),
localScopes,
nonLocalTowerDataElements.add(element)
)
}
fun addNonLocalScopeIfNotNull(scope: FirScope?): FirTowerDataContext {
if (scope == null) return this
return addNonLocalScope(scope)
}
private fun addNonLocalScope(scope: FirScope): FirTowerDataContext {
val element = scope.asTowerDataElement(isLocal = false)
return FirTowerDataContext(
towerDataElements.add(element),
implicitReceiverStack,
localScopes,
nonLocalTowerDataElements.add(element)
)
}
fun createSnapshot(): FirTowerDataContext {
return FirTowerDataContext(
towerDataElements.map { FirTowerDataElement(it.scope, it.implicitReceiver?.createSnapshot(), it.isLocal) }.toPersistentList(),
implicitReceiverStack.createSnapshot(),
localScopes.toPersistentList(),
nonLocalTowerDataElements.map { FirTowerDataElement(it.scope, it.implicitReceiver?.createSnapshot(), it.isLocal) }
.toPersistentList()
)
}
}
class FirTowerDataElement(val scope: FirScope?, val implicitReceiver: ImplicitReceiverValue<*>?, val isLocal: Boolean)
fun ImplicitReceiverValue<*>.asTowerDataElement(): FirTowerDataElement =
FirTowerDataElement(scope = null, this, isLocal = false)
fun FirScope.asTowerDataElement(isLocal: Boolean): FirTowerDataElement =
FirTowerDataElement(this, implicitReceiver = null, isLocal)
private fun FirClass.staticScope(sessionHolder: SessionHolder) =
scopeProvider.getStaticScope(this, sessionHolder.session, sessionHolder.scopeSession)
typealias FirLocalScopes = PersistentList<FirLocalScope>
@@ -0,0 +1,42 @@
/*
* Copyright 2010-2021 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
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.resolve.providers.getSymbolByTypeRef
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.StandardClassIds
fun CallableId.isInvoke(): Boolean =
isKFunctionInvoke()
|| callableName.asString() == "invoke"
&& className?.asString()?.startsWith("Function") == true
&& packageName == StandardClassIds.BASE_KOTLIN_PACKAGE
fun CallableId.isKFunctionInvoke(): Boolean =
callableName.asString() == "invoke"
&& className?.asString()?.startsWith("KFunction") == true
&& packageName.asString() == "kotlin.reflect"
fun CallableId.isIteratorNext(): Boolean =
callableName.asString() == "next" && className?.asString()?.endsWith("Iterator") == true
&& packageName.asString() == "kotlin.collections"
fun CallableId.isIteratorHasNext(): Boolean =
callableName.asString() == "hasNext" && className?.asString()?.endsWith("Iterator") == true
&& packageName.asString() == "kotlin.collections"
fun CallableId.isIterator(): Boolean =
callableName.asString() == "iterator" && packageName.asString() in arrayOf("kotlin.collections", "kotlin.ranges")
fun FirAnnotation.fqName(session: FirSession): FqName? {
val symbol = session.symbolProvider.getSymbolByTypeRef<FirRegularClassSymbol>(annotationTypeRef) ?: return null
return symbol.classId.asSingleFqName()
}
@@ -0,0 +1,75 @@
/*
* Copyright 2010-2021 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
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.containingClassForLocalAttr
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.isLocal
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
import org.jetbrains.kotlin.fir.symbols.impl.LookupTagInternals
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.ClassId
fun FirClassLikeDeclaration.getContainingDeclaration(session: FirSession): FirClassLikeDeclaration? {
if (isLocal) {
@OptIn(LookupTagInternals::class)
return (this as? FirRegularClass)?.containingClassForLocalAttr?.toFirRegularClass(session)
} else {
val classId = symbol.classId
val parentId = classId.relativeClassName.parent()
if (!parentId.isRoot) {
val containingDeclarationId = ClassId(classId.packageFqName, parentId, false)
return session.symbolProvider.getClassLikeSymbolByClassId(containingDeclarationId)?.fir
}
}
return null
}
fun isValidTypeParameterFromOuterClass(
typeParameterSymbol: FirTypeParameterSymbol,
classDeclaration: FirRegularClass?,
session: FirSession
): Boolean {
if (classDeclaration == null) {
return true // Extra check is required because of classDeclaration will be resolved later
}
fun containsTypeParameter(currentClassDeclaration: FirRegularClass): Boolean {
if (currentClassDeclaration.typeParameters.any { it.symbol == typeParameterSymbol }) {
return true
}
for (superTypeRef in currentClassDeclaration.superTypeRefs) {
val superClassFir = superTypeRef.firClassLike(session)
if (superClassFir == null || superClassFir is FirRegularClass && containsTypeParameter(superClassFir)) {
return true
}
}
return false
}
return containsTypeParameter(classDeclaration)
}
fun FirTypeRef.firClassLike(session: FirSession): FirClassLikeDeclaration? {
val type = coneTypeSafe<ConeClassLikeType>() ?: return null
return type.lookupTag.toSymbol(session)?.fir
}
fun List<FirQualifierPart>.toTypeProjections(): Array<ConeTypeProjection> =
asReversed().flatMap { it.typeArgumentList.typeArguments.map { typeArgument -> typeArgument.toConeTypeProjection() } }.toTypedArray()
private object TypeAliasConstructorKey : FirDeclarationDataKey()
var FirConstructor.originalConstructorIfTypeAlias: FirConstructor? by FirDeclarationDataRegistry.data(TypeAliasConstructorKey)
val FirConstructorSymbol.isTypeAliasedConstructor: Boolean
get() = fir.originalConstructorIfTypeAlias != null
@@ -0,0 +1,17 @@
/*
* 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
import org.jetbrains.kotlin.fir.resolve.calls.ImplicitDispatchReceiverValue
import org.jetbrains.kotlin.fir.resolve.calls.ImplicitReceiverValue
abstract class ImplicitReceiverStack : Iterable<ImplicitReceiverValue<*>> {
abstract operator fun get(name: String?): ImplicitReceiverValue<*>?
abstract fun lastDispatchReceiver(): ImplicitDispatchReceiverValue?
abstract fun lastDispatchReceiver(lookupCondition: (ImplicitReceiverValue<*>) -> Boolean): ImplicitDispatchReceiverValue?
abstract fun receiversAsReversed(): List<ImplicitReceiverValue<*>>
}
@@ -0,0 +1,93 @@
/*
* Copyright 2010-2021 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
import kotlinx.collections.immutable.*
import org.jetbrains.kotlin.fir.resolve.calls.ImplicitDispatchReceiverValue
import org.jetbrains.kotlin.fir.resolve.calls.ImplicitReceiverValue
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.util.PersistentSetMultimap
import org.jetbrains.kotlin.name.Name
class PersistentImplicitReceiverStack private constructor(
private val stack: PersistentList<ImplicitReceiverValue<*>>,
// This multi-map holds indexes of the stack ^
private val indexesPerLabel: PersistentSetMultimap<Name, Int>,
private val indexesPerSymbol: PersistentMap<FirBasedSymbol<*>, Int>,
private val originalTypes: PersistentList<ConeKotlinType>,
) : ImplicitReceiverStack(), Iterable<ImplicitReceiverValue<*>> {
val size: Int get() = stack.size
constructor() : this(
persistentListOf(),
PersistentSetMultimap(),
persistentMapOf(),
persistentListOf(),
)
fun addAll(receivers: List<ImplicitReceiverValue<*>>): PersistentImplicitReceiverStack {
return receivers.fold(this) { acc, value -> acc.add(name = null, value) }
}
fun add(name: Name?, value: ImplicitReceiverValue<*>): PersistentImplicitReceiverStack {
val stack = stack.add(value)
val originalTypes = originalTypes.add(value.originalType)
val index = stack.size - 1
val indexesPerLabel = name?.let { indexesPerLabel.put(it, index) } ?: indexesPerLabel
val indexesPerSymbol = indexesPerSymbol.put(value.boundSymbol, index)
return PersistentImplicitReceiverStack(
stack,
indexesPerLabel,
indexesPerSymbol,
originalTypes
)
}
override operator fun get(name: String?): ImplicitReceiverValue<*>? {
if (name == null) return stack.lastOrNull()
return indexesPerLabel[Name.identifier(name)].lastOrNull()?.let { stack[it] }
}
override fun lastDispatchReceiver(): ImplicitDispatchReceiverValue? {
return stack.filterIsInstance<ImplicitDispatchReceiverValue>().lastOrNull()
}
override fun lastDispatchReceiver(lookupCondition: (ImplicitReceiverValue<*>) -> Boolean): ImplicitDispatchReceiverValue? {
return stack.filterIsInstance<ImplicitDispatchReceiverValue>().lastOrNull(lookupCondition)
}
override fun receiversAsReversed(): List<ImplicitReceiverValue<*>> = stack.asReversed()
override operator fun iterator(): Iterator<ImplicitReceiverValue<*>> {
return stack.iterator()
}
// These methods are only used at org.jetbrains.kotlin.fir.resolve.dfa.FirDataFlowAnalyzer.Companion.createFirDataFlowAnalyzer
// No need to be extracted to an interface
fun getReceiverIndex(symbol: FirBasedSymbol<*>): Int? = indexesPerSymbol[symbol]
fun getOriginalType(index: Int): ConeKotlinType {
return originalTypes[index]
}
// This method is only used from DFA and it's in some sense breaks persistence contracts of the data structure
// But it's ok since DFA handles everything properly yet, but still may be it should be rewritten somehow
fun replaceReceiverType(index: Int, type: ConeKotlinType) {
assert(index >= 0 && index < stack.size)
stack[index].replaceType(type)
}
fun createSnapshot(): PersistentImplicitReceiverStack {
return PersistentImplicitReceiverStack(
stack.map { it.createSnapshot() }.toPersistentList(),
indexesPerLabel,
indexesPerSymbol,
originalTypes
)
}
}
@@ -0,0 +1,13 @@
/*
* Copyright 2010-2021 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
import org.jetbrains.kotlin.fir.FirSession
interface SessionHolder {
val session: FirSession
val scopeSession: ScopeSession
}
@@ -0,0 +1,21 @@
/*
* Copyright 2010-2021 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.calls
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.expressions.FirArgumentList
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.name.Name
abstract class AbstractCallInfo {
abstract val callSite: FirElement
abstract val name: Name
abstract val containingFile: FirFile
abstract val isImplicitInvoke: Boolean
abstract val explicitReceiver: FirExpression?
abstract val argumentList: FirArgumentList
}
@@ -0,0 +1,22 @@
/*
* Copyright 2010-2021 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.calls
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.resolve.calls.inference.model.NewConstraintSystemImpl
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
import org.jetbrains.kotlin.resolve.calls.tower.CandidateApplicability
abstract class AbstractCandidate {
abstract val symbol: FirBasedSymbol<*>
abstract val dispatchReceiverValue: ReceiverValue?
abstract val extensionReceiverValue: ReceiverValue?
abstract val explicitReceiverKind: ExplicitReceiverKind
abstract val callInfo: AbstractCallInfo
abstract val diagnostics: List<ResolutionDiagnostic>
abstract val system: NewConstraintSystemImpl
abstract val applicability: CandidateApplicability
}
@@ -0,0 +1,108 @@
/*
* Copyright 2010-2020 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.calls
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.declarations.FirFunction
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirExpressionWithSmartcast
import org.jetbrains.kotlin.fir.expressions.FirNamedArgumentExpression
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.resolve.ForbiddenNamedArgumentsTarget
import org.jetbrains.kotlin.resolve.calls.tower.CandidateApplicability
import org.jetbrains.kotlin.resolve.calls.tower.CandidateApplicability.*
abstract class ResolutionDiagnostic(val applicability: CandidateApplicability)
abstract class InapplicableArgumentDiagnostic : ResolutionDiagnostic(INAPPLICABLE) {
abstract val argument: FirExpression
}
class MixingNamedAndPositionArguments(override val argument: FirExpression) : InapplicableArgumentDiagnostic()
class TooManyArguments(
val argument: FirExpression,
val function: FirFunction
) : ResolutionDiagnostic(INAPPLICABLE_ARGUMENTS_MAPPING_ERROR)
class NamedArgumentNotAllowed(
override val argument: FirExpression,
val function: FirFunction,
val forbiddenNamedArgumentsTarget: ForbiddenNamedArgumentsTarget
) : InapplicableArgumentDiagnostic()
class ArgumentPassedTwice(
override val argument: FirExpression,
val valueParameter: FirValueParameter,
val firstOccurrence: ResolvedCallArgument
) : InapplicableArgumentDiagnostic()
class VarargArgumentOutsideParentheses(
override val argument: FirExpression,
val valueParameter: FirValueParameter
) : InapplicableArgumentDiagnostic()
class NonVarargSpread(override val argument: FirExpression) : InapplicableArgumentDiagnostic()
class NoValueForParameter(
val valueParameter: FirValueParameter,
val function: FirFunction
) : ResolutionDiagnostic(INAPPLICABLE_ARGUMENTS_MAPPING_ERROR)
class NameNotFound(
val argument: FirNamedArgumentExpression,
val function: FirFunction
) : ResolutionDiagnostic(INAPPLICABLE_ARGUMENTS_MAPPING_ERROR)
object InapplicableCandidate : ResolutionDiagnostic(INAPPLICABLE)
object HiddenCandidate : ResolutionDiagnostic(HIDDEN)
object VisibilityError : ResolutionDiagnostic(VISIBILITY_ERROR)
object ResolvedWithLowPriority : ResolutionDiagnostic(RESOLVED_LOW_PRIORITY)
class InapplicableWrongReceiver(
val expectedType: ConeKotlinType? = null,
val actualType: ConeKotlinType? = null,
) : ResolutionDiagnostic(INAPPLICABLE_WRONG_RECEIVER)
object NoCompanionObject : ResolutionDiagnostic(NO_COMPANION_OBJECT)
class UnsafeCall(val actualType: ConeKotlinType) : ResolutionDiagnostic(UNSAFE_CALL)
object LowerPriorityToPreserveCompatibilityDiagnostic : ResolutionDiagnostic(RESOLVED_NEED_PRESERVE_COMPATIBILITY)
object CandidateChosenUsingOverloadResolutionByLambdaAnnotation : ResolutionDiagnostic(RESOLVED)
class UnstableSmartCast(val argument: FirExpressionWithSmartcast, val targetType: ConeKotlinType, val isCastToNotNull: Boolean) : ResolutionDiagnostic(UNSTABLE_SMARTCAST)
class ArgumentTypeMismatch(
val expectedType: ConeKotlinType,
val actualType: ConeKotlinType,
val argument: FirExpression,
val isMismatchDueToNullability: Boolean,
) : ResolutionDiagnostic(INAPPLICABLE)
class NullForNotNullType(
val argument: FirExpression
) : ResolutionDiagnostic(INAPPLICABLE)
class ManyLambdaExpressionArguments(
val argument: FirExpression
) : ResolutionDiagnostic(INAPPLICABLE_ARGUMENTS_MAPPING_ERROR)
class InfixCallOfNonInfixFunction(val function: FirNamedFunctionSymbol) : ResolutionDiagnostic(INAPPLICABLE_MODIFIER)
class OperatorCallOfNonOperatorFunction(val function: FirNamedFunctionSymbol) : ResolutionDiagnostic(INAPPLICABLE_MODIFIER)
class Unsupported(val message: String, val source: FirSourceElement? = null) : ResolutionDiagnostic(UNSUPPORTED)
object PropertyAsOperator : ResolutionDiagnostic(PROPERTY_AS_OPERATOR)
class DslScopeViolation(val calleeSymbol: FirBasedSymbol<*>) : ResolutionDiagnostic(DSL_SCOPE_VIOLATION)
@@ -0,0 +1,9 @@
/*
* Copyright 2010-2021 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
@RequiresOptIn
annotation class DfaInternals
@@ -0,0 +1,122 @@
/*
* Copyright 2010-2021 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.FirElement
import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.types.SmartcastStability
data class Identifier(
val symbol: FirBasedSymbol<*>,
val dispatchReceiver: DataFlowVariable?,
val extensionReceiver: DataFlowVariable?
) {
override fun toString(): String {
val callableId = (symbol as? FirCallableSymbol<*>)?.callableId
return "[$callableId, dispatchReceiver = $dispatchReceiver, extensionReceiver = $extensionReceiver]"
}
}
sealed class DataFlowVariable(private val variableIndexForDebug: Int) {
final override fun toString(): String {
return "d$variableIndexForDebug"
}
}
enum class PropertyStability(val impliedSmartcastStability: SmartcastStability?) {
// Immutable and no custom getter or local.
// Smartcast is definitely safe regardless of usage.
STABLE_VALUE(SmartcastStability.STABLE_VALUE),
// Open or custom getter.
// Smartcast is always unsafe regardless of usage.
PROPERTY_WITH_GETTER(SmartcastStability.PROPERTY_WITH_GETTER),
// Protected / public member value from another module.
// Smartcast is always unsafe regardless of usage.
ALIEN_PUBLIC_PROPERTY(SmartcastStability.ALIEN_PUBLIC_PROPERTY),
// Smartcast may or may not be safe, depending on whether there are concurrent writes to this local variable.
LOCAL_VAR(null),
// Mutable member property of a class or object.
// Smartcast is always unsafe regardless of usage.
MUTABLE_PROPERTY(SmartcastStability.MUTABLE_PROPERTY),
// Delegated property of a class or object.
// Smartcast is always unsafe regardless of usage.
DELEGATED_PROPERTY(SmartcastStability.DELEGATED_PROPERTY),
}
class RealVariable(
val identifier: Identifier,
val isThisReference: Boolean,
val explicitReceiverVariable: DataFlowVariable?,
variableIndexForDebug: Int,
val stability: PropertyStability,
) : DataFlowVariable(variableIndexForDebug) {
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 RealVariableAndType(val variable: RealVariable, val originalType: ConeKotlinType?) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as RealVariableAndType
if (variable != other.variable) return false
if (originalType != other.originalType) return false
return true
}
override fun hashCode(): Int {
var result = variable.hashCode()
result = 31 * result + originalType.hashCode()
return result
}
}
class SyntheticVariable(val fir: FirElement, variableIndexForDebug: Int) : DataFlowVariable(variableIndexForDebug) {
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 {
// hack for enums
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
}
@@ -0,0 +1,34 @@
/*
* Copyright 2010-2021 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.FirSourceElement
import org.jetbrains.kotlin.fir.references.FirControlFlowGraphReference
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
import org.jetbrains.kotlin.fir.visitors.FirTransformer
import org.jetbrains.kotlin.fir.visitors.FirVisitor
class FirControlFlowGraphReferenceImpl(
val controlFlowGraph: ControlFlowGraph,
val dataFlowInfo: DataFlowInfo? = null
) : FirControlFlowGraphReference() {
override val source: FirSourceElement? get() = null
override fun <R, D> acceptChildren(visitor: FirVisitor<R, D>, data: D) {}
override fun <D> transformChildren(transformer: FirTransformer<D>, data: D): FirControlFlowGraphReference {
return this
}
}
class DataFlowInfo(val variableStorage: VariableStorage, val flowOnNodes: Map<CFGNode<*>, Flow>)
val FirControlFlowGraphReference.controlFlowGraph: ControlFlowGraph?
get() = (this as? FirControlFlowGraphReferenceImpl)?.controlFlowGraph
val FirControlFlowGraphReference.dataFlowInfo: DataFlowInfo?
get() = (this as? FirControlFlowGraphReferenceImpl)?.dataFlowInfo
@@ -0,0 +1,21 @@
/*
* Copyright 2010-2021 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
abstract class Flow {
abstract fun getTypeStatement(variable: RealVariable): TypeStatement?
abstract fun getImplications(variable: DataFlowVariable): Collection<Implication>
abstract fun getVariablesInTypeStatements(): Collection<RealVariable>
abstract fun removeOperations(variable: DataFlowVariable): Collection<Implication>
abstract val directAliasMap: Map<RealVariable, RealVariableAndType>
abstract val backwardsAliasMap: Map<RealVariable, List<RealVariable>>
abstract val assignmentIndex: Map<RealVariable, Int>
}
fun Flow.unwrapVariable(variable: RealVariable): RealVariable {
return directAliasMap[variable]?.variable ?: variable
}
@@ -0,0 +1,195 @@
/*
* Copyright 2010-2021 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.types.ConeInferenceContext
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.canBeNull
import org.jetbrains.kotlin.fir.types.commonSuperTypeOrNull
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 unionFlow(flows: Collection<FLOW>): FLOW
abstract fun addTypeStatement(flow: FLOW, statement: TypeStatement)
abstract fun addImplication(flow: FLOW, implication: Implication)
abstract fun removeTypeStatementsAboutVariable(flow: FLOW, variable: RealVariable)
abstract fun removeLogicStatementsAboutVariable(flow: FLOW, variable: DataFlowVariable)
abstract fun removeAliasInformationAboutVariable(flow: FLOW, variable: RealVariable)
abstract fun translateVariableFromConditionInStatements(
flow: FLOW,
originalVariable: DataFlowVariable,
newVariable: DataFlowVariable,
shouldRemoveOriginalStatements: Boolean,
filter: (Implication) -> Boolean = { true },
transform: (Implication) -> Implication? = { it },
)
abstract fun approveStatementsInsideFlow(
flow: FLOW,
approvedStatement: OperationStatement,
shouldForkFlow: Boolean,
shouldRemoveSynthetics: Boolean,
): FLOW
abstract fun addLocalVariableAlias(flow: FLOW, alias: RealVariable, underlyingVariable: RealVariableAndType)
abstract fun removeLocalVariableAlias(flow: FLOW, alias: RealVariable)
abstract fun recordNewAssignment(flow: FLOW, variable: RealVariable, index: Int)
protected abstract fun getImplicationsWithVariable(flow: FLOW, variable: DataFlowVariable): Collection<Implication>
protected abstract fun ConeKotlinType.isAcceptableForSmartcast(): Boolean
// ------------------------------- Callbacks for updating implicit receiver stack -------------------------------
abstract fun processUpdatedReceiverVariable(flow: FLOW, variable: RealVariable)
abstract fun updateAllReceivers(flow: FLOW)
// ------------------------------- Public TypeStatement util functions -------------------------------
data class InfoForBooleanOperator(
val conditionalFromLeft: Collection<Implication>,
val conditionalFromRight: Collection<Implication>,
val knownFromRight: TypeStatements,
)
abstract fun collectInfoForBooleanOperator(
leftFlow: FLOW,
leftVariable: DataFlowVariable,
rightFlow: FLOW,
rightVariable: DataFlowVariable,
): InfoForBooleanOperator
abstract fun approveStatementsTo(
destination: MutableTypeStatements,
flow: FLOW,
approvedStatement: OperationStatement,
statements: Collection<Implication>,
)
/**
* Recursively collects all TypeStatements approved by [approvedStatement] and all predicates
* that has been implied by it
* TODO: or not recursively?
*/
fun approveOperationStatement(flow: FLOW, approvedStatement: OperationStatement): Collection<TypeStatement> {
val statements = getImplicationsWithVariable(flow, approvedStatement.variable)
return approveOperationStatement(flow, approvedStatement, statements).values
}
fun orForTypeStatements(
left: TypeStatements,
right: TypeStatements,
): MutableTypeStatements {
if (left.isNullOrEmpty() || right.isNullOrEmpty()) return mutableMapOf()
val map = mutableMapOf<RealVariable, MutableTypeStatement>()
for (variable in left.keys.intersect(right.keys)) {
val leftStatement = left.getValue(variable)
val rightStatement = right.getValue(variable)
map[variable] = or(listOf(leftStatement, rightStatement))
}
return map
}
// ------------------------------- Util functions -------------------------------
// TODO
protected fun <E> Collection<Collection<E>>.intersectSets(): Set<E> {
if (isEmpty()) return emptySet()
val iterator = iterator()
val result = LinkedHashSet<E>(iterator.next())
while (iterator.hasNext()) {
result.retainAll(iterator.next())
}
return result
}
private inline fun manipulateTypeStatements(
statements: Collection<TypeStatement>,
op: (Collection<Set<ConeKotlinType>>) -> MutableSet<ConeKotlinType>
): MutableTypeStatement {
require(statements.isNotEmpty())
statements.singleOrNull()?.let { return it as MutableTypeStatement }
val variable = statements.first().variable
assert(statements.all { it.variable == variable })
val exactType = op.invoke(statements.map { it.exactType })
val exactNotType = op.invoke(statements.map { it.exactNotType })
return MutableTypeStatement(variable, exactType, exactNotType)
}
protected fun or(statements: Collection<TypeStatement>): MutableTypeStatement =
manipulateTypeStatements(statements, ::orForTypes)
private fun orForTypes(types: Collection<Set<ConeKotlinType>>): MutableSet<ConeKotlinType> {
if (types.any { it.isEmpty() }) return mutableSetOf()
val intersectedTypes = types.map {
if (it.size > 1) {
context.intersectTypes(it.toList())
} else {
assert(it.size == 1) { "We've already checked each set of types is not empty." }
it.single()
}
}
val result = mutableSetOf<ConeKotlinType>()
context.commonSuperTypeOrNull(intersectedTypes)?.let {
if (it.isAcceptableForSmartcast()) {
result.add(it)
} else if (!it.canBeNull) {
result.add(context.anyType())
}
Unit
}
return result
}
protected fun and(statements: Collection<TypeStatement>): MutableTypeStatement =
manipulateTypeStatements(statements, ::andForTypes)
private fun andForTypes(types: Collection<Set<ConeKotlinType>>): MutableSet<ConeKotlinType> {
return types.flatMapTo(mutableSetOf()) { it }
}
}
fun <FLOW : Flow> LogicSystem<FLOW>.approveOperationStatement(
flow: FLOW,
approvedStatement: OperationStatement,
statements: Collection<Implication>,
): MutableTypeStatements {
return mutableMapOf<RealVariable, MutableTypeStatement>().apply {
approveStatementsTo(this, flow, approvedStatement, 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>.replaceVariableFromConditionInStatements(
flow: F,
originalVariable: DataFlowVariable,
newVariable: DataFlowVariable,
filter: (Implication) -> Boolean = { true },
transform: (Implication) -> Implication = { it },
) {
translateVariableFromConditionInStatements(
flow,
originalVariable,
newVariable,
shouldRemoveOriginalStatements = true,
filter,
transform,
)
}
@@ -0,0 +1,536 @@
/*
* Copyright 2010-2021 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 com.google.common.collect.ArrayListMultimap
import kotlinx.collections.immutable.*
import org.jetbrains.kotlin.fir.types.ConeInferenceContext
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.utils.addIfNotNull
import java.util.*
data class PersistentTypeStatement(
override val variable: RealVariable,
override val exactType: PersistentSet<ConeKotlinType>,
override val exactNotType: PersistentSet<ConeKotlinType>
) : TypeStatement() {
override operator fun plus(other: TypeStatement): PersistentTypeStatement {
return PersistentTypeStatement(
variable,
exactType + other.exactType,
exactNotType + other.exactNotType
)
}
override val isEmpty: Boolean
get() = exactType.isEmpty() && exactNotType.isEmpty()
override fun invert(): PersistentTypeStatement {
return PersistentTypeStatement(variable, exactNotType, exactType)
}
}
typealias PersistentApprovedTypeStatements = PersistentMap<RealVariable, PersistentTypeStatement>
typealias PersistentImplications = PersistentMap<DataFlowVariable, PersistentList<Implication>>
class PersistentFlow : Flow {
val previousFlow: PersistentFlow?
var approvedTypeStatements: PersistentApprovedTypeStatements
var logicStatements: PersistentImplications
val level: Int
var approvedTypeStatementsDiff: PersistentApprovedTypeStatements = persistentHashMapOf()
/*
* val x = a
* val y = a
*
* directAliasMap: { x -> a, y -> a}
* backwardsAliasMap: { a -> [x, y] }
*/
override var directAliasMap: PersistentMap<RealVariable, RealVariableAndType>
override var backwardsAliasMap: PersistentMap<RealVariable, PersistentList<RealVariable>>
override var assignmentIndex: PersistentMap<RealVariable, Int>
constructor(previousFlow: PersistentFlow) {
this.previousFlow = previousFlow
approvedTypeStatements = previousFlow.approvedTypeStatements
logicStatements = previousFlow.logicStatements
level = previousFlow.level + 1
directAliasMap = previousFlow.directAliasMap
backwardsAliasMap = previousFlow.backwardsAliasMap
assignmentIndex = previousFlow.assignmentIndex
}
constructor() {
previousFlow = null
approvedTypeStatements = persistentHashMapOf()
logicStatements = persistentHashMapOf()
level = 1
directAliasMap = persistentMapOf()
backwardsAliasMap = persistentMapOf()
assignmentIndex = persistentMapOf()
}
override fun getTypeStatement(variable: RealVariable): TypeStatement? {
return approvedTypeStatements[variable]
}
override fun getImplications(variable: DataFlowVariable): Collection<Implication> {
return logicStatements[variable] ?: emptyList()
}
override fun getVariablesInTypeStatements(): Collection<RealVariable> {
return approvedTypeStatements.keys
}
override fun removeOperations(variable: DataFlowVariable): Collection<Implication> {
return getImplications(variable).also {
if (it.isNotEmpty()) {
logicStatements -= variable
}
}
}
}
abstract class PersistentLogicSystem(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 {
return foldFlow(
flows,
mergeOperation = { statements -> this.or(statements).takeIf { it.isNotEmpty } },
)
}
override fun unionFlow(flows: Collection<PersistentFlow>): PersistentFlow {
return foldFlow(
flows,
this::and,
)
}
private inline fun foldFlow(
flows: Collection<PersistentFlow>,
mergeOperation: (Collection<TypeStatement>) -> MutableTypeStatement?,
): PersistentFlow {
if (flows.isEmpty()) return createEmptyFlow()
flows.singleOrNull()?.let { return it }
val aliasedVariablesThatDontChangeAlias = computeAliasesThatDontChange(flows)
val commonFlow = flows.reduce(::lowestCommonFlow)
val variables = flows.flatMap { it.approvedTypeStatements.keys }.toSet()
for (variable in variables) {
val info = mergeOperation(flows.map { it.getApprovedTypeStatements(variable) }) ?: continue
removeTypeStatementsAboutVariable(commonFlow, variable)
val thereWereReassignments = variable.hasDifferentReassignments(flows)
if (thereWereReassignments) {
removeLogicStatementsAboutVariable(commonFlow, variable)
removeAliasInformationAboutVariable(commonFlow, variable)
}
commonFlow.addApprovedStatements(info)
}
commonFlow.addVariableAliases(aliasedVariablesThatDontChangeAlias)
return commonFlow
}
private fun RealVariable.hasDifferentReassignments(flows: Collection<PersistentFlow>): Boolean {
val firstIndex = flows.first().assignmentIndex[this] ?: -1
for (flow in flows) {
val index = flow.assignmentIndex[this] ?: -1
if (index != firstIndex) return true
}
return false
}
private fun computeAliasesThatDontChange(
flows: Collection<PersistentFlow>
): MutableMap<RealVariable, RealVariableAndType> {
val flowsSize = flows.size
val aliasedVariablesThatDontChangeAlias = mutableMapOf<RealVariable, RealVariableAndType>()
flows.flatMapTo(mutableSetOf()) { it.directAliasMap.keys }.forEach { aliasedVariable ->
val originals = flows.map { it.directAliasMap[aliasedVariable] ?: return@forEach }
if (originals.size != flowsSize) return@forEach
val firstOriginal = originals.first()
if (originals.all { it == firstOriginal }) {
aliasedVariablesThatDontChangeAlias[aliasedVariable] = firstOriginal
}
}
return aliasedVariablesThatDontChangeAlias
}
private fun PersistentFlow.addVariableAliases(
aliasedVariablesThatDontChangeAlias: MutableMap<RealVariable, RealVariableAndType>
) {
for ((alias, underlyingVariable) in aliasedVariablesThatDontChangeAlias) {
addLocalVariableAlias(this, alias, underlyingVariable)
}
}
private fun PersistentFlow.addApprovedStatements(
info: MutableTypeStatement
) {
approvedTypeStatements = approvedTypeStatements.addTypeStatement(info)
if (previousFlow != null) {
approvedTypeStatementsDiff = approvedTypeStatementsDiff.addTypeStatement(info)
}
}
override fun addLocalVariableAlias(flow: PersistentFlow, alias: RealVariable, underlyingVariable: RealVariableAndType) {
removeLocalVariableAlias(flow, alias)
flow.directAliasMap = flow.directAliasMap.put(alias, underlyingVariable)
flow.backwardsAliasMap = flow.backwardsAliasMap.put(
underlyingVariable.variable,
{ persistentListOf(alias) },
{ variables -> variables + alias }
)
}
/**
* For example, consider `var b = a`. In this case, `b` is an alias of `a`. In other words, `a` is the original variable of `b`.
*
* For back alias, consider the following.
* ```
* var b = a
* var c = b
* ```
* Here, if the current `alias` references `b`, `c` is a back alias of `b`. So if one calls this method with `b`, we must also
* remove aliasing between `b` and `c`. But before removing aliasing, we need to copy any statements that apply to `b` to `c`.
*/
override fun removeLocalVariableAlias(flow: PersistentFlow, alias: RealVariable) {
val backAliases = flow.backwardsAliasMap[alias] ?: emptyList()
for (backAlias in backAliases) {
flow.logicStatements[alias]?.let { it ->
val newStatements = it.map { it.replaceVariable(alias, backAlias) }
val replacedStatements =
flow.logicStatements[backAlias]?.let { existing -> existing + newStatements } ?: newStatements.toPersistentList()
flow.logicStatements = flow.logicStatements.put(backAlias, replacedStatements)
}
flow.approvedTypeStatements[alias]?.let { it ->
val newStatements = it.replaceVariable(alias, backAlias)
val replacedStatements =
flow.approvedTypeStatements[backAlias]?.let { existing -> existing + newStatements } ?: newStatements
flow.approvedTypeStatements = flow.approvedTypeStatements.put(backAlias, replacedStatements.toPersistent())
}
flow.approvedTypeStatementsDiff[alias]?.let { it ->
val newStatements = it.replaceVariable(alias, backAlias)
val replacedStatements =
flow.approvedTypeStatementsDiff[backAlias]?.let { existing -> existing + newStatements } ?: newStatements
flow.approvedTypeStatementsDiff = flow.approvedTypeStatementsDiff.put(backAlias, replacedStatements.toPersistent())
}
}
val original = flow.directAliasMap[alias]?.variable
if (original != null) {
flow.directAliasMap = flow.directAliasMap.remove(alias)
val variables = flow.backwardsAliasMap.getValue(original)
flow.backwardsAliasMap = flow.backwardsAliasMap.put(original, variables - alias)
}
flow.backwardsAliasMap = flow.backwardsAliasMap.remove(alias)
for (backAlias in backAliases) {
flow.directAliasMap = flow.directAliasMap.remove(backAlias)
}
}
private fun Implication.replaceVariable(from: RealVariable, to: RealVariable): Implication {
return Implication(condition.replaceVariable(from, to), effect.replaceVariable(from, to))
}
private fun <T : Statement<T>> Statement<T>.replaceVariable(from: RealVariable, to: RealVariable): T {
val statement = when (this) {
is OperationStatement -> if (variable == from) copy(variable = to) else this
is PersistentTypeStatement -> if (variable == from) copy(variable = to) else this
is MutableTypeStatement -> if (variable == from) MutableTypeStatement(to, exactType, exactNotType) else this
else -> throw IllegalArgumentException("unknown type of statement $this")
}
@Suppress("UNCHECKED_CAST")
return statement as T
}
@OptIn(DfaInternals::class)
private fun PersistentFlow.getApprovedTypeStatements(variable: RealVariable): MutableTypeStatement {
var flow = this
val result = MutableTypeStatement(variable)
val variableUnderAlias = directAliasMap[variable]
if (variableUnderAlias == null) {
flow.approvedTypeStatements[variable]?.let {
result += it
}
} else {
result.exactType.addIfNotNull(variableUnderAlias.originalType)
flow.approvedTypeStatements[variableUnderAlias.variable]?.let { result += it }
}
return result
}
override fun addTypeStatement(flow: PersistentFlow, statement: TypeStatement) {
if (statement.isEmpty) return
with(flow) {
approvedTypeStatements = approvedTypeStatements.addTypeStatement(statement)
if (previousFlow != null) {
approvedTypeStatementsDiff = approvedTypeStatementsDiff.addTypeStatement(statement)
}
if (statement.variable.isThisReference) {
processUpdatedReceiverVariable(flow, statement.variable)
}
}
}
override fun addImplication(flow: PersistentFlow, implication: Implication) {
if ((implication.effect as? TypeStatement)?.isEmpty == true) return
if (implication.condition == implication.effect) return
with(flow) {
val variable = implication.condition.variable
val existingImplications = logicStatements[variable]
logicStatements = if (existingImplications == null) {
logicStatements.put(variable, persistentListOf(implication))
} else {
logicStatements.put(variable, existingImplications + implication)
}
}
}
override fun removeTypeStatementsAboutVariable(flow: PersistentFlow, variable: RealVariable) {
flow.approvedTypeStatements -= variable
flow.approvedTypeStatementsDiff -= variable
}
override fun removeLogicStatementsAboutVariable(flow: PersistentFlow, variable: DataFlowVariable) {
flow.logicStatements -= variable
var newLogicStatements = flow.logicStatements
for ((key, implications) in flow.logicStatements) {
val implicationsToDelete = mutableListOf<Implication>()
implications.forEach { implication ->
if (implication.effect.variable == variable) {
implicationsToDelete += implication
}
}
if (implicationsToDelete.isEmpty()) continue
val newImplications = implications.removeAll(implicationsToDelete)
newLogicStatements = if (newImplications.isNotEmpty()) {
newLogicStatements.put(key, newImplications)
} else {
newLogicStatements.remove(key)
}
}
flow.logicStatements = newLogicStatements
}
override fun removeAliasInformationAboutVariable(flow: PersistentFlow, variable: RealVariable) {
val existedAlias = flow.directAliasMap[variable]?.variable
if (existedAlias != null) {
flow.directAliasMap = flow.directAliasMap.remove(variable)
val updatedBackwardsAliasList = flow.backwardsAliasMap.getValue(existedAlias).remove(variable)
flow.backwardsAliasMap = if (updatedBackwardsAliasList.isEmpty()) {
flow.backwardsAliasMap.remove(existedAlias)
} else {
flow.backwardsAliasMap.put(existedAlias, updatedBackwardsAliasList)
}
}
}
override fun translateVariableFromConditionInStatements(
flow: PersistentFlow,
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 newStatement = OperationStatement(newVariable, it.condition.operation) implies it.effect
transform(newStatement)
}.toPersistentList()
if (shouldRemoveOriginalStatements) {
logicStatements -= originalVariable
}
logicStatements = logicStatements.put(newVariable, logicStatements[newVariable]?.let { it + newStatements } ?: newStatements)
}
}
override fun approveStatementsInsideFlow(
flow: PersistentFlow,
approvedStatement: OperationStatement,
shouldForkFlow: Boolean,
shouldRemoveSynthetics: Boolean
): PersistentFlow {
val approvedFacts = approveOperationStatementsInternal(
flow,
approvedStatement,
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 = PersistentTypeStatement(variable, persistentSetOf(), persistentSetOf())
for (info in infos) {
resultInfo += info
}
if (variable.isThisReference) {
updatedReceivers += variable
}
addTypeStatement(resultFlow, resultInfo)
}
updatedReceivers.forEach {
processUpdatedReceiverVariable(resultFlow, it)
}
return resultFlow
}
private fun approveOperationStatementsInternal(
flow: PersistentFlow,
approvedStatement: OperationStatement,
initialStatements: Collection<Implication>?,
shouldRemoveSynthetics: Boolean
): ArrayListMultimap<RealVariable, TypeStatement> {
val approvedFacts: ArrayListMultimap<RealVariable, TypeStatement> = ArrayListMultimap.create()
val approvedStatements = LinkedList<OperationStatement>().apply { this += approvedStatement }
approveOperationStatementsInternal(flow, approvedStatements, initialStatements, shouldRemoveSynthetics, approvedFacts)
return approvedFacts
}
private fun approveOperationStatementsInternal(
flow: PersistentFlow,
approvedStatements: LinkedList<OperationStatement>,
initialStatements: Collection<Implication>?,
shouldRemoveSynthetics: Boolean,
approvedTypeStatements: ArrayListMultimap<RealVariable, TypeStatement>
) {
if (approvedStatements.isEmpty()) return
val approvedOperationStatements = mutableSetOf<OperationStatement>()
var firstIteration = true
while (approvedStatements.isNotEmpty()) {
@Suppress("NAME_SHADOWING")
val approvedStatement: OperationStatement = approvedStatements.removeFirst()
// Defense from cycles in facts
if (!approvedOperationStatements.add(approvedStatement)) {
continue
}
val statements = initialStatements?.takeIf { firstIteration }
?: flow.logicStatements[approvedStatement.variable]?.takeIf { it.isNotEmpty() }
?: continue
if (shouldRemoveSynthetics && approvedStatement.variable.isSynthetic()) {
flow.logicStatements -= approvedStatement.variable
}
for (statement in statements) {
if (statement.condition == approvedStatement) {
when (val effect = statement.effect) {
is OperationStatement -> approvedStatements += effect
is TypeStatement -> approvedTypeStatements.put(effect.variable, effect)
}
}
}
firstIteration = false
}
}
override fun approveStatementsTo(
destination: MutableTypeStatements,
flow: PersistentFlow,
approvedStatement: OperationStatement,
statements: Collection<Implication>
) {
val approveOperationStatements =
approveOperationStatementsInternal(flow, approvedStatement, statements, shouldRemoveSynthetics = false)
approveOperationStatements.asMap().forEach { (variable, infos) ->
for (info in infos) {
val mutableInfo = info.asMutableStatement()
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.approvedTypeStatementsDiff
)
}
override fun getImplicationsWithVariable(flow: PersistentFlow, variable: DataFlowVariable): Collection<Implication> {
return flow.logicStatements[variable] ?: emptyList()
}
override fun recordNewAssignment(flow: PersistentFlow, variable: RealVariable, index: Int) {
flow.assignmentIndex = flow.assignmentIndex.put(variable, index)
}
// --------------------------------------------------------------------\
}
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 PersistentApprovedTypeStatements.addTypeStatement(info: TypeStatement): PersistentApprovedTypeStatements {
val variable = info.variable
val existingInfo = this[variable]
return if (existingInfo == null) {
val persistentInfo = if (info is PersistentTypeStatement) info else info.toPersistent()
put(variable, persistentInfo)
} else {
put(variable, existingInfo + info)
}
}
private fun TypeStatement.toPersistent(): PersistentTypeStatement = PersistentTypeStatement(
variable,
exactType.toPersistentSet(),
exactNotType.toPersistentSet()
)
fun TypeStatement.asMutableStatement(): MutableTypeStatement = when (this) {
is MutableTypeStatement -> this
is PersistentTypeStatement -> MutableTypeStatement(variable, exactType.toMutableSet(), exactNotType.toMutableSet())
else -> throw IllegalArgumentException("Unknown TypeStatement type: ${this::class}")
}
@@ -0,0 +1,107 @@
/*
* Copyright 2010-2021 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.FirElement
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.util.asReversedFrozen
abstract class Stack<T> {
abstract val size: Int
abstract fun top(): T
abstract fun pop(): T
abstract fun push(value: T)
abstract fun reset()
/**
* returns all elements of the stack in order of retrieval
*/
abstract fun all(): List<T>
}
fun <T> stackOf(vararg values: T): Stack<T> = StackImpl(*values)
val Stack<*>.isEmpty: Boolean get() = size == 0
val Stack<*>.isNotEmpty: Boolean get() = size != 0
fun <T> Stack<T>.topOrNull(): T? = if (size == 0) null else top()
fun <T> Stack<T>.popOrNull(): T? = if (size == 0) null else pop()
private class StackImpl<T>(vararg values: T) : Stack<T>() {
private val stack = mutableListOf(*values)
override fun top(): T = stack[stack.size - 1]
override fun pop(): T = stack.removeAt(stack.size - 1)
override fun push(value: T) {
stack.add(value)
}
override val size: Int get() = stack.size
override fun reset() {
stack.clear()
}
override fun all(): List<T> = stack.asReversedFrozen()
}
class NodeStorage<T : FirElement, N : CFGNode<T>> : Stack<N>(){
private val stack: Stack<N> = StackImpl()
private val map: MutableMap<T, N> = mutableMapOf()
override val size: Int get() = stack.size
override fun top(): N = stack.top()
override fun pop(): N = stack.pop().also {
map.remove(it.fir)
}
override fun push(value: N) {
stack.push(value)
map[value.fir] = value
}
operator fun get(key: T): N? {
return map[key]
}
override fun reset() {
stack.reset()
map.clear()
}
override fun all(): List<N> = stack.all()
}
class SymbolBasedNodeStorage<T, N : CFGNode<T>> : Stack<N>() where T : FirElement {
private val stack: Stack<N> = StackImpl()
private val map: MutableMap<FirBasedSymbol<*>, N> = mutableMapOf()
override val size: Int get() = stack.size
override fun top(): N = stack.top()
override fun pop(): N = stack.pop().also {
map.remove((it.fir as FirDeclaration).symbol)
}
override fun push(value: N) {
stack.push(value)
map[(value.fir as FirDeclaration).symbol] = value
}
operator fun get(key: FirBasedSymbol<*>): N? {
return map[key]
}
override fun reset() {
stack.reset()
map.clear()
}
override fun all(): List<N> = stack.all()
}
@@ -0,0 +1,16 @@
/*
* Copyright 2010-2021 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.FirElement
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
abstract class VariableStorage {
abstract fun getRealVariableWithoutUnwrappingAlias(symbol: FirBasedSymbol<*>?, fir: FirElement, flow: Flow): RealVariable?
abstract fun getRealVariable(symbol: FirBasedSymbol<*>?, fir: FirElement, flow: Flow): RealVariable?
abstract fun getSyntheticVariable(fir: FirElement): SyntheticVariable?
abstract fun getVariable(fir: FirElement, flow: Flow): DataFlowVariable?
}
@@ -0,0 +1,202 @@
/*
* Copyright 2010-2021 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.descriptors.Modality
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirAnonymousObject
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor
import org.jetbrains.kotlin.fir.declarations.utils.modality
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.impl.FirNoReceiverExpression
import org.jetbrains.kotlin.fir.originalOrSelf
import org.jetbrains.kotlin.fir.references.FirThisReference
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.types.coneTypeSafe
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
@OptIn(DfaInternals::class)
class VariableStorageImpl(private val session: FirSession) : VariableStorage() {
private var counter = 1
private val _realVariables: MutableMap<Identifier, RealVariable> = HashMap()
val realVariables: Map<Identifier, RealVariable>
get() = _realVariables
private val syntheticVariables: MutableMap<FirElement, SyntheticVariable> = HashMap()
fun clear(): VariableStorageImpl = VariableStorageImpl(session)
fun getOrCreateRealVariableWithoutUnwrappingAlias(
flow: Flow,
symbol: FirBasedSymbol<*>,
fir: FirElement,
stability: PropertyStability
): RealVariable {
val realFir = fir.unwrapElement()
val identifier = getIdentifierBySymbol(flow, symbol, realFir)
return _realVariables.getOrPut(identifier) { createRealVariableInternal(flow, identifier, realFir, stability) }
}
private fun getOrCreateRealVariable(
flow: Flow,
symbol: FirBasedSymbol<*>,
fir: FirElement,
stability: PropertyStability
): RealVariable {
val variable = getOrCreateRealVariableWithoutUnwrappingAlias(flow, symbol, fir, stability)
return flow.directAliasMap[variable]?.variable ?: variable
}
private fun FirElement.unwrapElement(): FirElement = when (this) {
is FirWhenSubjectExpression -> whenRef.value.let { it.subjectVariable ?: it.subject }?.unwrapElement() ?: this
is FirExpressionWithSmartcast -> originalExpression.unwrapElement()
is FirSafeCallExpression -> regularQualifiedAccess.unwrapElement()
is FirCheckedSafeCallSubject -> originalReceiverRef.value.unwrapElement()
else -> this
}
private fun getIdentifierBySymbol(
flow: Flow,
symbol: FirBasedSymbol<*>,
fir: FirElement,
): Identifier {
val expression = fir as? FirQualifiedAccess
return Identifier(
symbol,
expression?.dispatchReceiver?.takeIf { it != FirNoReceiverExpression }?.let { getOrCreateVariable(flow, it) },
expression?.extensionReceiver?.takeIf { it != FirNoReceiverExpression }?.let { getOrCreateVariable(flow, it) }
)
}
/**
* [originalFir] used for extracting expression under <when_subject> and extracting receiver
*/
private fun createRealVariableInternal(
flow: Flow,
identifier: Identifier,
originalFir: FirElement,
stability: PropertyStability
): RealVariable {
val receiver: FirExpression?
val isThisReference: Boolean
val expression: FirQualifiedAccess? = when (originalFir) {
is FirQualifiedAccessExpression -> originalFir
is FirWhenSubjectExpression -> originalFir.whenRef.value.subject as? FirQualifiedAccessExpression
is FirVariableAssignment -> originalFir
else -> null
}
if (expression != null) {
receiver = expression.explicitReceiver
isThisReference = expression.calleeReference is FirThisReference
} else {
receiver = null
isThisReference = false
}
val receiverVariable = receiver?.let { getOrCreateVariable(flow, it) }
return RealVariable(identifier, isThisReference, receiverVariable, counter++, stability)
}
@JvmName("getOrCreateRealVariableOrNull")
fun getOrCreateRealVariable(flow: Flow, symbol: FirBasedSymbol<*>?, fir: FirElement): RealVariable? =
symbol.getStability(fir)?.let { getOrCreateRealVariable(flow, symbol!!, fir, it) }
fun createSyntheticVariable(fir: FirElement): SyntheticVariable =
SyntheticVariable(fir, counter++).also { syntheticVariables[fir] = it }
fun getOrCreateVariable(flow: Flow, fir: FirElement): DataFlowVariable {
val realFir = fir.unwrapElement()
val symbol = realFir.symbol
val stability = symbol.getStability(realFir)
return if (stability != null) {
getOrCreateRealVariable(flow, symbol!!, realFir, stability)
} else {
syntheticVariables[realFir] ?: createSyntheticVariable(realFir)
}
}
override fun getRealVariableWithoutUnwrappingAlias(symbol: FirBasedSymbol<*>?, fir: FirElement, flow: Flow): RealVariable? {
val realFir = fir.unwrapElement()
return symbol.takeIf { it.getStability(realFir) != null }?.let {
_realVariables[getIdentifierBySymbol(flow, it, realFir.unwrapElement())]
}
}
override fun getRealVariable(symbol: FirBasedSymbol<*>?, fir: FirElement, flow: Flow): RealVariable? {
return getRealVariableWithoutUnwrappingAlias(symbol, fir, flow)?.let { flow.unwrapVariable(it) }
}
override fun getSyntheticVariable(fir: FirElement): SyntheticVariable? {
return syntheticVariables[fir.unwrapElement()]
}
override fun getVariable(fir: FirElement, flow: Flow): DataFlowVariable? {
val realFir = fir.unwrapElement()
val symbol = realFir.symbol
val stability = symbol.getStability(fir)
return if (stability != null) {
getRealVariable(symbol, realFir, flow)
} else {
getSyntheticVariable(fir)
}
}
fun removeRealVariable(symbol: FirBasedSymbol<*>) {
_realVariables.remove(Identifier(symbol, null, null))
}
fun removeSyntheticVariable(variable: DataFlowVariable) {
if (!variable.isSynthetic()) return
syntheticVariables.remove(variable.fir)
}
@OptIn(ExperimentalContracts::class)
fun FirBasedSymbol<*>?.getStability(originalFir: FirElement): PropertyStability? {
contract {
returnsNotNull() implies (this@getStability != null)
}
when (this) {
is FirAnonymousObjectSymbol -> return null
is FirFunctionSymbol<*>,
is FirClassSymbol<*>,
is FirBackingFieldSymbol -> return PropertyStability.STABLE_VALUE
null -> return null
}
if (originalFir is FirThisReceiverExpression) return PropertyStability.STABLE_VALUE
if (this !is FirVariableSymbol<*>) return null
val property = this.fir as? FirProperty ?: return PropertyStability.STABLE_VALUE
return when {
property.delegate != null -> PropertyStability.DELEGATED_PROPERTY
property.isLocal -> if (property.isVal) PropertyStability.STABLE_VALUE else PropertyStability.LOCAL_VAR
property.isVar -> PropertyStability.MUTABLE_PROPERTY
property.receiverTypeRef != null -> PropertyStability.PROPERTY_WITH_GETTER
property.getter.let { it != null && it !is FirDefaultPropertyAccessor } -> PropertyStability.PROPERTY_WITH_GETTER
property.originalOrSelf().moduleData.session != session -> PropertyStability.ALIEN_PUBLIC_PROPERTY
property.modality != Modality.FINAL -> {
val dispatchReceiver = (originalFir.unwrapElement() as? FirQualifiedAccess)?.dispatchReceiver ?: return null
val receiverType = dispatchReceiver.typeRef.coneTypeSafe<ConeClassLikeType>()?.fullyExpandedType(session) ?: return null
val receiverSymbol = receiverType.lookupTag.toSymbol(session) ?: return null
when (val receiverFir = receiverSymbol.fir) {
is FirAnonymousObject -> PropertyStability.STABLE_VALUE
is FirRegularClass -> if (receiverFir.modality == Modality.FINAL) PropertyStability.STABLE_VALUE else PropertyStability.PROPERTY_WITH_GETTER
else -> throw IllegalStateException("Should not be here: $receiverFir")
}
}
else -> PropertyStability.STABLE_VALUE
}
}
}
@@ -0,0 +1,833 @@
/*
* Copyright 2010-2021 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.
*/
@file:Suppress("Reformat")
package org.jetbrains.kotlin.fir.resolve.dfa.cfg
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.resolve.dfa.controlFlowGraph
import org.jetbrains.kotlin.fir.visitors.FirTransformer
import org.jetbrains.kotlin.fir.visitors.FirVisitor
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.runIf
@RequiresOptIn
annotation class CfgInternals
sealed class CFGNode<out E : FirElement>(val owner: ControlFlowGraph, val level: Int, private val id: Int) {
companion object {
@CfgInternals
fun addEdge(
from: CFGNode<*>,
to: CFGNode<*>,
kind: EdgeKind,
propagateDeadness: Boolean,
label: EdgeLabel = NormalPath
) {
from._followingNodes += to
to._previousNodes += from
addJustKindEdge(from, to, kind, propagateDeadness, edgeExists = false, label = label)
}
@CfgInternals
fun addJustKindEdge(
from: CFGNode<*>,
to: CFGNode<*>,
kind: EdgeKind,
propagateDeadness: Boolean,
label: EdgeLabel = NormalPath
) {
addJustKindEdge(from, to, kind, propagateDeadness, edgeExists = true, label = label)
}
private fun addJustKindEdge(
from: CFGNode<*>,
to: CFGNode<*>,
kind: EdgeKind,
propagateDeadness: Boolean,
edgeExists: Boolean,
label: EdgeLabel = NormalPath
) {
// It's hard to define label merging, hence overwritten with the latest one.
// One day, if we allow multiple edges between nodes with different labels, we won't even need kind merging.
if (kind != EdgeKind.Forward || label != NormalPath) {
val fromToKind = from._outgoingEdges[to]?.kind ?: runIf(edgeExists) { EdgeKind.Forward }
merge(kind, fromToKind)?.let {
from._outgoingEdges[to] = Edge.create(label, it)
} ?: from._outgoingEdges.remove(to)
val toFromKind = to._incomingEdges[from]?.kind ?: runIf(edgeExists) { EdgeKind.Forward }
merge(kind, toFromKind)?.let {
to._incomingEdges[from] = Edge.create(label, it)
} ?: to._incomingEdges.remove(from)
}
if (propagateDeadness && kind == EdgeKind.DeadForward) {
to.isDead = true
}
}
private fun merge(first: EdgeKind, second: EdgeKind?): EdgeKind? {
return when {
second == null -> first
first == second -> first
first == EdgeKind.DeadForward || second == EdgeKind.DeadForward -> EdgeKind.DeadForward
first == EdgeKind.DeadBackward || second == EdgeKind.DeadBackward -> EdgeKind.DeadBackward
first == EdgeKind.Forward || second == EdgeKind.Forward -> null
first.usedInDfa xor second.usedInDfa -> null
else -> throw IllegalStateException()
}
}
@CfgInternals
fun removeAllIncomingEdges(to: CFGNode<*>) {
for (from in to._previousNodes) {
from._followingNodes.remove(to)
from._outgoingEdges.remove(to)
to._incomingEdges.remove(from)
}
to._previousNodes.clear()
}
@CfgInternals
fun removeAllOutgoingEdges(from: CFGNode<*>) {
for (to in from._followingNodes) {
to._previousNodes.remove(from)
from._outgoingEdges.remove(to)
to._incomingEdges.remove(from)
}
from._followingNodes.clear()
}
}
init {
@Suppress("LeakingThis")
owner.addNode(this)
}
private val _previousNodes: MutableList<CFGNode<*>> = mutableListOf()
private val _followingNodes: MutableList<CFGNode<*>> = mutableListOf()
val previousNodes: List<CFGNode<*>> get() = _previousNodes
val followingNodes: List<CFGNode<*>> get() = _followingNodes
private val _incomingEdges = mutableMapOf<CFGNode<*>, Edge>().withDefault { Edge.Normal_Forward }
private val _outgoingEdges = mutableMapOf<CFGNode<*>, Edge>().withDefault { Edge.Normal_Forward }
val incomingEdges: Map<CFGNode<*>, Edge> get() = _incomingEdges
val outgoingEdges: Map<CFGNode<*>, Edge> get() = _outgoingEdges
abstract val fir: E
var isDead: Boolean = false
protected set
@CfgInternals
fun updateDeadStatus() {
isDead = incomingEdges.size == previousNodes.size && incomingEdges.values.all {
it.kind == EdgeKind.DeadForward || !it.kind.usedInCfa
}
}
abstract fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R
fun accept(visitor: ControlFlowGraphVisitorVoid) {
accept(visitor, null)
}
final override fun equals(other: Any?): Boolean {
if (other !is CFGNode<*>) return false
return this === other
}
final override fun hashCode(): Int {
return id
}
}
val CFGNode<*>.firstPreviousNode: CFGNode<*> get() = previousNodes[0]
val CFGNode<*>.lastPreviousNode: CFGNode<*> get() = previousNodes.last()
interface EnterNodeMarker
interface ExitNodeMarker
// ----------------------------------- EnterNode for declaration with CFG -----------------------------------
sealed class CFGNodeWithCfgOwner<out E : FirControlFlowGraphOwner>(owner: ControlFlowGraph, level: Int, id: Int) : CFGNode<E>(owner, level, id) {
private val _subGraphs = mutableListOf<ControlFlowGraph>()
fun addSubGraph(graph: ControlFlowGraph){
_subGraphs += graph
}
val subGraphs: List<ControlFlowGraph> by lazy {
_subGraphs.also { it.addIfNotNull(fir.controlFlowGraphReference?.controlFlowGraph) }
}
}
// ----------------------------------- Named function -----------------------------------
class FunctionEnterNode(owner: ControlFlowGraph, override val fir: FirFunction, level: Int, id: Int) : CFGNode<FirFunction>(owner, level, id),
EnterNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitFunctionEnterNode(this, data)
}
}
class FunctionExitNode(owner: ControlFlowGraph, override val fir: FirFunction, level: Int, id: Int) : CFGNode<FirFunction>(owner, level, id),
ExitNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitFunctionExitNode(this, data)
}
}
class LocalFunctionDeclarationNode(owner: ControlFlowGraph, override val fir: FirFunction, level: Int, id: Int) : CFGNodeWithCfgOwner<FirFunction>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitLocalFunctionDeclarationNode(this, data)
}
}
// ----------------------------------- Default arguments -----------------------------------
@OptIn(CfgInternals::class)
class EnterDefaultArgumentsNode(owner: ControlFlowGraph, override val fir: FirValueParameter, level: Int, id: Int) : CFGNodeWithCfgOwner<FirValueParameter>(owner, level, id),
EnterNodeMarker {
init {
owner.enterNode = this
}
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitEnterDefaultArgumentsNode(this, data)
}
}
@OptIn(CfgInternals::class)
class ExitDefaultArgumentsNode(owner: ControlFlowGraph, override val fir: FirValueParameter, level: Int, id: Int) : CFGNode<FirValueParameter>(owner, level, id),
ExitNodeMarker {
init {
owner.exitNode = this
}
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitExitDefaultArgumentsNode(this, data)
}
}
// ----------------------------------- Anonymous function -----------------------------------
class PostponedLambdaEnterNode(owner: ControlFlowGraph, override val fir: FirAnonymousFunction, level: Int, id: Int) : CFGNodeWithCfgOwner<FirAnonymousFunction>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitPostponedLambdaEnterNode(this, data)
}
}
class PostponedLambdaExitNode(owner: ControlFlowGraph, override val fir: FirAnonymousFunctionExpression, level: Int, id: Int) : CFGNode<FirAnonymousFunctionExpression>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitPostponedLambdaExitNode(this, data)
}
}
class UnionFunctionCallArgumentsNode(owner: ControlFlowGraph, override val fir: FirElement, level: Int, id: Int) : CFGNode<FirElement>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitUnionFunctionCallArgumentsNode(this, data)
}
}
class AnonymousFunctionExpressionExitNode(owner: ControlFlowGraph, override val fir: FirAnonymousFunctionExpression, level: Int, id: Int) : CFGNode<FirAnonymousFunctionExpression>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitAnonymousFunctionExpressionExitNode(this, data)
}
}
// ----------------------------------- Classes -----------------------------------
@OptIn(CfgInternals::class)
class ClassEnterNode(owner: ControlFlowGraph, override val fir: FirClass, level: Int, id: Int) : CFGNode<FirClass>(owner, level, id),
EnterNodeMarker {
init {
owner.enterNode = this
}
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitClassEnterNode(this, data)
}
}
@OptIn(CfgInternals::class)
class ClassExitNode(owner: ControlFlowGraph, override val fir: FirClass, level: Int, id: Int) : CFGNode<FirClass>(owner, level, id),
ExitNodeMarker {
init {
owner.exitNode = this
}
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitClassExitNode(this, data)
}
}
class LocalClassExitNode(owner: ControlFlowGraph, override val fir: FirRegularClass, level: Int, id: Int) : CFGNodeWithCfgOwner<FirRegularClass>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitLocalClassExitNode(this, data)
}
}
class AnonymousObjectExitNode(owner: ControlFlowGraph, override val fir: FirAnonymousObject, level: Int, id: Int) : CFGNodeWithCfgOwner<FirAnonymousObject>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitAnonymousObjectExitNode(this, data)
}
}
class AnonymousObjectExpressionExitNode(owner: ControlFlowGraph, override val fir: FirAnonymousObjectExpression, level: Int, id: Int) : CFGNode<FirAnonymousObjectExpression>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitAnonymousObjectExpressionExitNode(this, data)
}
}
// ----------------------------------- Initialization -----------------------------------
class PartOfClassInitializationNode(owner: ControlFlowGraph, override val fir: FirControlFlowGraphOwner, level: Int, id: Int) : CFGNodeWithCfgOwner<FirControlFlowGraphOwner>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitPartOfClassInitializationNode(this, data)
}
}
// ----------------------------------- Property -----------------------------------
@OptIn(CfgInternals::class)
class PropertyInitializerEnterNode(owner: ControlFlowGraph, override val fir: FirProperty, level: Int, id: Int) : CFGNode<FirProperty>(owner, level, id),
EnterNodeMarker {
init {
owner.enterNode = this
}
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitPropertyInitializerEnterNode(this, data)
}
}
@OptIn(CfgInternals::class)
class PropertyInitializerExitNode(owner: ControlFlowGraph, override val fir: FirProperty, level: Int, id: Int) : CFGNode<FirProperty>(owner, level, id),
ExitNodeMarker {
init {
owner.exitNode = this
}
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitPropertyInitializerExitNode(this, data)
}
}
// ----------------------------------- Field -----------------------------------
@OptIn(CfgInternals::class)
class FieldInitializerEnterNode(owner: ControlFlowGraph, override val fir: FirField, level: Int, id: Int) : CFGNode<FirField>(owner, level, id),
EnterNodeMarker {
init {
owner.enterNode = this
}
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitFieldInitializerEnterNode(this, data)
}
}
@OptIn(CfgInternals::class)
class FieldInitializerExitNode(owner: ControlFlowGraph, override val fir: FirField, level: Int, id: Int) : CFGNode<FirField>(owner, level, id),
ExitNodeMarker {
init {
owner.exitNode = this
}
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitFieldInitializerExitNode(this, data)
}
}
// ----------------------------------- Init -----------------------------------
@OptIn(CfgInternals::class)
class InitBlockEnterNode(owner: ControlFlowGraph, override val fir: FirAnonymousInitializer, level: Int, id: Int) : CFGNode<FirAnonymousInitializer>(owner, level, id),
EnterNodeMarker {
init {
owner.enterNode = this
}
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitInitBlockEnterNode(this, data)
}
}
@OptIn(CfgInternals::class)
class InitBlockExitNode(owner: ControlFlowGraph, override val fir: FirAnonymousInitializer, level: Int, id: Int) : CFGNode<FirAnonymousInitializer>(owner, level, id),
ExitNodeMarker {
init {
owner.exitNode = this
}
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitInitBlockExitNode(this, data)
}
}
// ----------------------------------- Block -----------------------------------
class BlockEnterNode(owner: ControlFlowGraph, override val fir: FirBlock, level: Int, id: Int) : CFGNode<FirBlock>(owner, level, id),
EnterNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitBlockEnterNode(this, data)
}
}
class BlockExitNode(owner: ControlFlowGraph, override val fir: FirBlock, level: Int, id: Int) : CFGNode<FirBlock>(owner, level, id),
ExitNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitBlockExitNode(this, data)
}
}
// ----------------------------------- When -----------------------------------
class WhenEnterNode(owner: ControlFlowGraph, override val fir: FirWhenExpression, level: Int, id: Int) : CFGNode<FirWhenExpression>(owner, level, id),
EnterNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitWhenEnterNode(this, data)
}
}
class WhenExitNode(owner: ControlFlowGraph, override val fir: FirWhenExpression, level: Int, id: Int) : CFGNode<FirWhenExpression>(owner, level, id),
ExitNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitWhenExitNode(this, data)
}
}
class WhenBranchConditionEnterNode(owner: ControlFlowGraph, override val fir: FirWhenBranch, level: Int, id: Int) : CFGNode<FirWhenBranch>(owner, level, id),
EnterNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitWhenBranchConditionEnterNode(this, data)
}
}
class WhenBranchConditionExitNode(owner: ControlFlowGraph, override val fir: FirWhenBranch, level: Int, id: Int) : CFGNode<FirWhenBranch>(owner, level, id),
ExitNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitWhenBranchConditionExitNode(this, data)
}
}
class WhenBranchResultEnterNode(owner: ControlFlowGraph, override val fir: FirWhenBranch, level: Int, id: Int) : CFGNode<FirWhenBranch>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitWhenBranchResultEnterNode(this, data)
}
}
class WhenBranchResultExitNode(owner: ControlFlowGraph, override val fir: FirWhenBranch, level: Int, id: Int) : CFGNode<FirWhenBranch>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitWhenBranchResultExitNode(this, data)
}
}
class WhenSyntheticElseBranchNode(owner: ControlFlowGraph, override val fir: FirWhenExpression, level: Int, id: Int) : CFGNode<FirWhenExpression>(owner, level, id) {
init {
assert(!fir.isProperlyExhaustive)
}
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitWhenSyntheticElseBranchNode(this, data)
}
}
// ----------------------------------- Loop -----------------------------------
class LoopEnterNode(owner: ControlFlowGraph, override val fir: FirLoop, level: Int, id: Int) : CFGNode<FirLoop>(owner, level, id),
EnterNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitLoopEnterNode(this, data)
}
}
class LoopBlockEnterNode(owner: ControlFlowGraph, override val fir: FirLoop, level: Int, id: Int) : CFGNode<FirLoop>(owner, level, id),
EnterNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitLoopBlockEnterNode(this, data)
}
}
class LoopBlockExitNode(owner: ControlFlowGraph, override val fir: FirLoop, level: Int, id: Int) : CFGNode<FirLoop>(owner, level, id),
ExitNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitLoopBlockExitNode(this, data)
}
}
class LoopConditionEnterNode(owner: ControlFlowGraph, override val fir: FirExpression, val loop: FirLoop, level: Int, id: Int) : CFGNode<FirExpression>(owner, level, id),
EnterNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitLoopConditionEnterNode(this, data)
}
}
class LoopConditionExitNode(owner: ControlFlowGraph, override val fir: FirExpression, level: Int, id: Int) : CFGNode<FirExpression>(owner, level, id),
ExitNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitLoopConditionExitNode(this, data)
}
}
class LoopExitNode(owner: ControlFlowGraph, override val fir: FirLoop, level: Int, id: Int) : CFGNode<FirLoop>(owner, level, id),
ExitNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitLoopExitNode(this, data)
}
}
// ----------------------------------- Try-catch-finally -----------------------------------
class TryExpressionEnterNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int, id: Int) : CFGNode<FirTryExpression>(owner, level, id),
EnterNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitTryExpressionEnterNode(this, data)
}
}
class TryMainBlockEnterNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int, id: Int) : CFGNode<FirTryExpression>(owner, level, id),
EnterNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitTryMainBlockEnterNode(this, data)
}
}
class TryMainBlockExitNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int, id: Int) : CFGNode<FirTryExpression>(owner, level, id),
ExitNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitTryMainBlockExitNode(this, data)
}
}
class CatchClauseEnterNode(owner: ControlFlowGraph, override val fir: FirCatch, level: Int, id: Int) : CFGNode<FirCatch>(owner, level, id),
EnterNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitCatchClauseEnterNode(this, data)
}
}
class CatchClauseExitNode(owner: ControlFlowGraph, override val fir: FirCatch, level: Int, id: Int) : CFGNode<FirCatch>(owner, level, id),
ExitNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitCatchClauseExitNode(this, data)
}
}
class FinallyBlockEnterNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int, id: Int) : CFGNode<FirTryExpression>(owner, level, id),
EnterNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitFinallyBlockEnterNode(this, data)
}
}
class FinallyBlockExitNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int, id: Int) : CFGNode<FirTryExpression>(owner, level, id),
ExitNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitFinallyBlockExitNode(this, data)
}
}
class FinallyProxyEnterNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int, id: Int) : CFGNode<FirTryExpression>(owner, level, id),
EnterNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitFinallyProxyEnterNode(this, data)
}
}
class FinallyProxyExitNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int, id: Int) : CFGNode<FirTryExpression>(owner, level, id),
ExitNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitFinallyProxyExitNode(this, data)
}
}
class TryExpressionExitNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int, id: Int) : CFGNode<FirTryExpression>(owner, level, id),
ExitNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitTryExpressionExitNode(this, data)
}
}
// ----------------------------------- Boolean operators -----------------------------------
abstract class AbstractBinaryExitNode<T : FirElement>(owner: ControlFlowGraph, level: Int, id: Int) : CFGNode<T>(owner, level, id) {
val leftOperandNode: CFGNode<*> get() = previousNodes[0]
val rightOperandNode: CFGNode<*> get() = previousNodes[1]
}
class BinaryAndEnterNode(owner: ControlFlowGraph, override val fir: FirBinaryLogicExpression, level: Int, id: Int) : CFGNode<FirBinaryLogicExpression>(owner, level, id),
EnterNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitBinaryAndEnterNode(this, data)
}
}
class BinaryAndExitLeftOperandNode(owner: ControlFlowGraph, override val fir: FirBinaryLogicExpression, level: Int, id: Int) : CFGNode<FirBinaryLogicExpression>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitBinaryAndExitLeftOperandNode(this, data)
}
}
class BinaryAndEnterRightOperandNode(owner: ControlFlowGraph, override val fir: FirBinaryLogicExpression, level: Int, id: Int) : CFGNode<FirBinaryLogicExpression>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitBinaryAndEnterRightOperandNode(this, data)
}
}
class BinaryAndExitNode(owner: ControlFlowGraph, override val fir: FirBinaryLogicExpression, level: Int, id: Int) : AbstractBinaryExitNode<FirBinaryLogicExpression>(owner, level, id),
ExitNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitBinaryAndExitNode(this, data)
}
}
class BinaryOrEnterNode(owner: ControlFlowGraph, override val fir: FirBinaryLogicExpression, level: Int, id: Int) : CFGNode<FirBinaryLogicExpression>(owner, level, id),
EnterNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitBinaryOrEnterNode(this, data)
}
}
class BinaryOrExitLeftOperandNode(owner: ControlFlowGraph, override val fir: FirBinaryLogicExpression, level: Int, id: Int) : CFGNode<FirBinaryLogicExpression>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitBinaryOrExitLeftOperandNode(this, data)
}
}
class BinaryOrEnterRightOperandNode(owner: ControlFlowGraph, override val fir: FirBinaryLogicExpression, level: Int, id: Int) : CFGNode<FirBinaryLogicExpression>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitBinaryOrEnterRightOperandNode(this, data)
}
}
class BinaryOrExitNode(owner: ControlFlowGraph, override val fir: FirBinaryLogicExpression, level: Int, id: Int) : AbstractBinaryExitNode<FirBinaryLogicExpression>(owner, level, id),
ExitNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitBinaryOrExitNode(this, data)
}
}
// ----------------------------------- Operator call -----------------------------------
class TypeOperatorCallNode(owner: ControlFlowGraph, override val fir: FirTypeOperatorCall, level: Int, id: Int) : CFGNode<FirTypeOperatorCall>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitTypeOperatorCallNode(this, data)
}
}
class ComparisonExpressionNode(owner: ControlFlowGraph, override val fir: FirComparisonExpression, level: Int, id: Int) : CFGNode<FirComparisonExpression>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitComparisonExpressionNode(this, data)
}
}
class EqualityOperatorCallNode(owner: ControlFlowGraph, override val fir: FirEqualityOperatorCall, level: Int, id: Int) : AbstractBinaryExitNode<FirEqualityOperatorCall>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitEqualityOperatorCallNode(this, data)
}
}
// ----------------------------------- Jump -----------------------------------
class JumpNode(owner: ControlFlowGraph, override val fir: FirJump<*>, level: Int, id: Int) : CFGNode<FirJump<*>>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitJumpNode(this, data)
}
}
class ConstExpressionNode(owner: ControlFlowGraph, override val fir: FirConstExpression<*>, level: Int, id: Int) : CFGNode<FirConstExpression<*>>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitConstExpressionNode(this, data)
}
}
// ----------------------------------- Check not null call -----------------------------------
class CheckNotNullCallNode(owner: ControlFlowGraph, override val fir: FirCheckNotNullCall, level: Int, id: Int) : CFGNode<FirCheckNotNullCall>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitCheckNotNullCallNode(this, data)
}
}
// ----------------------------------- Resolvable call -----------------------------------
class QualifiedAccessNode(
owner: ControlFlowGraph,
override val fir: FirQualifiedAccessExpression,
level: Int, id: Int
) : CFGNode<FirQualifiedAccessExpression>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitQualifiedAccessNode(this, data)
}
}
class ResolvedQualifierNode(
owner: ControlFlowGraph,
override val fir: FirResolvedQualifier,
level: Int, id: Int
) : CFGNode<FirResolvedQualifier>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitResolvedQualifierNode(this, data)
}
}
class FunctionCallNode(
owner: ControlFlowGraph,
override val fir: FirFunctionCall,
level: Int, id: Int
) : CFGNode<FirFunctionCall>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitFunctionCallNode(this, data)
}
}
class CallableReferenceNode(
owner: ControlFlowGraph,
override val fir: FirCallableReferenceAccess,
level: Int, id: Int
) : CFGNode<FirCallableReferenceAccess>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitCallableReferenceNode(this, data)
}
}
class GetClassCallNode(owner: ControlFlowGraph, override val fir: FirGetClassCall, level: Int, id: Int) : CFGNode<FirGetClassCall>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitGetClassCallNode(this, data)
}
}
class DelegatedConstructorCallNode(
owner: ControlFlowGraph,
override val fir: FirDelegatedConstructorCall,
level: Int,
id: Int
) : CFGNode<FirDelegatedConstructorCall>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitDelegatedConstructorCallNode(this, data)
}
}
class StringConcatenationCallNode(
owner: ControlFlowGraph,
override val fir: FirStringConcatenationCall,
level: Int,
id: Int
) : CFGNode<FirStringConcatenationCall>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitStringConcatenationCallNode(this, data)
}
}
class ThrowExceptionNode(
owner: ControlFlowGraph,
override val fir: FirThrowExpression,
level: Int, id: Int
) : CFGNode<FirThrowExpression>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitThrowExceptionNode(this, data)
}
}
class StubNode(owner: ControlFlowGraph, level: Int, id: Int) : CFGNode<FirStub>(owner, level, id) {
init {
isDead = true
}
override val fir: FirStub get() = FirStub
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitStubNode(this, data)
}
}
@OptIn(CfgInternals::class)
class ContractDescriptionEnterNode(owner: ControlFlowGraph, level: Int, id: Int) : CFGNode<FirStub>(owner, level, id) {
init {
owner.enterNode = this
}
override val fir: FirStub = FirStub
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitContractDescriptionEnterNode(this, data)
}
}
class VariableDeclarationNode(owner: ControlFlowGraph, override val fir: FirProperty, level: Int, id: Int) : CFGNode<FirProperty>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitVariableDeclarationNode(this, data)
}
}
class VariableAssignmentNode(owner: ControlFlowGraph, override val fir: FirVariableAssignment, level: Int, id: Int) : CFGNode<FirVariableAssignment>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitVariableAssignmentNode(this, data)
}
}
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)
}
}
class ExitSafeCallNode(owner: ControlFlowGraph, override val fir: FirSafeCallExpression, level: Int, id: Int) : CFGNode<FirSafeCallExpression>(owner, level, id) {
val lastPreviousNode: CFGNode<*> get() = previousNodes.last()
val secondPreviousNode: CFGNode<*>? get() = previousNodes.getOrNull(1)
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitExitSafeCallNode(this, data)
}
}
// ----------------------------------- Elvis -----------------------------------
class ElvisLhsExitNode(owner: ControlFlowGraph, override val fir: FirElvisExpression, level: Int, id: Int) : CFGNode<FirElvisExpression>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitElvisLhsExitNode(this, data)
}
}
class ElvisLhsIsNotNullNode(owner: ControlFlowGraph, override val fir: FirElvisExpression, level: Int, id: Int) : CFGNode<FirElvisExpression>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitElvisLhsIsNotNullNode(this, data)
}
}
class ElvisRhsEnterNode(owner: ControlFlowGraph, override val fir: FirElvisExpression, level: Int, id: Int) : CFGNode<FirElvisExpression>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitElvisRhsEnterNode(this, data)
}
}
class ElvisExitNode(owner: ControlFlowGraph, override val fir: FirElvisExpression, level: Int, id: Int) : AbstractBinaryExitNode<FirElvisExpression>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitElvisExitNode(this, data)
}
}
// ----------------------------------- Other -----------------------------------
@OptIn(CfgInternals::class)
class AnnotationEnterNode(owner: ControlFlowGraph, override val fir: FirAnnotation, level: Int, id: Int) : CFGNode<FirAnnotation>(owner, level, id),
EnterNodeMarker {
init {
owner.enterNode = this
}
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitAnnotationEnterNode(this, data)
}
}
@OptIn(CfgInternals::class)
class AnnotationExitNode(owner: ControlFlowGraph, override val fir: FirAnnotation, level: Int, id: Int) : CFGNode<FirAnnotation>(owner, level, id),
ExitNodeMarker {
init {
owner.exitNode = this
}
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitAnnotationExitNode(this, data)
}
}
// ----------------------------------- Stub -----------------------------------
object FirStub : FirElement {
override val source: FirSourceElement? get() = null
override fun <R, D> acceptChildren(visitor: FirVisitor<R, D>, data: D) {}
override fun <D> transformChildren(transformer: FirTransformer<D>, data: D): FirElement {
return this
}
}
@@ -0,0 +1,154 @@
/*
* Copyright 2010-2021 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.cfg
import org.jetbrains.kotlin.fir.FirRenderer
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.FirDoWhileLoop
import org.jetbrains.kotlin.fir.expressions.FirLoop
import org.jetbrains.kotlin.fir.expressions.FirWhileLoop
import org.jetbrains.kotlin.fir.expressions.impl.FirElseIfTrueCondition
import org.jetbrains.kotlin.fir.render
fun CFGNode<*>.render(): String =
buildString {
append(
when (this@render) {
is FunctionEnterNode -> "Enter function \"${fir.name()}\""
is FunctionExitNode -> "Exit function \"${fir.name()}\""
is LocalFunctionDeclarationNode -> "Local function declaration ${owner.name}"
is BlockEnterNode -> "Enter block"
is BlockExitNode -> "Exit block"
is WhenEnterNode -> "Enter when"
is WhenBranchConditionEnterNode -> "Enter when branch condition ${if (fir.condition is FirElseIfTrueCondition) "\"else\"" else ""}"
is WhenBranchConditionExitNode -> "Exit when branch condition"
is WhenBranchResultEnterNode -> "Enter when branch result"
is WhenBranchResultExitNode -> "Exit when branch result"
is WhenSyntheticElseBranchNode -> "Synthetic else branch"
is WhenExitNode -> "Exit when"
is LoopEnterNode -> "Enter ${fir.type()} loop"
is LoopBlockEnterNode -> "Enter loop block"
is LoopBlockExitNode -> "Exit loop block"
is LoopConditionEnterNode -> "Enter loop condition"
is LoopConditionExitNode -> "Exit loop condition"
is LoopExitNode -> "Exit ${fir.type()}loop"
is QualifiedAccessNode -> "Access variable ${fir.calleeReference.render(CfgRenderMode)}"
is ResolvedQualifierNode -> "Access qualifier ${fir.classId}"
is ComparisonExpressionNode -> "Comparison ${fir.operation.operator}"
is TypeOperatorCallNode -> "Type operator: \"${fir.render(CfgRenderMode)}\""
is EqualityOperatorCallNode -> "Equality operator ${fir.operation.operator}"
is JumpNode -> "Jump: ${fir.render()}"
is StubNode -> "Stub"
is CheckNotNullCallNode -> "Check not null: ${fir.render(CfgRenderMode)}"
is ConstExpressionNode -> "Const: ${fir.render()}"
is VariableDeclarationNode ->
"Variable declaration: ${buildString {
FirRenderer(
this,
CfgRenderMode
).visitCallableDeclaration(fir)
}}"
is VariableAssignmentNode -> "Assignment: ${fir.lValue.render(CfgRenderMode)}"
is FunctionCallNode -> "Function call: ${fir.render(CfgRenderMode)}"
is DelegatedConstructorCallNode -> "Delegated constructor call: ${fir.render(CfgRenderMode)}"
is StringConcatenationCallNode -> "String concatenation call: ${fir.render(CfgRenderMode)}"
is ThrowExceptionNode -> "Throw: ${fir.render(CfgRenderMode)}"
is TryExpressionEnterNode -> "Try expression enter"
is TryMainBlockEnterNode -> "Try main block enter"
is TryMainBlockExitNode -> "Try main block exit"
is CatchClauseEnterNode -> "Catch enter"
is CatchClauseExitNode -> "Catch exit"
is FinallyBlockEnterNode -> "Enter finally"
is FinallyBlockExitNode -> "Exit finally"
is FinallyProxyEnterNode -> TODO()
is FinallyProxyExitNode -> TODO()
is TryExpressionExitNode -> "Try expression exit"
is BinaryAndEnterNode -> "Enter &&"
is BinaryAndExitLeftOperandNode -> "Exit left part of &&"
is BinaryAndEnterRightOperandNode -> "Enter right part of &&"
is BinaryAndExitNode -> "Exit &&"
is BinaryOrEnterNode -> "Enter ||"
is BinaryOrExitLeftOperandNode -> "Exit left part of ||"
is BinaryOrEnterRightOperandNode -> "Enter right part of ||"
is BinaryOrExitNode -> "Exit ||"
is PartOfClassInitializationNode -> "Part of class initialization"
is PropertyInitializerEnterNode -> "Enter property"
is PropertyInitializerExitNode -> "Exit property"
is FieldInitializerEnterNode -> "Enter field"
is FieldInitializerExitNode -> "Exit field"
is InitBlockEnterNode -> "Enter init block"
is InitBlockExitNode -> "Exit init block"
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"
is PostponedLambdaEnterNode -> "Postponed enter to lambda"
is PostponedLambdaExitNode -> "Postponed exit from lambda"
is AnonymousFunctionExpressionExitNode -> "Exit anonymous function expression"
is UnionFunctionCallArgumentsNode -> "Call arguments union"
is ClassEnterNode -> "Enter class ${owner.name}"
is ClassExitNode -> "Exit class ${owner.name}"
is LocalClassExitNode -> "Exit local class ${owner.name}"
is AnonymousObjectExitNode -> "Exit anonymous object"
is AnonymousObjectExpressionExitNode -> "Exit anonymous object expression"
is ContractDescriptionEnterNode -> "Enter contract description"
is EnterDefaultArgumentsNode -> "Enter default value of ${fir.name}"
is ExitDefaultArgumentsNode -> "Exit default value of ${fir.name}"
is ElvisLhsExitNode -> "Exit lhs of ?:"
is ElvisLhsIsNotNullNode -> "Lhs of ?: is not null"
is ElvisRhsEnterNode -> "Enter rhs of ?:"
is ElvisExitNode -> "Exit ?:"
is CallableReferenceNode -> "Callable reference: ${fir.render(CfgRenderMode)}"
is GetClassCallNode -> "::class call"
is AbstractBinaryExitNode -> throw IllegalStateException()
},
)
}
private val CfgRenderMode = FirRenderer.RenderMode(
renderLambdaBodies = false,
renderCallArguments = false,
renderCallableFqNames = false,
renderDeclarationResolvePhase = false,
renderAnnotation = false,
)
private fun FirFunction.name(): String = when (this) {
is FirSimpleFunction -> name.asString()
is FirAnonymousFunction -> "anonymousFunction"
is FirConstructor -> "<init>"
is FirPropertyAccessor -> if (isGetter) "getter" else "setter"
is FirErrorFunction -> "errorFunction"
else -> TODO(toString())
}
private fun FirLoop.type(): String = when (this) {
is FirWhileLoop -> "while"
is FirDoWhileLoop -> "do-while"
else -> throw IllegalArgumentException()
}
@@ -0,0 +1,214 @@
/*
* Copyright 2010-2021 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.cfg
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
class ControlFlowGraph(val declaration: FirDeclaration?, val name: String, val kind: Kind) {
private var _nodes: MutableList<CFGNode<*>> = mutableListOf()
val nodes: List<CFGNode<*>>
get() = _nodes
internal fun addNode(node: CFGNode<*>) {
assertState(State.Building)
_nodes.add(node)
}
@set:CfgInternals
lateinit var enterNode: CFGNode<*>
@set:CfgInternals
lateinit var exitNode: CFGNode<*>
var owner: ControlFlowGraph? = null
private set
var state: State = State.Building
private set
private val _subGraphs: MutableList<ControlFlowGraph> = mutableListOf()
val subGraphs: List<ControlFlowGraph> get() = _subGraphs
@CfgInternals
fun complete() {
assertState(State.Building)
state = State.Completed
if (kind == Kind.Stub) return
val sortedNodes = orderNodes()
// TODO Fix this
// assert(sortedNodes.size == _nodes.size)
// for (node in _nodes) {
// assert(node in sortedNodes)
// }
_nodes.clear()
_nodes.addAll(sortedNodes)
}
@CfgInternals
fun addSubGraph(graph: ControlFlowGraph) {
assert(graph.owner == null) {
"SubGraph already has owner"
}
graph.owner = this
_subGraphs += graph
}
@CfgInternals
fun removeSubGraph(graph: ControlFlowGraph) {
assert(graph.owner == this)
_subGraphs.remove(graph)
graph.owner = null
CFGNode.removeAllIncomingEdges(graph.enterNode)
CFGNode.removeAllOutgoingEdges(graph.exitNode)
}
private fun assertState(state: State) {
assert(this.state == state) {
"This action can not be performed at $this state"
}
}
enum class State {
Building,
Completed;
}
enum class Kind(val withBody: Boolean) {
Function(withBody = true),
AnonymousFunction(withBody = true),
ClassInitializer(withBody = true),
PropertyInitializer(withBody = true),
FieldInitializer(withBody = true),
TopLevel(withBody = false),
AnnotationCall(withBody = true),
DefaultArgument(withBody = false),
Stub(withBody = true)
}
}
data class Edge(
val label: EdgeLabel,
val kind: EdgeKind,
) {
companion object {
val Normal_Forward = Edge(NormalPath, EdgeKind.Forward)
private val Normal_DeadForward = Edge(NormalPath, EdgeKind.DeadForward)
private val Normal_DfgForward = Edge(NormalPath, EdgeKind.DfgForward)
private val Normal_CfgForward = Edge(NormalPath, EdgeKind.CfgForward)
private val Normal_CfgBackward = Edge(NormalPath, EdgeKind.CfgBackward)
private val Normal_DeadBackward = Edge(NormalPath, EdgeKind.DeadBackward)
fun create(label: EdgeLabel, kind: EdgeKind): Edge =
when (label) {
NormalPath -> {
when (kind) {
EdgeKind.Forward -> Normal_Forward
EdgeKind.DeadForward -> Normal_DeadForward
EdgeKind.DfgForward -> Normal_DfgForward
EdgeKind.CfgForward -> Normal_CfgForward
EdgeKind.CfgBackward -> Normal_CfgBackward
EdgeKind.DeadBackward -> Normal_DeadBackward
}
}
else -> {
Edge(label, kind)
}
}
}
}
sealed class EdgeLabel(val label: String?) {
open val isNormal: Boolean
get() = false
override fun toString(): String {
return label ?: ""
}
}
object NormalPath : EdgeLabel(label = null) {
override val isNormal: Boolean
get() = true
}
object LoopBackPath : EdgeLabel(label = null) {
override val isNormal: Boolean
get() = true
}
object UncaughtExceptionPath : EdgeLabel(label = "onUncaughtException")
// TODO: Label `return`ing edge with this.
class ReturnPath(
returnTargetSymbol: FirFunctionSymbol<*>
) : EdgeLabel(label = "\"return@${returnTargetSymbol.callableId}\"")
enum class EdgeKind(
val usedInDfa: Boolean,
val usedInCfa: Boolean,
val isBack: Boolean,
val isDead: Boolean
) {
Forward(usedInDfa = true, usedInCfa = true, isBack = false, isDead = false),
DeadForward(usedInDfa = false, usedInCfa = true, isBack = false, isDead = true),
DfgForward(usedInDfa = true, usedInCfa = false, isBack = false, isDead = false),
CfgForward(usedInDfa = false, usedInCfa = true, isBack = false, isDead = false),
CfgBackward(usedInDfa = false, usedInCfa = true, isBack = true, isDead = false),
DeadBackward(usedInDfa = false, usedInCfa = true, isBack = true, isDead = true)
}
@OptIn(ExperimentalStdlibApi::class)
private fun ControlFlowGraph.orderNodes(): LinkedHashSet<CFGNode<*>> {
val visitedNodes = linkedSetOf<CFGNode<*>>()
/*
* [delayedNodes] is needed to accomplish next order contract:
* for each node all previous node lays before it
*/
val stack = ArrayDeque<CFGNode<*>>()
stack.addFirst(enterNode)
while (stack.isNotEmpty()) {
val node = stack.removeFirst()
val previousNodes = node.previousNodes
if (previousNodes.any { it !in visitedNodes && it.owner == this && !node.incomingEdges.getValue(it).kind.isBack }) {
stack.addLast(node)
continue
}
if (!visitedNodes.add(node)) continue
val followingNodes = node.followingNodes
for (followingNode in followingNodes) {
if (followingNode.owner == this) {
if (followingNode !in visitedNodes) {
stack.addFirst(followingNode)
}
} else {
walkThrowSubGraphs(followingNode.owner, visitedNodes, stack)
}
}
}
return visitedNodes
}
@OptIn(ExperimentalStdlibApi::class)
private fun ControlFlowGraph.walkThrowSubGraphs(
otherGraph: ControlFlowGraph,
visitedNodes: Set<CFGNode<*>>,
stack: ArrayDeque<CFGNode<*>>
) {
if (otherGraph.owner != this) return
for (otherNode in otherGraph.exitNode.followingNodes) {
if (otherNode.owner == this) {
if (otherNode !in visitedNodes) {
stack.addFirst(otherNode)
}
} else if (otherNode.owner != otherGraph) {
walkThrowSubGraphs(otherNode.owner, visitedNodes, stack)
}
}
}
@@ -0,0 +1,377 @@
/*
* Copyright 2010-2021 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.cfg
abstract class ControlFlowGraphVisitor<out R, in D> {
abstract fun visitNode(node: CFGNode<*>, data: D): R
// ----------------------------------- Simple function -----------------------------------
open fun visitFunctionEnterNode(node: FunctionEnterNode, data: D): R {
return visitNode(node, data)
}
open fun visitFunctionExitNode(node: FunctionExitNode, data: D): R {
return visitNode(node, data)
}
open fun visitLocalFunctionDeclarationNode(node: LocalFunctionDeclarationNode, data: D): R {
return visitNode(node, data)
}
// ----------------------------------- Default arguments -----------------------------------
open fun visitExitDefaultArgumentsNode(node: ExitDefaultArgumentsNode, data: D): R {
return visitNode(node, data)
}
open fun visitEnterDefaultArgumentsNode(node: EnterDefaultArgumentsNode, data: D): R {
return visitNode(node, data)
}
// ----------------------------------- Anonymous function -----------------------------------
open fun visitPostponedLambdaEnterNode(node: PostponedLambdaEnterNode, data: D): R {
return visitNode(node, data)
}
open fun visitPostponedLambdaExitNode(node: PostponedLambdaExitNode, data: D): R {
return visitNode(node, data)
}
open fun visitUnionFunctionCallArgumentsNode(node: UnionFunctionCallArgumentsNode, data: D): R {
return visitNode(node, data)
}
open fun visitAnonymousFunctionExpressionExitNode(node: AnonymousFunctionExpressionExitNode, data: D): R {
return visitNode(node, data)
}
// ----------------------------------- Classes -----------------------------------
open fun visitAnonymousObjectExitNode(node: AnonymousObjectExitNode, data: D): R {
return visitNode(node, data)
}
open fun visitAnonymousObjectExpressionExitNode(node: AnonymousObjectExpressionExitNode, data: D): R {
return visitNode(node, data)
}
open fun visitClassEnterNode(node: ClassEnterNode, data: D): R {
return visitNode(node, data)
}
open fun visitClassExitNode(node: ClassExitNode, data: D): R {
return visitNode(node, data)
}
open fun visitLocalClassExitNode(node: LocalClassExitNode, data: D): R {
return visitNode(node, data)
}
// ----------------------------------- Initialization -----------------------------------
open fun visitPartOfClassInitializationNode(node: PartOfClassInitializationNode, data: D): R {
return visitNode(node, data)
}
// ----------------------------------- Property -----------------------------------
open fun visitPropertyInitializerEnterNode(node: PropertyInitializerEnterNode, data: D): R {
return visitNode(node, data)
}
open fun visitPropertyInitializerExitNode(node: PropertyInitializerExitNode, data: D): R {
return visitNode(node, data)
}
// ----------------------------------- Field -----------------------------------
open fun visitFieldInitializerEnterNode(node: FieldInitializerEnterNode, data: D): R {
return visitNode(node, data)
}
open fun visitFieldInitializerExitNode(node: FieldInitializerExitNode, data: D): R {
return visitNode(node, data)
}
// ----------------------------------- Init -----------------------------------
open fun visitInitBlockEnterNode(node: InitBlockEnterNode, data: D): R {
return visitNode(node, data)
}
open fun visitInitBlockExitNode(node: InitBlockExitNode, data: D): R {
return visitNode(node, data)
}
// ----------------------------------- Block -----------------------------------
open fun visitBlockEnterNode(node: BlockEnterNode, data: D): R {
return visitNode(node, data)
}
open fun visitBlockExitNode(node: BlockExitNode, data: D): R {
return visitNode(node, data)
}
// ----------------------------------- When -----------------------------------
open fun visitWhenEnterNode(node: WhenEnterNode, data: D): R {
return visitNode(node, data)
}
open fun visitWhenExitNode(node: WhenExitNode, data: D): R {
return visitNode(node, data)
}
open fun visitWhenBranchConditionEnterNode(node: WhenBranchConditionEnterNode, data: D): R {
return visitNode(node, data)
}
open fun visitWhenBranchConditionExitNode(node: WhenBranchConditionExitNode, data: D): R {
return visitNode(node, data)
}
open fun visitWhenBranchResultEnterNode(node: WhenBranchResultEnterNode, data: D): R {
return visitNode(node, data)
}
open fun visitWhenBranchResultExitNode(node: WhenBranchResultExitNode, data: D): R {
return visitNode(node, data)
}
open fun visitWhenSyntheticElseBranchNode(node: WhenSyntheticElseBranchNode, data: D): R {
return visitNode(node, data)
}
// ----------------------------------- Loop -----------------------------------
open fun visitLoopEnterNode(node: LoopEnterNode, data: D): R {
return visitNode(node, data)
}
open fun visitLoopBlockEnterNode(node: LoopBlockEnterNode, data: D): R {
return visitNode(node, data)
}
open fun visitLoopBlockExitNode(node: LoopBlockExitNode, data: D): R {
return visitNode(node, data)
}
open fun visitLoopConditionEnterNode(node: LoopConditionEnterNode, data: D): R {
return visitNode(node, data)
}
open fun visitLoopConditionExitNode(node: LoopConditionExitNode, data: D): R {
return visitNode(node, data)
}
open fun visitLoopExitNode(node: LoopExitNode, data: D): R {
return visitNode(node, data)
}
// ----------------------------------- Try-catch-finally -----------------------------------
open fun visitTryExpressionEnterNode(node: TryExpressionEnterNode, data: D): R {
return visitNode(node, data)
}
open fun visitTryMainBlockEnterNode(node: TryMainBlockEnterNode, data: D): R {
return visitNode(node, data)
}
open fun visitTryMainBlockExitNode(node: TryMainBlockExitNode, data: D): R {
return visitNode(node, data)
}
open fun visitCatchClauseEnterNode(node: CatchClauseEnterNode, data: D): R {
return visitNode(node, data)
}
open fun visitCatchClauseExitNode(node: CatchClauseExitNode, data: D): R {
return visitNode(node, data)
}
open fun visitFinallyBlockEnterNode(node: FinallyBlockEnterNode, data: D): R {
return visitNode(node, data)
}
open fun visitFinallyBlockExitNode(node: FinallyBlockExitNode, data: D): R {
return visitNode(node, data)
}
open fun visitFinallyProxyEnterNode(node: FinallyProxyEnterNode, data: D): R {
return visitNode(node, data)
}
open fun visitFinallyProxyExitNode(node: FinallyProxyExitNode, data: D): R {
return visitNode(node, data)
}
open fun visitTryExpressionExitNode(node: TryExpressionExitNode, data: D): R {
return visitNode(node, data)
}
// ----------------------------------- Boolean operators -----------------------------------
open fun visitBinaryAndEnterNode(node: BinaryAndEnterNode, data: D): R {
return visitNode(node, data)
}
open fun visitBinaryAndExitLeftOperandNode(node: BinaryAndExitLeftOperandNode, data: D): R {
return visitNode(node, data)
}
open fun visitBinaryAndEnterRightOperandNode(node: BinaryAndEnterRightOperandNode, data: D): R {
return visitNode(node, data)
}
open fun visitBinaryAndExitNode(node: BinaryAndExitNode, data: D): R {
return visitNode(node, data)
}
open fun visitBinaryOrEnterNode(node: BinaryOrEnterNode, data: D): R {
return visitNode(node, data)
}
open fun visitBinaryOrExitLeftOperandNode(node: BinaryOrExitLeftOperandNode, data: D): R {
return visitNode(node, data)
}
open fun visitBinaryOrEnterRightOperandNode(node: BinaryOrEnterRightOperandNode, data: D): R {
return visitNode(node, data)
}
open fun visitBinaryOrExitNode(node: BinaryOrExitNode, data: D): R {
return visitNode(node, data)
}
// ----------------------------------- Operator call -----------------------------------
open fun visitTypeOperatorCallNode(node: TypeOperatorCallNode, data: D): R {
return visitNode(node, data)
}
open fun visitComparisonExpressionNode(node: ComparisonExpressionNode, data: D): R {
return visitNode(node, data)
}
open fun visitEqualityOperatorCallNode(node: EqualityOperatorCallNode, data: D): R {
return visitNode(node, data)
}
// ----------------------------------- Jump -----------------------------------
open fun visitJumpNode(node: JumpNode, data: D): R {
return visitNode(node, data)
}
open fun visitConstExpressionNode(node: ConstExpressionNode, data: D): R {
return visitNode(node, data)
}
// ----------------------------------- Check not null call -----------------------------------
open fun visitCheckNotNullCallNode(node: CheckNotNullCallNode, data: D): R {
return visitNode(node, data)
}
// ----------------------------------- Resolvable call -----------------------------------
open fun visitQualifiedAccessNode(node: QualifiedAccessNode, data: D): R {
return visitNode(node, data)
}
open fun visitResolvedQualifierNode(node: ResolvedQualifierNode, data: D): R {
return visitNode(node, data)
}
open fun visitFunctionCallNode(node: FunctionCallNode, data: D): R {
return visitNode(node, data)
}
open fun visitCallableReferenceNode(node: CallableReferenceNode, data: D): R {
return visitNode(node, data)
}
open fun visitGetClassCallNode(node: GetClassCallNode, data: D): R {
return visitNode(node, data)
}
open fun visitDelegatedConstructorCallNode(node: DelegatedConstructorCallNode, data: D): R {
return visitNode(node, data)
}
open fun visitStringConcatenationCallNode(node: StringConcatenationCallNode, data: D): R {
return visitNode(node, data)
}
open fun visitThrowExceptionNode(node: ThrowExceptionNode, data: D): R {
return visitNode(node, data)
}
open fun visitStubNode(node: StubNode, data: D): R {
return visitNode(node, data)
}
open fun visitContractDescriptionEnterNode(node: ContractDescriptionEnterNode, data: D): R {
return visitNode(node, data)
}
open fun visitVariableDeclarationNode(node: VariableDeclarationNode, data: D): R {
return visitNode(node, data)
}
open fun visitVariableAssignmentNode(node: VariableAssignmentNode, data: D): R {
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)
}
open fun visitExitSafeCallNode(node: ExitSafeCallNode, data: D): R {
return visitNode(node, data)
}
// ----------------------------------- Elvis -----------------------------------
open fun visitElvisLhsExitNode(node: ElvisLhsExitNode, data: D): R {
return visitNode(node, data)
}
open fun visitElvisLhsIsNotNullNode(node: ElvisLhsIsNotNullNode, data: D): R {
return visitNode(node, data)
}
open fun visitElvisRhsEnterNode(node: ElvisRhsEnterNode, data: D): R {
return visitNode(node, data)
}
open fun visitElvisExitNode(node: ElvisExitNode, data: D): R {
return visitNode(node, data)
}
// ----------------------------------- Other -----------------------------------
open fun visitAnnotationEnterNode(node: AnnotationEnterNode, data: D): R {
return visitNode(node, data)
}
open fun visitAnnotationExitNode(node: AnnotationExitNode, data: D): R {
return visitNode(node, data)
}
}
@@ -0,0 +1,590 @@
/*
* Copyright 2010-2021 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.cfg
abstract class ControlFlowGraphVisitorVoid : ControlFlowGraphVisitor<Unit, Nothing?>() {
abstract fun visitNode(node: CFGNode<*>)
// ----------------------------------- Simple function -----------------------------------
open fun visitFunctionEnterNode(node: FunctionEnterNode) {
visitNode(node)
}
open fun visitFunctionExitNode(node: FunctionExitNode) {
visitNode(node)
}
// ----------------------------------- Anonymous function -----------------------------------
open fun visitPostponedLambdaEnterNode(node: PostponedLambdaEnterNode) {
visitNode(node)
}
open fun visitPostponedLambdaExitNode(node: PostponedLambdaExitNode) {
visitNode(node)
}
open fun visitUnionFunctionCallArgumentsNode(node: UnionFunctionCallArgumentsNode) {
visitNode(node)
}
// ----------------------------------- Anonymous object -----------------------------------
open fun visitAnonymousObjectExitNode(node: AnonymousObjectExitNode) {
visitNode(node)
}
// ----------------------------------- Property -----------------------------------
open fun visitPropertyInitializerEnterNode(node: PropertyInitializerEnterNode) {
visitNode(node)
}
open fun visitPropertyInitializerExitNode(node: PropertyInitializerExitNode) {
visitNode(node)
}
// ----------------------------------- Init -----------------------------------
open fun visitInitBlockEnterNode(node: InitBlockEnterNode) {
visitNode(node)
}
open fun visitInitBlockExitNode(node: InitBlockExitNode) {
visitNode(node)
}
// ----------------------------------- Block -----------------------------------
open fun visitBlockEnterNode(node: BlockEnterNode) {
visitNode(node)
}
open fun visitBlockExitNode(node: BlockExitNode) {
visitNode(node)
}
// ----------------------------------- When -----------------------------------
open fun visitWhenEnterNode(node: WhenEnterNode) {
visitNode(node)
}
open fun visitWhenExitNode(node: WhenExitNode) {
visitNode(node)
}
open fun visitWhenBranchConditionEnterNode(node: WhenBranchConditionEnterNode) {
visitNode(node)
}
open fun visitWhenBranchConditionExitNode(node: WhenBranchConditionExitNode) {
visitNode(node)
}
open fun visitWhenBranchResultEnterNode(node: WhenBranchResultEnterNode) {
visitNode(node)
}
open fun visitWhenBranchResultExitNode(node: WhenBranchResultExitNode) {
visitNode(node)
}
open fun visitWhenSyntheticElseBranchNode(node: WhenSyntheticElseBranchNode) {
visitNode(node)
}
// ----------------------------------- Loop -----------------------------------
open fun visitLoopEnterNode(node: LoopEnterNode) {
visitNode(node)
}
open fun visitLoopBlockEnterNode(node: LoopBlockEnterNode) {
visitNode(node)
}
open fun visitLoopBlockExitNode(node: LoopBlockExitNode) {
visitNode(node)
}
open fun visitLoopConditionEnterNode(node: LoopConditionEnterNode) {
visitNode(node)
}
open fun visitLoopConditionExitNode(node: LoopConditionExitNode) {
visitNode(node)
}
open fun visitLoopExitNode(node: LoopExitNode) {
visitNode(node)
}
// ----------------------------------- Try-catch-finally -----------------------------------
open fun visitTryExpressionEnterNode(node: TryExpressionEnterNode) {
visitNode(node)
}
open fun visitTryMainBlockEnterNode(node: TryMainBlockEnterNode) {
visitNode(node)
}
open fun visitTryMainBlockExitNode(node: TryMainBlockExitNode) {
visitNode(node)
}
open fun visitCatchClauseEnterNode(node: CatchClauseEnterNode) {
visitNode(node)
}
open fun visitCatchClauseExitNode(node: CatchClauseExitNode) {
visitNode(node)
}
open fun visitFinallyBlockEnterNode(node: FinallyBlockEnterNode) {
visitNode(node)
}
open fun visitFinallyBlockExitNode(node: FinallyBlockExitNode) {
visitNode(node)
}
open fun visitFinallyProxyEnterNode(node: FinallyProxyEnterNode) {
visitNode(node)
}
open fun visitFinallyProxyExitNode(node: FinallyProxyExitNode) {
visitNode(node)
}
open fun visitTryExpressionExitNode(node: TryExpressionExitNode) {
visitNode(node)
}
// ----------------------------------- Boolean operators -----------------------------------
open fun visitBinaryAndEnterNode(node: BinaryAndEnterNode) {
visitNode(node)
}
open fun visitBinaryAndExitLeftOperandNode(node: BinaryAndExitLeftOperandNode) {
visitNode(node)
}
open fun visitBinaryAndEnterRightOperandNode(node: BinaryAndEnterRightOperandNode) {
visitNode(node)
}
open fun visitBinaryAndExitNode(node: BinaryAndExitNode) {
visitNode(node)
}
open fun visitBinaryOrEnterNode(node: BinaryOrEnterNode) {
visitNode(node)
}
open fun visitBinaryOrExitLeftOperandNode(node: BinaryOrExitLeftOperandNode) {
visitNode(node)
}
open fun visitBinaryOrEnterRightOperandNode(node: BinaryOrEnterRightOperandNode) {
visitNode(node)
}
open fun visitBinaryOrExitNode(node: BinaryOrExitNode) {
visitNode(node)
}
// ----------------------------------- Operator call -----------------------------------
open fun visitTypeOperatorCallNode(node: TypeOperatorCallNode) {
visitNode(node)
}
open fun visitComparisonExpressionNode(node: ComparisonExpressionNode) {
visitNode(node)
}
open fun visitEqualityOperatorCallNode(node: EqualityOperatorCallNode) {
visitNode(node)
}
// ----------------------------------- Jump -----------------------------------
open fun visitJumpNode(node: JumpNode) {
visitNode(node)
}
open fun visitConstExpressionNode(node: ConstExpressionNode) {
visitNode(node)
}
// ----------------------------------- Check not null call -----------------------------------
open fun visitCheckNotNullCallNode(node: CheckNotNullCallNode) {
visitNode(node)
}
// ----------------------------------- Resolvable call -----------------------------------
open fun visitQualifiedAccessNode(node: QualifiedAccessNode) {
visitNode(node)
}
open fun visitResolvedQualifierNode(node: ResolvedQualifierNode) {
visitNode(node)
}
open fun visitFunctionCallNode(node: FunctionCallNode) {
visitNode(node)
}
open fun visitDelegatedConstructorCallNode(node: DelegatedConstructorCallNode) {
visitNode(node)
}
open fun visitStringConcatenationCallNode(node: StringConcatenationCallNode) {
visitNode(node)
}
open fun visitThrowExceptionNode(node: ThrowExceptionNode) {
visitNode(node)
}
open fun visitStubNode(node: StubNode) {
visitNode(node)
}
open fun visitVariableDeclarationNode(node: VariableDeclarationNode) {
visitNode(node)
}
open fun visitVariableAssignmentNode(node: VariableAssignmentNode) {
visitNode(node)
}
open fun visitEnterContractNode(node: EnterContractNode) {
visitNode(node)
}
open fun visitExitContractNode(node: ExitContractNode) {
visitNode(node)
}
open fun visitEnterSafeCallNode(node: EnterSafeCallNode) {
visitNode(node)
}
open fun visitExitSafeCallNode(node: ExitSafeCallNode) {
visitNode(node)
}
// ----------------------------------- Other -----------------------------------
open fun visitAnnotationEnterNode(node: AnnotationEnterNode) {
visitNode(node)
}
open fun visitAnnotationExitNode(node: AnnotationExitNode) {
visitNode(node)
}
// ---------------------------------------------------------------------------------------------------------------------
final override fun visitNode(node: CFGNode<*>, data: Nothing?) {
visitNode(node)
}
// ----------------------------------- Simple function -----------------------------------
final override fun visitFunctionEnterNode(node: FunctionEnterNode, data: Nothing?) {
visitFunctionEnterNode(node)
}
final override fun visitFunctionExitNode(node: FunctionExitNode, data: Nothing?) {
visitFunctionExitNode(node)
}
// ----------------------------------- Anonymous function -----------------------------------
final override fun visitPostponedLambdaEnterNode(node: PostponedLambdaEnterNode, data: Nothing?) {
visitPostponedLambdaEnterNode(node)
}
final override fun visitPostponedLambdaExitNode(node: PostponedLambdaExitNode, data: Nothing?) {
visitPostponedLambdaExitNode(node)
}
final override fun visitUnionFunctionCallArgumentsNode(node: UnionFunctionCallArgumentsNode, data: Nothing?) {
visitUnionFunctionCallArgumentsNode(node)
}
// ----------------------------------- Anonymous object -----------------------------------
final override fun visitAnonymousObjectExitNode(node: AnonymousObjectExitNode, data: Nothing?) {
visitAnonymousObjectExitNode(node)
}
// ----------------------------------- Property -----------------------------------
final override fun visitPropertyInitializerEnterNode(node: PropertyInitializerEnterNode, data: Nothing?) {
visitPropertyInitializerEnterNode(node)
}
final override fun visitPropertyInitializerExitNode(node: PropertyInitializerExitNode, data: Nothing?) {
visitPropertyInitializerExitNode(node)
}
// ----------------------------------- Init -----------------------------------
final override fun visitInitBlockEnterNode(node: InitBlockEnterNode, data: Nothing?) {
visitInitBlockEnterNode(node)
}
final override fun visitInitBlockExitNode(node: InitBlockExitNode, data: Nothing?) {
visitInitBlockExitNode(node)
}
// ----------------------------------- Block -----------------------------------
final override fun visitBlockEnterNode(node: BlockEnterNode, data: Nothing?) {
visitBlockEnterNode(node)
}
final override fun visitBlockExitNode(node: BlockExitNode, data: Nothing?) {
visitBlockExitNode(node)
}
// ----------------------------------- When -----------------------------------
final override fun visitWhenEnterNode(node: WhenEnterNode, data: Nothing?) {
visitWhenEnterNode(node)
}
final override fun visitWhenExitNode(node: WhenExitNode, data: Nothing?) {
visitWhenExitNode(node)
}
final override fun visitWhenBranchConditionEnterNode(node: WhenBranchConditionEnterNode, data: Nothing?) {
visitWhenBranchConditionEnterNode(node)
}
final override fun visitWhenBranchConditionExitNode(node: WhenBranchConditionExitNode, data: Nothing?) {
visitWhenBranchConditionExitNode(node)
}
final override fun visitWhenBranchResultEnterNode(node: WhenBranchResultEnterNode, data: Nothing?) {
visitWhenBranchResultEnterNode(node)
}
final override fun visitWhenBranchResultExitNode(node: WhenBranchResultExitNode, data: Nothing?) {
visitWhenBranchResultExitNode(node)
}
final override fun visitWhenSyntheticElseBranchNode(node: WhenSyntheticElseBranchNode, data: Nothing?) {
visitWhenSyntheticElseBranchNode(node)
}
// ----------------------------------- Loop -----------------------------------
final override fun visitLoopEnterNode(node: LoopEnterNode, data: Nothing?) {
visitLoopEnterNode(node)
}
final override fun visitLoopBlockEnterNode(node: LoopBlockEnterNode, data: Nothing?) {
visitLoopBlockEnterNode(node)
}
final override fun visitLoopBlockExitNode(node: LoopBlockExitNode, data: Nothing?) {
visitLoopBlockExitNode(node)
}
final override fun visitLoopConditionEnterNode(node: LoopConditionEnterNode, data: Nothing?) {
visitLoopConditionEnterNode(node)
}
final override fun visitLoopConditionExitNode(node: LoopConditionExitNode, data: Nothing?) {
visitLoopConditionExitNode(node)
}
final override fun visitLoopExitNode(node: LoopExitNode, data: Nothing?) {
visitLoopExitNode(node)
}
// ----------------------------------- Try-catch-finally -----------------------------------
final override fun visitTryExpressionEnterNode(node: TryExpressionEnterNode, data: Nothing?) {
visitTryExpressionEnterNode(node)
}
final override fun visitTryMainBlockEnterNode(node: TryMainBlockEnterNode, data: Nothing?) {
visitTryMainBlockEnterNode(node)
}
final override fun visitTryMainBlockExitNode(node: TryMainBlockExitNode, data: Nothing?) {
visitTryMainBlockExitNode(node)
}
final override fun visitCatchClauseEnterNode(node: CatchClauseEnterNode, data: Nothing?) {
visitCatchClauseEnterNode(node)
}
final override fun visitCatchClauseExitNode(node: CatchClauseExitNode, data: Nothing?) {
visitCatchClauseExitNode(node)
}
final override fun visitFinallyBlockEnterNode(node: FinallyBlockEnterNode, data: Nothing?) {
visitFinallyBlockEnterNode(node)
}
final override fun visitFinallyBlockExitNode(node: FinallyBlockExitNode, data: Nothing?) {
visitFinallyBlockExitNode(node)
}
final override fun visitFinallyProxyEnterNode(node: FinallyProxyEnterNode, data: Nothing?) {
visitFinallyProxyEnterNode(node)
}
final override fun visitFinallyProxyExitNode(node: FinallyProxyExitNode, data: Nothing?) {
visitFinallyProxyExitNode(node)
}
final override fun visitTryExpressionExitNode(node: TryExpressionExitNode, data: Nothing?) {
visitTryExpressionExitNode(node)
}
// ----------------------------------- Boolean operators -----------------------------------
final override fun visitBinaryAndEnterNode(node: BinaryAndEnterNode, data: Nothing?) {
visitBinaryAndEnterNode(node)
}
final override fun visitBinaryAndExitLeftOperandNode(node: BinaryAndExitLeftOperandNode, data: Nothing?) {
visitBinaryAndExitLeftOperandNode(node)
}
final override fun visitBinaryAndEnterRightOperandNode(node: BinaryAndEnterRightOperandNode, data: Nothing?) {
visitBinaryAndEnterRightOperandNode(node)
}
final override fun visitBinaryAndExitNode(node: BinaryAndExitNode, data: Nothing?) {
visitBinaryAndExitNode(node)
}
final override fun visitBinaryOrEnterNode(node: BinaryOrEnterNode, data: Nothing?) {
visitBinaryOrEnterNode(node)
}
final override fun visitBinaryOrExitLeftOperandNode(node: BinaryOrExitLeftOperandNode, data: Nothing?) {
visitBinaryOrExitLeftOperandNode(node)
}
final override fun visitBinaryOrEnterRightOperandNode(node: BinaryOrEnterRightOperandNode, data: Nothing?) {
visitBinaryOrEnterRightOperandNode(node)
}
final override fun visitBinaryOrExitNode(node: BinaryOrExitNode, data: Nothing?) {
visitBinaryOrExitNode(node)
}
// ----------------------------------- Operator call -----------------------------------
final override fun visitTypeOperatorCallNode(node: TypeOperatorCallNode, data: Nothing?) {
visitTypeOperatorCallNode(node)
}
final override fun visitEqualityOperatorCallNode(node: EqualityOperatorCallNode, data: Nothing?) {
visitEqualityOperatorCallNode(node)
}
final override fun visitComparisonExpressionNode(node: ComparisonExpressionNode, data: Nothing?) {
visitComparisonExpressionNode(node)
}
// ----------------------------------- Jump -----------------------------------
final override fun visitJumpNode(node: JumpNode, data: Nothing?) {
visitJumpNode(node)
}
final override fun visitConstExpressionNode(node: ConstExpressionNode, data: Nothing?) {
visitConstExpressionNode(node)
}
// ----------------------------------- Check not null call -----------------------------------
final override fun visitCheckNotNullCallNode(node: CheckNotNullCallNode, data: Nothing?) {
visitCheckNotNullCallNode(node)
}
// ----------------------------------- Resolvable call -----------------------------------
final override fun visitQualifiedAccessNode(node: QualifiedAccessNode, data: Nothing?) {
visitQualifiedAccessNode(node)
}
final override fun visitResolvedQualifierNode(node: ResolvedQualifierNode, data: Nothing?) {
visitResolvedQualifierNode(node)
}
final override fun visitFunctionCallNode(node: FunctionCallNode, data: Nothing?) {
visitFunctionCallNode(node)
}
final override fun visitDelegatedConstructorCallNode(node: DelegatedConstructorCallNode, data: Nothing?) {
visitDelegatedConstructorCallNode(node)
}
final override fun visitStringConcatenationCallNode(node: StringConcatenationCallNode, data: Nothing?) {
visitStringConcatenationCallNode(node)
}
final override fun visitThrowExceptionNode(node: ThrowExceptionNode, data: Nothing?) {
visitThrowExceptionNode(node)
}
final override fun visitStubNode(node: StubNode, data: Nothing?) {
visitStubNode(node)
}
final override fun visitVariableDeclarationNode(node: VariableDeclarationNode, data: Nothing?) {
visitVariableDeclarationNode(node)
}
final override fun visitVariableAssignmentNode(node: VariableAssignmentNode, data: Nothing?) {
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)
}
final override fun visitExitSafeCallNode(node: ExitSafeCallNode, data: Nothing?) {
visitExitSafeCallNode(node)
}
// ----------------------------------- Other -----------------------------------
final override fun visitAnnotationEnterNode(node: AnnotationEnterNode, data: Nothing?) {
visitAnnotationEnterNode(node)
}
final override fun visitAnnotationExitNode(node: AnnotationExitNode, data: Nothing?) {
visitAnnotationExitNode(node)
}
}
@@ -0,0 +1,118 @@
/*
* Copyright 2010-2021 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.types.ConeClassErrorType
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
// --------------------------------------- Facts ---------------------------------------
operator fun TypeStatement.plus(other: TypeStatement?): TypeStatement = other?.let { this + other } ?: this
class MutableTypeStatement(
override val variable: RealVariable,
override val exactType: MutableSet<ConeKotlinType> = linkedSetOf(),
override val exactNotType: MutableSet<ConeKotlinType> = linkedSetOf()
) : TypeStatement() {
override fun plus(other: TypeStatement): MutableTypeStatement = MutableTypeStatement(
variable,
LinkedHashSet(exactType).apply { addAll(other.exactType) },
LinkedHashSet(exactNotType).apply { addAll(other.exactNotType) }
)
override val isEmpty: Boolean
get() = exactType.isEmpty() && exactType.isEmpty()
override fun invert(): MutableTypeStatement {
return MutableTypeStatement(
variable,
LinkedHashSet(exactNotType),
LinkedHashSet(exactType)
)
}
operator fun plusAssign(info: TypeStatement) {
exactType += info.exactType
exactNotType += info.exactNotType
}
fun copy(): MutableTypeStatement = MutableTypeStatement(variable, LinkedHashSet(exactType), LinkedHashSet(exactNotType))
}
fun Implication.invertCondition(): Implication = Implication(condition.invert(), effect)
// --------------------------------------- Aliases ---------------------------------------
typealias TypeStatements = Map<RealVariable, TypeStatement>
typealias MutableTypeStatements = MutableMap<RealVariable, MutableTypeStatement>
typealias MutableOperationStatements = MutableMap<RealVariable, MutableTypeStatement>
fun MutableTypeStatements.addStatement(variable: RealVariable, statement: TypeStatement) {
put(variable, statement.asMutableStatement()) { it.apply { this += statement } }
}
fun MutableTypeStatements.mergeTypeStatements(other: TypeStatements) {
other.forEach { (variable, info) ->
addStatement(variable, info)
}
}
// --------------------------------------- DSL ---------------------------------------
infix fun DataFlowVariable.eq(constant: Boolean?): OperationStatement {
val condition = when (constant) {
true -> Operation.EqTrue
false -> Operation.EqFalse
null -> Operation.EqNull
}
return OperationStatement(this, condition)
}
infix fun DataFlowVariable.notEq(constant: Boolean?): OperationStatement {
val condition = when (constant) {
true -> Operation.EqFalse
false -> Operation.EqTrue
null -> Operation.NotEqNull
}
return OperationStatement(this, condition)
}
infix fun OperationStatement.implies(effect: Statement<*>): Implication = Implication(this, effect)
infix fun RealVariable.typeEq(type: ConeKotlinType): TypeStatement =
if (type !is ConeClassErrorType) {
MutableTypeStatement(this, linkedSetOf<ConeKotlinType>().apply { this += type }, HashSet())
} else {
MutableTypeStatement(this)
}
infix fun RealVariable.typeNotEq(type: ConeKotlinType): TypeStatement =
if (type !is ConeClassErrorType) {
MutableTypeStatement(this, linkedSetOf(), LinkedHashSet<ConeKotlinType>().apply { this += type })
} else {
MutableTypeStatement(this)
}
// --------------------------------------- Utils ---------------------------------------
@OptIn(ExperimentalContracts::class)
fun DataFlowVariable.isSynthetic(): Boolean {
contract {
returns(true) implies (this@isSynthetic is SyntheticVariable)
}
return this is SyntheticVariable
}
@OptIn(ExperimentalContracts::class)
fun DataFlowVariable.isReal(): Boolean {
contract {
returns(true) implies (this@isReal is RealVariable)
}
return this is RealVariable
}
@@ -0,0 +1,71 @@
/*
* Copyright 2010-2021 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.types.ConeKotlinType
sealed class Statement<T : Statement<T>> {
abstract fun invert(): T
abstract val variable: DataFlowVariable
}
/*
* Examples:
* d == Null
* d != Null
* d == True
* d == False
*/
data class OperationStatement(override val variable: DataFlowVariable, val operation: Operation) : Statement<OperationStatement>() {
override fun invert(): OperationStatement {
return OperationStatement(variable, operation.invert())
}
override fun toString(): String {
return "$variable $operation"
}
}
abstract class TypeStatement : Statement<TypeStatement>() {
abstract override val variable: RealVariable
abstract val exactType: Set<ConeKotlinType>
abstract val exactNotType: Set<ConeKotlinType>
abstract operator fun plus(other: TypeStatement): TypeStatement
abstract val isEmpty: Boolean
val isNotEmpty: Boolean get() = !isEmpty
override fun toString(): String {
return "$variable: $exactType, $exactNotType"
}
}
class Implication(
val condition: OperationStatement,
val effect: Statement<*>
) {
override fun toString(): String {
return "$condition -> $effect"
}
}
enum class Operation {
EqTrue, EqFalse, EqNull, NotEqNull;
fun invert(): Operation = when (this) {
EqTrue -> EqFalse
EqFalse -> EqTrue
EqNull -> NotEqNull
NotEqNull -> EqNull
}
override fun toString(): String = when (this) {
EqTrue -> "== True"
EqFalse -> "== False"
EqNull -> "== Null"
NotEqNull -> "!= Null"
}
}
@@ -0,0 +1,121 @@
/*
* Copyright 2010-2021 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.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
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.FirAccessorSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@OptIn(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))
}
}
@OptIn(ExperimentalContracts::class)
internal inline fun <K, V> PersistentMap<K, V>.put(
key: K,
valueProducer: () -> V,
remappingFunction: (existing: V) -> V
): PersistentMap<K, V> {
contract {
callsInPlace(remappingFunction, InvocationKind.AT_MOST_ONCE)
callsInPlace(valueProducer, InvocationKind.AT_MOST_ONCE)
}
val existing = this[key]
return if (existing == null) {
put(key, valueProducer())
} else {
put(key, remappingFunction(existing))
}
}
@DfaInternals
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")
}
@DfaInternals
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")
}
}
fun FirFunctionCall.isBooleanNot(): Boolean {
val symbol = calleeReference.safeAs<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
@DfaInternals
val FirElement.symbol: FirBasedSymbol<*>?
get() = when (this) {
is FirResolvable -> symbol
is FirDeclaration -> symbol
is FirWhenSubjectExpression -> whenRef.value.subject?.symbol
is FirSafeCallExpression -> regularQualifiedAccess.symbol
else -> null
}?.takeIf {
(this as? FirExpression)?.unwrapSmartcastExpression() is FirThisReceiverExpression ||
(it !is FirFunctionSymbol<*> && it !is FirAccessorSymbol)
}
@DfaInternals
internal val FirResolvable.symbol: FirBasedSymbol<*>?
get() = when (val reference = calleeReference) {
is FirThisReference -> reference.boundSymbol
is FirResolvedNamedReference -> reference.resolvedSymbol
is FirSimpleNamedReference -> reference.candidateSymbol
else -> null
}
fun FirExpression.unwrapSmartcastExpression(): FirExpression =
when (this) {
is FirExpressionWithSmartcast -> originalExpression
else -> this
}
@@ -0,0 +1,229 @@
/*
* 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.diagnostics
import kotlinx.collections.immutable.ImmutableList
import org.jetbrains.kotlin.resolve.deprecation.DeprecationInfo
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration
import org.jetbrains.kotlin.fir.declarations.FirVariable
import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic
import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnosticWithSource
import org.jetbrains.kotlin.fir.render
import org.jetbrains.kotlin.fir.resolve.calls.AbstractCandidate
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.FirTypeRef
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.calls.tower.CandidateApplicability
sealed interface ConeUnresolvedError : ConeDiagnostic {
val qualifier: String?
}
interface ConeDiagnosticWithCandidates : ConeDiagnostic {
val candidateSymbols: Collection<FirBasedSymbol<*>>
}
interface ConeDiagnosticWithSingleCandidate : ConeDiagnosticWithCandidates {
override val candidateSymbols: Collection<FirBasedSymbol<*>> get() = listOf(candidateSymbol)
val candidateSymbol: FirBasedSymbol<*>
}
class ConeUnresolvedReferenceError(val name: Name? = null) : ConeUnresolvedError {
override val qualifier: String? get() = name?.asString()
override val reason: String get() = "Unresolved reference" + if (name != null) ": ${name.asString()}" else ""
}
class ConeUnresolvedSymbolError(val classId: ClassId) : ConeUnresolvedError {
override val qualifier: String get() = classId.asSingleFqName().asString()
override val reason: String get() = "Symbol not found for $classId"
}
class ConeUnresolvedQualifierError(override val qualifier: String) : ConeUnresolvedError {
override val reason: String get() = "Symbol not found for $qualifier"
}
class ConeUnresolvedNameError(val name: Name) : ConeUnresolvedError {
override val qualifier: String get() = name.asString()
override val reason: String get() = "Unresolved name: $name"
}
class ConeFunctionCallExpectedError(
val name: Name,
val hasValueParameters: Boolean,
override val candidateSymbols: Collection<FirBasedSymbol<*>>
) : ConeDiagnosticWithCandidates {
override val reason: String get() = "Function call expected: $name(${if (hasValueParameters) "..." else ""})"
}
class ConeFunctionExpectedError(val expression: String, val type: ConeKotlinType) : ConeDiagnostic {
override val reason: String get() = "Expression '$expression' of type '$type' cannot be invoked as a function"
}
class ConeResolutionToClassifierError(override val candidateSymbol: FirRegularClassSymbol) : ConeDiagnosticWithSingleCandidate {
override val reason: String get() = "Resolution to classifier"
}
class ConeHiddenCandidateError(
override val candidateSymbol: FirBasedSymbol<*>
) : ConeDiagnosticWithSingleCandidate {
override val reason: String get() = "HIDDEN: ${describeSymbol(candidateSymbol)} is deprecated with DeprecationLevel.HIDDEN"
}
class ConeVisibilityError(
override val candidateSymbol: FirBasedSymbol<*>
) : ConeDiagnosticWithSingleCandidate {
override val reason: String get() = "HIDDEN: ${describeSymbol(candidateSymbol)} is invisible"
}
class ConeInapplicableWrongReceiver(override val candidateSymbols: Collection<FirBasedSymbol<*>>) : ConeDiagnosticWithCandidates {
override val reason: String
get() = "None of the following candidates is applicable because of receiver type mismatch: ${
candidateSymbols.map { describeSymbol(it) }
}"
}
class ConeInapplicableCandidateError(
val applicability: CandidateApplicability,
val candidate: AbstractCandidate,
) : ConeDiagnosticWithSingleCandidate {
override val reason: String get() = "Inapplicable($applicability): ${describeSymbol(candidateSymbol)}"
override val candidateSymbol: FirBasedSymbol<*> get() = candidate.symbol
}
class ConeNoCompanionObject(
override val candidateSymbol: FirRegularClassSymbol
) : ConeDiagnosticWithSingleCandidate {
override val reason: String
get() = "Classifier ''$candidateSymbol'' does not have a companion object, and thus must be initialized here"
}
class ConeConstraintSystemHasContradiction(
val candidate: AbstractCandidate,
) : ConeDiagnosticWithSingleCandidate {
override val reason: String get() = "CS errors: ${describeSymbol(candidateSymbol)}"
override val candidateSymbol: FirBasedSymbol<*> get() = candidate.symbol
}
class ConeArgumentTypeMismatchCandidateError(
val expectedType: ConeKotlinType, val actualType: ConeKotlinType
) : ConeDiagnostic {
override val reason: String
get() = "Type mismatch. Expected: $expectedType, Actual: $actualType"
}
class ConeAmbiguityError(
val name: Name,
val applicability: CandidateApplicability,
val candidates: Collection<AbstractCandidate>
) : ConeDiagnosticWithCandidates {
override val reason: String get() = "Ambiguity: $name, ${candidateSymbols.map { describeSymbol(it) }}"
override val candidateSymbols: Collection<FirBasedSymbol<*>> get() = candidates.map { it.symbol }
}
class ConeOperatorAmbiguityError(override val candidateSymbols: Collection<FirBasedSymbol<*>>) : ConeDiagnosticWithCandidates {
override val reason: String get() = "Operator overload ambiguity. Compatible candidates: ${candidateSymbols.map { describeSymbol(it) }}"
}
object ConeVariableExpectedError : ConeDiagnostic {
override val reason: String get() = "Variable expected"
}
class ConeValReassignmentError(val variable: FirVariableSymbol<*>) : ConeDiagnostic {
override val reason: String get() = "Re-assigning a val variable"
}
class ConeContractDescriptionError(override val reason: String) : ConeDiagnostic
class ConeIllegalAnnotationError(val name: Name) : ConeDiagnostic {
override val reason: String get() = "Not a legal annotation: $name"
}
interface ConeUnmatchedTypeArgumentsError : ConeDiagnosticWithSingleCandidate {
val desiredCount: Int
override val candidateSymbol: FirClassLikeSymbol<*>
}
class ConeWrongNumberOfTypeArgumentsError(
override val desiredCount: Int,
override val candidateSymbol: FirRegularClassSymbol,
source: FirSourceElement
) : ConeDiagnosticWithSource(source), ConeUnmatchedTypeArgumentsError {
override val reason: String get() = "Wrong number of type arguments"
}
class ConeNoTypeArgumentsOnRhsError(
override val desiredCount: Int,
override val candidateSymbol: FirClassLikeSymbol<*>
) : ConeUnmatchedTypeArgumentsError {
override val reason: String get() = "No type arguments on RHS"
}
class ConeOuterClassArgumentsRequired(
val symbol: FirRegularClassSymbol,
) : ConeDiagnostic {
override val reason: String = "Type arguments should be specified for an outer class"
}
class ConeInstanceAccessBeforeSuperCall(val target: String) : ConeDiagnostic {
override val reason: String get() = "Cannot access ''${target}'' before superclass constructor has been called"
}
class ConeUnsupportedCallableReferenceTarget(val fir: FirCallableDeclaration) : ConeDiagnosticWithSingleCandidate {
override val reason: String get() = "Unsupported declaration for callable reference: ${fir.render()}"
override val candidateSymbol: FirBasedSymbol<*> get() = fir.symbol
}
class ConeTypeParameterSupertype(val symbol: FirTypeParameterSymbol) : ConeDiagnostic {
override val reason: String get() = "Type parameter ${symbol.fir.name} cannot be a supertype"
}
class ConeTypeParameterInQualifiedAccess(val symbol: FirTypeParameterSymbol) : ConeDiagnostic {
override val reason: String get() = "Type parameter ${symbol.fir.name} in qualified access"
}
class ConeCyclicTypeBound(val symbol: FirTypeParameterSymbol, val bounds: ImmutableList<FirTypeRef>) : ConeDiagnostic {
override val reason: String get() = "Type parameter ${symbol.fir.name} has cyclic bounds"
}
class ConeImportFromSingleton(val name: Name) : ConeDiagnostic {
override val reason: String get() = "Import from singleton $name is not allowed"
}
open class ConeUnsupported(override val reason: String, val source: FirSourceElement? = null) : ConeDiagnostic
class ConeUnsupportedDynamicType : ConeUnsupported("Dynamic types are not supported in this context")
class ConeDeprecated(
val source: FirSourceElement?,
override val candidateSymbol: FirBasedSymbol<*>,
val deprecationInfo: DeprecationInfo
) : ConeDiagnosticWithSingleCandidate {
override val reason: String get() = "Deprecated: ${deprecationInfo.message}"
}
class ConeLocalVariableNoTypeOrInitializer(val variable: FirVariable) : ConeDiagnostic {
override val reason: String get() = "Cannot infer variable type without initializer / getter / delegate"
}
class ConePropertyAsOperator(val symbol: FirPropertySymbol) : ConeDiagnostic {
override val reason: String get() = "Cannot use a property as an operator"
}
class ConeUnknownLambdaParameterTypeDiagnostic : ConeDiagnostic {
override val reason: String get() = "Unknown return lambda parameter type"
}
private fun describeSymbol(symbol: FirBasedSymbol<*>): String {
return when (symbol) {
is FirClassLikeSymbol<*> -> symbol.classId.asString()
is FirCallableSymbol<*> -> symbol.callableId.toString()
else -> "$symbol"
}
}
@@ -0,0 +1,18 @@
/*
* Copyright 2010-2021 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.inference
import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
import org.jetbrains.kotlin.fir.types.ConeTypeVariable
class ConeTypeVariableForPostponedAtom(name: String) : ConeTypeVariable(name)
class ConeTypeVariableForLambdaParameterType(name: String, val index: Int) : ConeTypeVariable(name)
class ConeTypeVariableForLambdaReturnType(val argument: FirAnonymousFunction, name: String) : ConeTypeVariable(name)
class ConeTypeParameterBasedTypeVariable(
val typeParameterSymbol: FirTypeParameterSymbol
) : ConeTypeVariable(typeParameterSymbol.name.identifier, typeParameterSymbol.toLookupTag())
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2020 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.inference.model
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction
import org.jetbrains.kotlin.fir.types.FirTypeProjection
import org.jetbrains.kotlin.resolve.calls.inference.model.*
import org.jetbrains.kotlin.types.model.TypeVariableMarker
class ConeDeclaredUpperBoundConstraintPosition : DeclaredUpperBoundConstraintPosition<Nothing?>(null)
class ConeFixVariableConstraintPosition(variable: TypeVariableMarker) : FixVariableConstraintPosition<Nothing?>(variable, null)
class ConeArgumentConstraintPosition(argument: FirElement) : ArgumentConstraintPosition<FirElement>(argument)
class ConeExpectedTypeConstraintPosition(
val expectedTypeMismatchIsReportedInChecker: Boolean
) : ExpectedTypeConstraintPosition<Nothing?>(null)
class ConeExplicitTypeParameterConstraintPosition(
typeArgument: FirTypeProjection,
) : ExplicitTypeParameterConstraintPosition<FirTypeProjection>(typeArgument)
class ConeLambdaArgumentConstraintPosition(
anonymousFunction: FirAnonymousFunction
) : LambdaArgumentConstraintPosition<FirAnonymousFunction>(anonymousFunction)
@@ -0,0 +1,18 @@
/*
* Copyright 2010-2021 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.transformers
import org.jetbrains.kotlin.fir.declarations.FirTypedDeclaration
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
interface ReturnTypeCalculator {
fun tryCalculateReturnType(declaration: FirTypedDeclaration): FirResolvedTypeRef
fun tryCalculateReturnType(symbol: FirCallableSymbol<*>): FirResolvedTypeRef {
return tryCalculateReturnType(symbol.fir)
}
}
@@ -0,0 +1,31 @@
/*
* Copyright 2010-2021 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.transformers
import org.jetbrains.kotlin.fir.declarations.FirTypedDeclaration
import org.jetbrains.kotlin.fir.diagnostics.ConeSimpleDiagnostic
import org.jetbrains.kotlin.fir.diagnostics.DiagnosticKind
import org.jetbrains.kotlin.fir.render
import org.jetbrains.kotlin.fir.scopes.FakeOverrideTypeCalculator
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
import org.jetbrains.kotlin.fir.types.builder.buildErrorTypeRef
class ReturnTypeCalculatorForFullBodyResolve : ReturnTypeCalculator {
override fun tryCalculateReturnType(declaration: FirTypedDeclaration): FirResolvedTypeRef {
val returnTypeRef = declaration.returnTypeRef
if (returnTypeRef is FirResolvedTypeRef) return returnTypeRef
if (declaration.origin.fromSupertypes) {
return FakeOverrideTypeCalculator.Forced.computeReturnType(declaration)
}
return buildErrorTypeRef {
diagnostic = ConeSimpleDiagnostic(
"Cannot calculate return type during full-body resolution (local class/object?): ${declaration.render()}",
DiagnosticKind.InferenceError
)
}
}
}
@@ -0,0 +1,23 @@
/*
* Copyright 2010-2021 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.transformers
import org.jetbrains.kotlin.descriptors.EffectiveVisibility
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirDeclarationDataKey
import org.jetbrains.kotlin.fir.declarations.FirDeclarationDataRegistry
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.symbols.ensureResolved
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
private object PublishedApiEffectiveVisibilityKey : FirDeclarationDataKey()
var FirDeclaration.publishedApiEffectiveVisibility: EffectiveVisibility? by FirDeclarationDataRegistry.data(PublishedApiEffectiveVisibilityKey)
inline val FirCallableSymbol<*>.publishedApiEffectiveVisibility: EffectiveVisibility?
get() {
ensureResolved(FirResolvePhase.STATUS)
return fir.publishedApiEffectiveVisibility
}