[LL API] Add 'ContextCollector'
The traditional way to get a 'FirTowerDataContext' in K2 was to reparse the callable body and pass it through all resolution phases. It is terribly inefficient and requires a lot of magic. Currently, 'ContextCollector' is only going to be used to collect a code fragment context. After proper test coverage and a plenty of patches it's going to become a generic replacement for read-only on-air resolution.
This commit is contained in:
+453
@@ -0,0 +1,453 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.analysis.low.level.api.fir.util
|
||||
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.psi.PsiElement
|
||||
import kotlinx.collections.immutable.PersistentMap
|
||||
import kotlinx.collections.immutable.persistentHashMapOf
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.LLFirResolveTarget
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.ContextCollector.ContextKind
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.ContextCollector.Context
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.ContextCollector.FilterResponse
|
||||
import org.jetbrains.kotlin.fir.FirElement
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.isLocal
|
||||
import org.jetbrains.kotlin.fir.expressions.*
|
||||
import org.jetbrains.kotlin.fir.psi
|
||||
import org.jetbrains.kotlin.fir.resolve.ResolutionMode
|
||||
import org.jetbrains.kotlin.fir.resolve.SessionHolder
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.DataFlowAnalyzerContext
|
||||
import org.jetbrains.kotlin.fir.resolve.transformers.ReturnTypeCalculatorForFullBodyResolve
|
||||
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.BodyResolveContext
|
||||
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
|
||||
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
||||
import org.jetbrains.kotlin.fir.visitors.FirDefaultVisitorVoid
|
||||
import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid
|
||||
|
||||
internal object ContextCollector {
|
||||
enum class ContextKind {
|
||||
/** Represents the context of the declaration itself. */
|
||||
SELF,
|
||||
|
||||
/** Represents the context inside the body (of a class, function, block, etc.) */
|
||||
BODY
|
||||
}
|
||||
|
||||
class Context(
|
||||
val towerDataContext: FirTowerDataContext,
|
||||
val smartCasts: Map<FirBasedSymbol<*>, Set<ConeKotlinType>>,
|
||||
)
|
||||
|
||||
enum class FilterResponse {
|
||||
/** Store context for the element and continue the traversal. */
|
||||
CONTINUE,
|
||||
|
||||
/** Store context for the element and stop traversal. */
|
||||
STOP,
|
||||
|
||||
/** Skip the element and continue the traversal. */
|
||||
SKIP
|
||||
}
|
||||
|
||||
fun process(target: LLFirResolveTarget, holder: SessionHolder, filter: (PsiElement) -> FilterResponse): ContextProvider {
|
||||
val pathIterator = (listOf(target.firFile) + target.path).iterator()
|
||||
|
||||
val visitor = ContextCollectorVisitor(holder, filter) {
|
||||
if (pathIterator.hasNext()) pathIterator.next() else null
|
||||
}
|
||||
|
||||
target.firFile.accept(visitor)
|
||||
visitor.processTarget(target)
|
||||
|
||||
return ContextProvider { element, kind -> visitor[element, kind] }
|
||||
}
|
||||
}
|
||||
|
||||
internal fun interface ContextProvider {
|
||||
operator fun get(element: PsiElement, kind: ContextKind): Context?
|
||||
}
|
||||
|
||||
private class ContextCollectorVisitor(
|
||||
private val holder: SessionHolder,
|
||||
private val filter: (PsiElement) -> FilterResponse,
|
||||
private val interceptor: () -> FirElement?
|
||||
) : FirDefaultVisitorVoid() {
|
||||
operator fun get(element: PsiElement, kind: ContextKind): Context? {
|
||||
return result[element to kind]
|
||||
}
|
||||
|
||||
private val session: FirSession
|
||||
get() = holder.session
|
||||
|
||||
private var isActive = true
|
||||
|
||||
private val context = BodyResolveContext(
|
||||
returnTypeCalculator = ReturnTypeCalculatorForFullBodyResolve.Default,
|
||||
dataFlowAnalyzerContext = DataFlowAnalyzerContext(session)
|
||||
)
|
||||
|
||||
private var smartCasts: PersistentMap<FirBasedSymbol<*>, Set<ConeKotlinType>> = persistentHashMapOf()
|
||||
|
||||
private val result = HashMap<Pair<PsiElement, ContextKind>, Context>()
|
||||
|
||||
fun processTarget(target: LLFirResolveTarget) {
|
||||
target.firFile.accept(this)
|
||||
}
|
||||
|
||||
override fun visitElement(element: FirElement) {
|
||||
dumpContext(element.psi, ContextKind.SELF)
|
||||
|
||||
onActive {
|
||||
element.acceptChildren(this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun dumpContext(psi: PsiElement?, kind: ContextKind) {
|
||||
ProgressManager.checkCanceled()
|
||||
|
||||
if (psi == null) {
|
||||
return
|
||||
}
|
||||
|
||||
val key = Pair(psi, kind)
|
||||
if (key in result) {
|
||||
return
|
||||
}
|
||||
|
||||
val response = filter(psi)
|
||||
if (response != FilterResponse.SKIP) {
|
||||
result[key] = Context(context.towerDataContext, smartCasts)
|
||||
}
|
||||
|
||||
if (response == FilterResponse.STOP) {
|
||||
isActive = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitFile(file: FirFile) {
|
||||
file.lazyResolveToPhase(FirResolvePhase.ANNOTATIONS_ARGUMENTS_MAPPING)
|
||||
|
||||
context.withFile(file, holder) {
|
||||
withInterceptor {
|
||||
super.visitFile(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitAnnotationCall(annotationCall: FirAnnotationCall) {
|
||||
context.forAnnotation {
|
||||
super.visitAnnotationCall(annotationCall)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitRegularClass(regularClass: FirRegularClass) = withProcessor {
|
||||
dumpContext(regularClass.psi, ContextKind.SELF)
|
||||
|
||||
regularClass.lazyResolveToPhase(FirResolvePhase.ANNOTATIONS_ARGUMENTS_MAPPING)
|
||||
|
||||
processSignatureAnnotations(regularClass)
|
||||
|
||||
context.withContainingClass(regularClass) {
|
||||
processList(regularClass.typeParameters)
|
||||
|
||||
context.withRegularClass(regularClass, holder) {
|
||||
dumpContext(regularClass.psi, ContextKind.BODY)
|
||||
|
||||
onActive {
|
||||
withInterceptor {
|
||||
processChildren(regularClass)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (regularClass.isLocal) {
|
||||
context.storeClassIfNotNested(regularClass, session)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitConstructor(constructor: FirConstructor) = withProcessor {
|
||||
dumpContext(constructor.psi, ContextKind.SELF)
|
||||
|
||||
constructor.lazyResolveToPhase(FirResolvePhase.ANNOTATIONS_ARGUMENTS_MAPPING)
|
||||
|
||||
processSignatureAnnotations(constructor)
|
||||
|
||||
context.withConstructor(constructor) {
|
||||
val owningClass = context.containerIfAny as? FirRegularClass
|
||||
context.forConstructorParameters(constructor, owningClass, holder) {
|
||||
processList(constructor.valueParameters)
|
||||
}
|
||||
|
||||
context.forConstructorBody(constructor, session) {
|
||||
processList(constructor.valueParameters)
|
||||
|
||||
dumpContext(constructor.psi, ContextKind.BODY)
|
||||
|
||||
onActive {
|
||||
constructor.lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE)
|
||||
process(constructor.body)
|
||||
}
|
||||
}
|
||||
|
||||
onActive {
|
||||
context.forDelegatedConstructorCall(constructor, owningClass = null, holder) {
|
||||
process(constructor.delegatedConstructor)
|
||||
}
|
||||
}
|
||||
|
||||
onActive {
|
||||
processChildren(constructor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitEnumEntry(enumEntry: FirEnumEntry) {
|
||||
context.forEnumEntry {
|
||||
super.visitEnumEntry(enumEntry)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitSimpleFunction(simpleFunction: FirSimpleFunction) = withProcessor {
|
||||
dumpContext(simpleFunction.psi, ContextKind.SELF)
|
||||
|
||||
simpleFunction.lazyResolveToPhase(FirResolvePhase.ANNOTATIONS_ARGUMENTS_MAPPING)
|
||||
|
||||
processSignatureAnnotations(simpleFunction)
|
||||
|
||||
context.withSimpleFunction(simpleFunction, session) {
|
||||
context.forFunctionBody(simpleFunction, holder) {
|
||||
processList(simpleFunction.valueParameters)
|
||||
|
||||
dumpContext(simpleFunction.psi, ContextKind.BODY)
|
||||
|
||||
onActive {
|
||||
simpleFunction.lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE)
|
||||
process(simpleFunction.body)
|
||||
}
|
||||
}
|
||||
|
||||
onActive {
|
||||
processChildren(simpleFunction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitProperty(property: FirProperty) = withProcessor {
|
||||
dumpContext(property.psi, ContextKind.SELF)
|
||||
|
||||
property.lazyResolveToPhase(FirResolvePhase.ANNOTATIONS_ARGUMENTS_MAPPING)
|
||||
|
||||
processSignatureAnnotations(property)
|
||||
|
||||
onActive {
|
||||
property.lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE)
|
||||
|
||||
context.withProperty(property) {
|
||||
context.forPropertyInitializer {
|
||||
onActive {
|
||||
process(property.initializer)
|
||||
}
|
||||
|
||||
onActive {
|
||||
process(property.delegate)
|
||||
}
|
||||
|
||||
onActive {
|
||||
process(property.backingField)
|
||||
}
|
||||
}
|
||||
|
||||
onActive {
|
||||
processChildren(property)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (property.isLocal) {
|
||||
context.storeVariable(property, session)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitPropertyAccessor(propertyAccessor: FirPropertyAccessor) = withProcessor {
|
||||
dumpContext(propertyAccessor.psi, ContextKind.SELF)
|
||||
|
||||
processSignatureAnnotations(propertyAccessor)
|
||||
|
||||
context.withPropertyAccessor(propertyAccessor.propertySymbol.fir, propertyAccessor, holder) {
|
||||
dumpContext(propertyAccessor.psi, ContextKind.BODY)
|
||||
|
||||
onActive {
|
||||
processChildren(propertyAccessor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitValueParameter(valueParameter: FirValueParameter) = withProcessor {
|
||||
dumpContext(valueParameter.psi, ContextKind.SELF)
|
||||
|
||||
processSignatureAnnotations(valueParameter)
|
||||
|
||||
context.withValueParameter(valueParameter, session) {
|
||||
dumpContext(valueParameter.psi, ContextKind.SELF)
|
||||
|
||||
onActive {
|
||||
processChildren(valueParameter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitAnonymousInitializer(anonymousInitializer: FirAnonymousInitializer) = withProcessor {
|
||||
dumpContext(anonymousInitializer.psi, ContextKind.SELF)
|
||||
|
||||
anonymousInitializer.lazyResolveToPhase(FirResolvePhase.ANNOTATIONS_ARGUMENTS_MAPPING)
|
||||
|
||||
processSignatureAnnotations(anonymousInitializer)
|
||||
|
||||
context.withAnonymousInitializer(anonymousInitializer, session) {
|
||||
dumpContext(anonymousInitializer.psi, ContextKind.BODY)
|
||||
|
||||
onActive {
|
||||
anonymousInitializer.lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE)
|
||||
processChildren(anonymousInitializer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitAnonymousFunction(anonymousFunction: FirAnonymousFunction) = withProcessor {
|
||||
dumpContext(anonymousFunction.psi, ContextKind.SELF)
|
||||
|
||||
processSignatureAnnotations(anonymousFunction)
|
||||
|
||||
context.withAnonymousFunction(anonymousFunction, holder, ResolutionMode.ContextIndependent) {
|
||||
context.forFunctionBody(anonymousFunction, holder) {
|
||||
for (parameter in anonymousFunction.valueParameters) {
|
||||
process(parameter)
|
||||
context.storeVariable(parameter, holder.session)
|
||||
}
|
||||
|
||||
dumpContext(anonymousFunction.psi, ContextKind.BODY)
|
||||
|
||||
onActive {
|
||||
process(anonymousFunction.body)
|
||||
}
|
||||
}
|
||||
|
||||
onActive {
|
||||
processChildren(anonymousFunction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitAnonymousObject(anonymousObject: FirAnonymousObject) = withProcessor {
|
||||
dumpContext(anonymousObject.psi, ContextKind.SELF)
|
||||
|
||||
processSignatureAnnotations(anonymousObject)
|
||||
|
||||
context.withAnonymousObject(anonymousObject, holder) {
|
||||
dumpContext(anonymousObject.psi, ContextKind.BODY)
|
||||
|
||||
onActive {
|
||||
processChildren(anonymousObject)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitBlock(block: FirBlock) = withProcessor {
|
||||
dumpContext(block.psi, ContextKind.SELF)
|
||||
|
||||
context.forBlock(session) {
|
||||
processChildren(block)
|
||||
|
||||
dumpContext(block.psi, ContextKind.BODY)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitSmartCastExpression(smartCastExpression: FirSmartCastExpression) {
|
||||
if (smartCastExpression.isStable) {
|
||||
val symbol = smartCastExpression.originalExpression.toResolvedCallableSymbol()
|
||||
if (symbol != null) {
|
||||
val previousSmartCasts = smartCasts
|
||||
try {
|
||||
smartCasts = smartCasts.put(symbol, smartCastExpression.typesFromSmartCast.toSet())
|
||||
super.visitSmartCastExpression(smartCastExpression)
|
||||
} finally {
|
||||
smartCasts = previousSmartCasts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.visitSmartCastExpression(smartCastExpression)
|
||||
}
|
||||
|
||||
@ContextCollectorDsl
|
||||
private fun Processor.processSignatureAnnotations(declaration: FirDeclaration) {
|
||||
for (annotation in declaration.annotations) {
|
||||
onActive {
|
||||
process(annotation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun withProcessor(block: Processor.() -> Unit) {
|
||||
Processor(this).block()
|
||||
}
|
||||
|
||||
private class Processor(private val delegate: FirVisitorVoid) {
|
||||
private val elementsToSkip = HashSet<FirElement>()
|
||||
|
||||
@ContextCollectorDsl
|
||||
fun process(element: FirElement?) {
|
||||
if (element != null) {
|
||||
element.accept(delegate)
|
||||
elementsToSkip += element
|
||||
}
|
||||
}
|
||||
|
||||
@ContextCollectorDsl
|
||||
fun processList(elements: Collection<FirElement>) {
|
||||
for (element in elements) {
|
||||
process(element)
|
||||
elementsToSkip += element
|
||||
}
|
||||
}
|
||||
|
||||
@ContextCollectorDsl
|
||||
fun processChildren(element: FirElement) {
|
||||
val visitor = FilteringVisitor(delegate, elementsToSkip)
|
||||
element.acceptChildren(visitor)
|
||||
}
|
||||
}
|
||||
|
||||
private class FilteringVisitor(val delegate: FirVisitorVoid, val elementsToSkip: Set<FirElement>) : FirVisitorVoid() {
|
||||
override fun visitElement(element: FirElement) {
|
||||
if (element !in elementsToSkip) {
|
||||
element.accept(delegate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun withInterceptor(block: () -> Unit) {
|
||||
val target = interceptor()
|
||||
if (target != null) {
|
||||
target.accept(this)
|
||||
} else {
|
||||
block()
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun onActive(block: () -> Unit) {
|
||||
if (isActive) {
|
||||
block()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DslMarker
|
||||
private annotation class ContextCollectorDsl
|
||||
Reference in New Issue
Block a user