~ partial resolve
diff --git a/analysis/low-level-api-fir/resources/META-INF/analysis-api/low-level-api-fir.xml b/analysis/low-level-api-fir/resources/META-INF/analysis-api/low-level-api-fir.xml
index db46e22..3405749 100644
--- a/analysis/low-level-api-fir/resources/META-INF/analysis-api/low-level-api-fir.xml
+++ b/analysis/low-level-api-fir/resources/META-INF/analysis-api/low-level-api-fir.xml
@@ -22,6 +22,13 @@
         restartRequired="true"
     />
 
+    <registryKey
+        defaultValue="true"
+        description="Perform partial body analysis in Kotlin. Speeds up occasional references resolution, e.g. in Find Usages."
+        key="kotlin.analysis.partialBodyAnalysis"
+        restartRequired="true"
+    />
+
     <projectService
         serviceInterface="org.jetbrains.kotlin.analysis.low.level.api.fir.api.services.LLFirElementByPsiElementChooser"
         serviceImplementation="org.jetbrains.kotlin.analysis.low.level.api.fir.services.LLRealFirElementByPsiElementChooser"
diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/targets/LLFirPartialBodyResolveTarget.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/targets/LLFirPartialBodyResolveTarget.kt
new file mode 100644
index 0000000..b9d5a98
--- /dev/null
+++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/targets/LLFirPartialBodyResolveTarget.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2010-2024 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.api.targets
+
+import org.jetbrains.kotlin.analysis.low.level.api.fir.api.FirDesignation
+import org.jetbrains.kotlin.analysis.low.level.api.fir.util.isPartialBodyResolvable
+import org.jetbrains.kotlin.fir.FirElementWithResolveState
+import org.jetbrains.kotlin.fir.declarations.FirDeclaration
+import org.jetbrains.kotlin.psi.KtElement
+
+internal class LLFirPartialBodyResolveTarget(
+    designation: FirDesignation,
+    val request: LLPartialBodyResolveRequest
+) : LLFirResolveTarget(designation) {
+    override fun visitTargetElement(element: FirElementWithResolveState, visitor: LLFirResolveTargetVisitor) {
+        visitor.performAction(element)
+    }
+}
+
+internal class LLPartialBodyResolveRequest(
+    val target: FirDeclaration,
+    val totalPsiStatementCount: Int,
+    val targetPsiStatementCount: Int,
+    val stopElement: KtElement?
+) {
+    init {
+        require(target.isPartialBodyResolvable)
+    }
+}
\ No newline at end of file
diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/targets/LLFirSingleResolveTarget.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/targets/LLFirSingleResolveTarget.kt
index 91d0ea6..383f4c2 100644
--- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/targets/LLFirSingleResolveTarget.kt
+++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/targets/LLFirSingleResolveTarget.kt
@@ -9,6 +9,11 @@
 import org.jetbrains.kotlin.fir.FirElementWithResolveState
 import org.jetbrains.kotlin.fir.declarations.*
 
+private object PartialBodyResolveStateKey : FirDeclarationDataKey()
+
+internal var FirDeclaration.partialBodyResolveState: LLPartialBodyResolveState?
+        by FirDeclarationDataRegistry.data(PartialBodyResolveStateKey)
+
 /**
  * [LLFirResolveTarget] representing single target to resolve. The [target] can be any of [FirElementWithResolveState]
  */
diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/targets/LLPartialBodyResolveState.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/targets/LLPartialBodyResolveState.kt
new file mode 100644
index 0000000..eb46fd2
--- /dev/null
+++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/targets/LLPartialBodyResolveState.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2010-2024 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.api.targets
+
+import org.jetbrains.kotlin.fir.declarations.FirTowerDataContext
+import org.jetbrains.kotlin.fir.resolve.dfa.DataFlowAnalyzerContext
+
+/**
+ * Represents the incomplete body resolve state.
+ * The attribute is only present if the function body was analyzed partially.
+ */
+internal data class LLPartialBodyResolveState(
+    val totalPsiStatementCount: Int,
+    val analyzedPsiStatementCount: Int,
+    val analyzedFirStatementCount: Int,
+    val performedAnalysesCount: Int,
+    val analysisStateSnapshot: LLPartialBodyResolveSnapshot?
+) {
+    val isFullyAnalyzed: Boolean
+        get() = totalPsiStatementCount == analyzedPsiStatementCount
+
+    override fun toString(): String {
+        return "$analyzedFirStatementCount($analyzedPsiStatementCount/$totalPsiStatementCount) #$performedAnalysesCount"
+    }
+}
+
+internal class LLPartialBodyResolveSnapshot(
+    val towerDataContext: FirTowerDataContext,
+    val dataFlowAnalyzerContext: DataFlowAnalyzerContext
+)
\ No newline at end of file
diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/element/builder/FirElementBuilder.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/element/builder/FirElementBuilder.kt
index d4f61a4..6af2975 100644
--- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/element/builder/FirElementBuilder.kt
+++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/element/builder/FirElementBuilder.kt
@@ -11,6 +11,7 @@
 import org.jetbrains.kotlin.analysis.low.level.api.fir.file.structure.FirElementsRecorder
 import org.jetbrains.kotlin.analysis.low.level.api.fir.lazy.resolve.declarationCanBeLazilyResolved
 import org.jetbrains.kotlin.analysis.low.level.api.fir.util.findSourceNonLocalFirDeclaration
+import org.jetbrains.kotlin.analysis.low.level.api.fir.util.isPartialBodyResolvable
 import org.jetbrains.kotlin.analysis.low.level.api.fir.util.parentsWithSelfCodeFragmentAware
 import org.jetbrains.kotlin.analysis.low.level.api.fir.util.requireTypeIntersectionWith
 import org.jetbrains.kotlin.analysis.utils.printer.parentOfType
@@ -121,7 +122,18 @@
 
         val structureElement = fileStructure.getStructureElementFor(element, nonLocalContainer)
         val mappings = structureElement.mappings
-        return mappings.getFir(psi)
+
+        val firElement = mappings.getFir(psi)
+
+        if (firElement is FirElementWithResolveState) {
+            // Partially resolvable declarations might have unresolved bodies in the mapping.
+            // Here we forcibly resolve them to obey to the 'getOrBuildFirFor()' contract.
+            if (firElement.isPartialBodyResolvable && firElement.resolvePhase < FirResolvePhase.BODY_RESOLVE) {
+                firElement.lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE)
+            }
+        }
+
+        return firElement
     }
 
     private inline fun <T : KtElement, E : PsiElement> getFirForNonBodyElement(
diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/builder/LLFirLockProvider.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/builder/LLFirLockProvider.kt
index 9922142..6a5dcb8 100644
--- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/builder/LLFirLockProvider.kt
+++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/builder/LLFirLockProvider.kt
@@ -6,7 +6,9 @@
 package org.jetbrains.kotlin.analysis.low.level.api.fir.file.builder
 
 import com.intellij.openapi.util.registry.Registry
+import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.partialBodyResolveState
 import org.jetbrains.kotlin.analysis.low.level.api.fir.lazy.resolve.LLFirLazyResolveContractChecker
+import org.jetbrains.kotlin.analysis.low.level.api.fir.transformers.PartialBodyAnalysisSuspendedException
 import org.jetbrains.kotlin.analysis.low.level.api.fir.util.checkCanceled
 import org.jetbrains.kotlin.analysis.low.level.api.fir.util.errorWithFirSpecificEntries
 import org.jetbrains.kotlin.analysis.low.level.api.fir.util.lockWithPCECheck
@@ -132,10 +134,15 @@
                     try {
                         action()
                     } catch (e: Throwable) {
-                        exceptionOccurred = true
+                        if (e !is PartialBodyAnalysisSuspendedException) {
+                            exceptionOccurred = true
+                        }
                         throw e
                     } finally {
-                        val newPhase = if (updatePhase && !exceptionOccurred) toPhase else stateSnapshot.resolvePhase
+                        val newPhase = when {
+                            !updatePhase || exceptionOccurred -> stateSnapshot.resolvePhase
+                            else -> computeNewPhase(stateSnapshot, toPhase)
+                        }
                         unlock(toPhase = newPhase)
                     }
 
@@ -149,6 +156,19 @@
         }
     }
 
+    private fun FirElementWithResolveState.computeNewPhase(stateSnapshot: FirResolveState, toPhase: FirResolvePhase): FirResolvePhase {
+        if (this is FirDeclaration && toPhase == FirResolvePhase.BODY_RESOLVE) {
+            val state = partialBodyResolveState
+            if (state != null && !state.isFullyAnalyzed) {
+                // We only update the phase to BODY_RESOLVE if all statements are resolved.
+                // Otherwise, we set (BODY_RESOLVE - 1), so the next BODY_RESOLVE phase run can finish the analysis.
+                return stateSnapshot.resolvePhase
+            }
+        }
+
+        return toPhase
+    }
+
     private fun waitOnBarrier(
         stateSnapshot: FirInProcessOfResolvingToPhaseStateWithBarrier,
     ): Boolean {
diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/FileElementFactory.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/FileElementFactory.kt
index fa03bf1c..ed7f713 100644
--- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/FileElementFactory.kt
+++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/FileElementFactory.kt
@@ -35,11 +35,13 @@
         }
 
         else -> {
-            firDeclaration.lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE)
             if (firDeclaration is FirPrimaryConstructor) {
+                firDeclaration.lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE)
                 firDeclaration.valueParameters.forEach { parameter ->
                     parameter.correspondingProperty?.lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE)
                 }
+            } else {
+                firDeclaration.lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE.previous)
             }
 
             DeclarationStructureElement(firFile, firDeclaration, moduleComponents)
diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/FileStructureElement.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/FileStructureElement.kt
index 06e4615..59ed0aa 100644
--- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/FileStructureElement.kt
+++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/FileStructureElement.kt
@@ -5,17 +5,40 @@
 
 package org.jetbrains.kotlin.analysis.low.level.api.fir.file.structure
 
+import com.intellij.openapi.util.registry.Registry
 import com.intellij.psi.PsiElement
 import com.intellij.psi.PsiErrorElement
+import kotlinx.collections.immutable.PersistentMap
+import kotlinx.collections.immutable.persistentHashMapOf
+import kotlinx.collections.immutable.persistentMapOf
 import org.jetbrains.kotlin.KtFakeSourceElementKind
 import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirModuleResolveComponents
+import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.LLPartialBodyResolveRequest
+import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.LLPartialBodyResolveState
+import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.partialBodyResolveState
 import org.jetbrains.kotlin.analysis.low.level.api.fir.diagnostics.*
+import org.jetbrains.kotlin.analysis.low.level.api.fir.file.builder.LLFirLockProvider
+import org.jetbrains.kotlin.analysis.low.level.api.fir.lazy.resolve.LLFirResolveDesignationCollector
+import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirResolvableModuleSession
+import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.llFirResolvableSession
+import org.jetbrains.kotlin.analysis.low.level.api.fir.util.body
+import org.jetbrains.kotlin.analysis.low.level.api.fir.util.isPartialBodyResolvable
 import org.jetbrains.kotlin.fir.FirElement
 import org.jetbrains.kotlin.fir.correspondingProperty
 import org.jetbrains.kotlin.fir.declarations.*
 import org.jetbrains.kotlin.fir.declarations.impl.FirPrimaryConstructor
+import org.jetbrains.kotlin.fir.expressions.FirBlock
+import org.jetbrains.kotlin.fir.expressions.FirLazyBlock
+import org.jetbrains.kotlin.fir.expressions.impl.FirEmptyExpressionBlock
+import org.jetbrains.kotlin.fir.expressions.impl.FirSingleExpressionBlock
+import org.jetbrains.kotlin.fir.realPsi
+import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
 import org.jetbrains.kotlin.fir.visitors.FirVisitor
 import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
+import org.jetbrains.kotlin.utils.exceptions.checkWithAttachment
+import org.jetbrains.kotlin.utils.exceptions.withPsiEntry
+import kotlin.collections.indexOf
 
 /**
  * Collects [KT -> FIR][KtToFirMapping] mapping and [diagnostics][FileStructureElementDiagnostics] for [declaration].
@@ -28,8 +51,9 @@
 internal sealed class FileStructureElement(
     val declaration: FirDeclaration,
     val diagnostics: FileStructureElementDiagnostics,
+    elementProvider: FirElementProvider = EagerFirElementProvider(declaration)
 ) {
-    val mappings: KtToFirMapping = KtToFirMapping(declaration)
+    val mappings: KtToFirMapping = KtToFirMapping(elementProvider)
 
     companion object {
         fun recorderFor(fir: FirDeclaration): FirElementsRecorder = when (fir) {
@@ -41,14 +65,175 @@
     }
 }
 
-internal class KtToFirMapping(firElement: FirDeclaration) {
+private typealias FirElementProvider = (KtElement) -> FirElement?
+
+private class EagerFirElementProvider(firDeclaration: FirDeclaration) : FirElementProvider {
     private val mapping = FirElementsRecorder.recordElementsFrom(
-        firElement = firElement,
-        recorder = FileStructureElement.recorderFor(firElement),
+        firElement = firDeclaration,
+        recorder = FileStructureElement.recorderFor(firDeclaration),
     )
 
-    fun getElement(ktElement: KtElement): FirElement? {
-        return mapping[ktElement]
+    override fun invoke(element: KtElement): FirElement? {
+        return mapping[element]
+    }
+}
+
+private class PartialBodyFirElementProvider(
+    private val declaration: FirDeclaration,
+    private val psiDeclaration: KtDeclaration,
+    private val psiBlock: KtBlockExpression,
+    private val psiStatements: List<KtExpression>,
+    private val session: LLFirResolvableModuleSession
+) : FirElementProvider {
+    /**
+     * Contains the latest known partial body resolution state.
+     *
+     * Initially, the [lastState] is empty, even though the declaration itself may already be partially resolved.
+     * On querying the mapping (by calling [invoke]), the actual resolved state is synchronized with the [lastState],
+     * and all missing elements are added to [bodyMappings].
+     */
+    @Volatile
+    private var lastState = LLPartialBodyResolveState(
+        totalPsiStatementCount = psiStatements.size,
+        analyzedPsiStatementCount = 0,
+        analyzedFirStatementCount = 0,
+        performedAnalysesCount = 0,
+        analysisStateSnapshot = null
+    )
+
+    /**
+     * Contains mappings for non-body elements.
+     */
+    private val signatureMappings: Map<KtElement, FirElement>
+
+    /**
+     * Contains collected mappings.
+     * Initially, only signature mappings are registered (the body is ignored).
+     * After consequent partial body analysis, elements from analyzed statements are appended.
+     */
+    @Volatile
+    private var bodyMappings: PersistentMap<KtElement, FirElement> = persistentMapOf()
+
+    // The body block cannot be cached on the element provider construction, as the body might be lazy at that point
+    private val bodyBlock: FirBlock
+        get() = declaration.body ?: error("Partial body element provider supports only declarations with bodies")
+
+    private val lockProvider: LLFirLockProvider
+        get() = session.moduleComponents.globalResolveComponents.lockProvider
+
+    init {
+        signatureMappings = persistentHashMapOf<KtElement, FirElement>()
+            .builder()
+            .also { declaration.accept(DeclarationStructureElement.SignatureRecorder(bodyBlock), it) }
+            .build()
+    }
+
+    override fun invoke(psiElement: KtElement): FirElement? {
+        val psiStatement = findTopmostStatement(psiElement)
+
+        if (psiStatement == null) {
+            // Didn't find a containing topmost statement. It's either a signature element or some unrelated one
+            return signatureMappings[psiElement]
+        }
+
+        val psiStatementIndex = psiStatements.indexOf(psiStatement)
+
+        checkWithAttachment(psiStatementIndex >= 0, { "The topmost statement was not found" }) {
+            withPsiEntry("statement", psiStatement)
+            withPsiEntry("declaration", psiDeclaration)
+        }
+
+        synchronized(this) {
+            if (lastState.analyzedPsiStatementCount > psiStatementIndex) {
+                // The statement is already analyzed and its children are in collected
+                return bodyMappings[psiElement]
+            }
+        }
+
+        performBodyAnalysis(psiStatementIndex)
+
+        synchronized(this) {
+            val newState = fetchPartialBodyResolveState()
+            if (newState != null) {
+                val lastCount = lastState.analyzedFirStatementCount
+                val newCount = newState.analyzedFirStatementCount
+
+                // There are newly analyzed statements, let's collect them
+                if (lastCount < newCount) {
+                    val firBody = bodyBlock
+                    require(firBody !is FirLazyBlock)
+
+                    val newBodyMappingsBuilder = bodyMappings.builder()
+
+                    for (index in lastCount until newCount) {
+                        val firStatement = firBody.statements[index]
+                        firStatement.accept(DeclarationStructureElement.Recorder, newBodyMappingsBuilder)
+                    }
+
+                    bodyMappings = newBodyMappingsBuilder.build()
+                    lastState = newState
+                }
+            } else {
+                // The body has never been analyzed (otherwise the partial body resolve state should have been present)
+                bodyMappings = bodyMappings
+                    .builder()
+                    .also { bodyBlock.accept(FileStructureElement.recorderFor(declaration), it) }
+                    .build()
+            }
+        }
+
+        return bodyMappings[psiElement]
+    }
+
+    private fun findTopmostStatement(psiElement: KtElement): KtElement? {
+        var previous: PsiElement? = null
+
+        for (current in psiElement.parentsWithSelf) {
+            when (current) {
+                psiBlock -> return previous as? KtElement
+                psiDeclaration -> return null
+            }
+
+            previous = current
+        }
+
+        return null
+    }
+
+    private fun fetchPartialBodyResolveState(): LLPartialBodyResolveState? {
+        var result: LLPartialBodyResolveState? = null
+        var isRun = false
+        lockProvider.withReadLock(declaration, FirResolvePhase.BODY_RESOLVE) {
+            result = declaration.partialBodyResolveState
+            isRun = true // 'withReadLock' does not call the lambda if the declaration already resolved
+        }
+        return if (isRun) result else declaration.partialBodyResolveState
+    }
+
+    private fun performBodyAnalysis(psiStatementIndex: Int) {
+        val psiStatementLimit = psiStatementIndex + 1
+        if (psiStatementLimit < psiStatements.size) {
+            val request = LLPartialBodyResolveRequest(
+                target = declaration,
+                totalPsiStatementCount = psiStatements.size,
+                targetPsiStatementCount = psiStatementLimit,
+                stopElement = psiStatements[psiStatementLimit]
+            )
+
+            val target = LLFirResolveDesignationCollector.getDesignationToResolveForPartialBody(request)
+            if (target != null) {
+                session.moduleComponents.firModuleLazyDeclarationResolver.lazyResolveTarget(target, FirResolvePhase.BODY_RESOLVE)
+                return
+            }
+        }
+
+        declaration.lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE)
+    }
+}
+
+internal class KtToFirMapping(private val elementProvider: FirElementProvider) {
+    private fun getElement(ktElement: KtElement): FirElement? {
+        return elementProvider(ktElement)
     }
 
     fun getFir(element: KtElement): FirElement? {
@@ -221,8 +406,59 @@
             moduleComponents = moduleComponents,
         )
     ),
+    elementProvider = createFirElementProvider(declaration),
 ) {
-    object Recorder : FirElementsRecorder() {
+    private companion object {
+        private val IS_PARTIAL_RESOLVE_ENABLED = Registry.`is`("kotlin.analysis.partialBodyAnalysis", true)
+
+        private fun createFirElementProvider(declaration: FirDeclaration): FirElementProvider {
+            if (IS_PARTIAL_RESOLVE_ENABLED) {
+                val bodyBlock = declaration.body
+                if (declaration.isPartialBodyResolvable && bodyBlock != null && declaration.resolvePhase < FirResolvePhase.BODY_RESOLVE) {
+                    require(declaration.resolvePhase == FirResolvePhase.BODY_RESOLVE.previous)
+
+                    val isPartiallyResolvable = when (bodyBlock) {
+                        is FirSingleExpressionBlock -> false
+                        is FirEmptyExpressionBlock -> false
+                        is FirLazyBlock -> true // Optimistic (however, below we also check the PSI statement count)
+                        else -> bodyBlock.statements.size > 1
+                    }
+
+                    val session = declaration.llFirResolvableSession
+                    val psiDeclaration = declaration.realPsi as? KtDeclaration
+                    val psiBodyBlock = psiDeclaration?.bodyBlock
+                    val psiStatements = psiBodyBlock?.statements
+
+                    if (isPartiallyResolvable && session != null && psiStatements != null && psiStatements.size > 1) {
+                        return PartialBodyFirElementProvider(declaration, psiDeclaration, psiBodyBlock, psiStatements, session)
+                    }
+                }
+            }
+
+            declaration.lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE)
+            return EagerFirElementProvider(declaration)
+        }
+
+        private val KtDeclaration.bodyBlock: KtBlockExpression?
+            get() = when (this) {
+                is KtAnonymousInitializer -> body as? KtBlockExpression
+                is KtDeclarationWithBody -> bodyBlockExpression
+                else -> null
+            }
+    }
+
+    object Recorder : AbstractRecorder()
+
+    class SignatureRecorder(val bodyElement: FirElement) : AbstractRecorder() {
+        override fun visitElement(element: FirElement, data: MutableMap<KtElement, FirElement>) {
+            if (element === bodyElement) {
+                return
+            }
+            super.visitElement(element, data)
+        }
+    }
+
+    abstract class AbstractRecorder : FirElementsRecorder() {
         override fun visitConstructor(constructor: FirConstructor, data: MutableMap<KtElement, FirElement>) {
             super.visitConstructor(constructor, data)
 
diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/inBlockModification.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/inBlockModification.kt
index f1bc902..8e61c78 100644
--- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/inBlockModification.kt
+++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/inBlockModification.kt
@@ -5,7 +5,9 @@
 
 package org.jetbrains.kotlin.analysis.low.level.api.fir.file.structure
 
+import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.partialBodyResolveState
 import org.jetbrains.kotlin.analysis.low.level.api.fir.util.errorWithFirSpecificEntries
+import org.jetbrains.kotlin.analysis.low.level.api.fir.util.isPartialBodyResolvable
 import org.jetbrains.kotlin.fir.declarations.*
 import org.jetbrains.kotlin.fir.expressions.FirBlock
 import org.jetbrains.kotlin.fir.expressions.FirExpression
@@ -180,6 +182,13 @@
     get() = this is FirFunction && body?.statements?.firstOrNull() is FirContractCallBlock
 
 private fun FirDeclaration.decreasePhase(newPhase: FirResolvePhase) {
+    if (isPartialBodyResolvable) {
+        val oldPhase = resolvePhase
+        if (oldPhase >= FirResolvePhase.BODY_RESOLVE.previous) {
+            partialBodyResolveState = null
+        }
+    }
+
     @OptIn(ResolveStateAccess::class)
     resolveState = newPhase.asResolveState()
 }
diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/lazy/resolve/LLFirModuleLazyDeclarationResolver.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/lazy/resolve/LLFirModuleLazyDeclarationResolver.kt
index a926875..86fd342 100644
--- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/lazy/resolve/LLFirModuleLazyDeclarationResolver.kt
+++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/lazy/resolve/LLFirModuleLazyDeclarationResolver.kt
@@ -10,6 +10,7 @@
 import org.jetbrains.kotlin.analysis.low.level.api.fir.projectStructure.llFirModuleData
 import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.llFirSession
 import org.jetbrains.kotlin.analysis.low.level.api.fir.transformers.LLFirLazyResolverRunner
+import org.jetbrains.kotlin.analysis.low.level.api.fir.transformers.PartialBodyAnalysisSuspendedException
 import org.jetbrains.kotlin.analysis.low.level.api.fir.util.checkCanceled
 import org.jetbrains.kotlin.analysis.low.level.api.fir.util.getContainingFile
 import org.jetbrains.kotlin.fir.FirElementWithResolveState
@@ -102,6 +103,8 @@
             if (toPhase == FirResolvePhase.IMPORTS) return
 
             lazyResolveTargets(target, toPhase)
+        } catch (_: PartialBodyAnalysisSuspendedException) {
+            // Do nothing, partial body resolve is complete
         } catch (e: Exception) {
             handleExceptionFromResolve(e, target, toPhase)
         }
diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/lazy/resolve/LLFirResolveDesignationCollector.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/lazy/resolve/LLFirResolveDesignationCollector.kt
index 49c6d5c..8e8d62e 100644
--- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/lazy/resolve/LLFirResolveDesignationCollector.kt
+++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/lazy/resolve/LLFirResolveDesignationCollector.kt
@@ -7,8 +7,10 @@
 
 import org.jetbrains.kotlin.analysis.low.level.api.fir.api.FirDesignation
 import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.LLFirClassWithAllCallablesResolveTarget
+import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.LLFirPartialBodyResolveTarget
 import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.LLFirResolveTarget
 import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.LLFirWholeElementResolveTarget
+import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.LLPartialBodyResolveRequest
 import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.asResolveTarget
 import org.jetbrains.kotlin.analysis.low.level.api.fir.api.throwUnexpectedFirElementError
 import org.jetbrains.kotlin.analysis.low.level.api.fir.api.tryCollectDesignation
@@ -39,6 +41,12 @@
         return getDesignationToResolve(target, ::LLFirWholeElementResolveTarget)
     }
 
+    fun getDesignationToResolveForPartialBody(request: LLPartialBodyResolveRequest): LLFirResolveTarget? {
+        return getDesignationToResolve(request.target) {
+            LLFirPartialBodyResolveTarget(it, request)
+        }
+    }
+
     private fun getDesignationToResolve(
         target: FirElementWithResolveState,
         resolveTarget: (FirDesignation) -> LLFirResolveTarget,
diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirBodyLazyResolver.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirBodyLazyResolver.kt
index 0b4d080..d23ccd8 100644
--- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirBodyLazyResolver.kt
+++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirBodyLazyResolver.kt
@@ -5,19 +5,28 @@
 
 package org.jetbrains.kotlin.analysis.low.level.api.fir.transformers
 
+import com.intellij.psi.util.descendantsOfType
 import kotlinx.collections.immutable.toPersistentList
+import org.jetbrains.kotlin.KtPsiSourceElement
 import org.jetbrains.kotlin.analysis.low.level.api.fir.api.FirDesignation
 import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getFirResolveSession
 import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getModule
+import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.LLFirPartialBodyResolveTarget
 import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.LLFirResolveTarget
+import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.LLPartialBodyResolveRequest
+import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.LLPartialBodyResolveSnapshot
+import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.LLPartialBodyResolveState
+import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.partialBodyResolveState
 import org.jetbrains.kotlin.analysis.low.level.api.fir.api.throwUnexpectedFirElementError
 import org.jetbrains.kotlin.analysis.low.level.api.fir.compile.codeFragmentScopeProvider
 import org.jetbrains.kotlin.analysis.low.level.api.fir.file.structure.LLFirDeclarationModificationService
 import org.jetbrains.kotlin.analysis.low.level.api.fir.projectStructure.llFirModuleData
 import org.jetbrains.kotlin.analysis.low.level.api.fir.state.LLFirResolvableResolveSession
 import org.jetbrains.kotlin.analysis.low.level.api.fir.util.*
+import org.jetbrains.kotlin.fir.FirElement
 import org.jetbrains.kotlin.fir.FirElementWithResolveState
 import org.jetbrains.kotlin.fir.FirSession
+import org.jetbrains.kotlin.fir.analysis.checkers.declaration.isLocalMember
 import org.jetbrains.kotlin.fir.declarations.*
 import org.jetbrains.kotlin.fir.declarations.utils.getExplicitBackingField
 import org.jetbrains.kotlin.fir.expressions.*
@@ -33,6 +42,7 @@
 import org.jetbrains.kotlin.fir.references.builder.buildExplicitSuperReference
 import org.jetbrains.kotlin.fir.references.builder.buildExplicitThisReference
 import org.jetbrains.kotlin.fir.resolve.FirCodeFragmentContext
+import org.jetbrains.kotlin.fir.resolve.ResolutionMode
 import org.jetbrains.kotlin.fir.resolve.ScopeSession
 import org.jetbrains.kotlin.fir.resolve.SessionHolderImpl
 import org.jetbrains.kotlin.fir.resolve.codeFragmentContext
@@ -41,7 +51,11 @@
 import org.jetbrains.kotlin.fir.resolve.dfa.cfg.isUsedInControlFlowGraphBuilderForClass
 import org.jetbrains.kotlin.fir.resolve.dfa.cfg.isUsedInControlFlowGraphBuilderForFile
 import org.jetbrains.kotlin.fir.resolve.dfa.cfg.isUsedInControlFlowGraphBuilderForScript
+import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformerDispatcher
 import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirBodyResolveTransformer
+import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer
+import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirExpressionsResolveTransformer
+import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.writeResultType
 import org.jetbrains.kotlin.fir.resolve.transformers.contracts.FirContractsDslNames
 import org.jetbrains.kotlin.fir.scopes.DelicateScopeAPI
 import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
@@ -50,10 +64,12 @@
 import org.jetbrains.kotlin.fir.types.isResolved
 import org.jetbrains.kotlin.fir.utils.exceptions.withFirEntry
 import org.jetbrains.kotlin.psi.KtCodeFragment
+import org.jetbrains.kotlin.psi.KtElement
 import org.jetbrains.kotlin.psi.KtFile
 import org.jetbrains.kotlin.utils.exceptions.checkWithAttachment
 import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
 import org.jetbrains.kotlin.utils.exceptions.withPsiEntry
+import java.util.concurrent.CancellationException
 
 internal object LLFirBodyLazyResolver : LLFirLazyResolver(FirResolvePhase.BODY_RESOLVE) {
     override fun createTargetResolver(target: LLFirResolveTarget): LLFirTargetResolver = LLFirBodyTargetResolver(target)
@@ -71,6 +87,215 @@
     }
 }
 
+internal class PartialBodyAnalysisSuspendedException : CancellationException("Partial body analysis was suspended")
+
+private class FirPartialBodyExpressionResolveTransformer(
+    transformer: FirAbstractBodyResolveTransformerDispatcher,
+    private val target: LLFirResolveTarget
+) : FirExpressionsResolveTransformer(transformer) {
+    private companion object {
+        // After a certain number of partial analyses,
+        // trigger the full analysis so we don't return to the same declaration over and over again
+        private const val MAX_ANALYSES_COUNT = 3
+    }
+
+    private var shouldStop = false
+
+    override fun <E : FirElement> transformElement(element: E, data: ResolutionMode): E {
+        if (shouldStop) {
+            return element
+        }
+
+        return super.transformElement(element, data)
+    }
+
+    override fun transformBlock(block: FirBlock, data: ResolutionMode): FirStatement {
+        val declaration = context.containerIfAny
+
+        val isApplicable = declaration is FirDeclaration
+                && !declaration.isLocalMember
+                && declaration.isPartialBodyResolvable
+                && declaration.body == block
+                && block.statements.size > 1
+
+        if (!isApplicable) {
+            return super.transformBlock(block, data)
+        }
+
+        require(data is ResolutionMode.ContextIndependent)
+
+        val state = declaration.partialBodyResolveState
+
+        if (target is LLFirPartialBodyResolveTarget && (state == null || state.performedAnalysesCount < MAX_ANALYSES_COUNT)) {
+            transformPartially(target.request, block, data, state)
+        } else {
+            transformFully(declaration, block, data, state)
+        }
+
+        return block
+    }
+
+    private fun transformPartially(
+        request: LLPartialBodyResolveRequest,
+        block: FirBlock,
+        data: ResolutionMode,
+        state: LLPartialBodyResolveState?
+    ): FirStatement {
+        val declaration = target.target as FirDeclaration
+
+        if (state == null) {
+            if (request.stopElement == null) {
+                // Without a 'stopElement', we resolve "the rest of the body".
+                // We didn't analyze the body yet, though.
+                // So here we just delegate to the non-partial implementation.
+                require(request.targetPsiStatementCount == request.totalPsiStatementCount)
+                return super.transformBlock(block, data)
+            }
+
+            context.forBlock(session) {
+                dataFlowAnalyzer.enterBlock(block)
+                if (transformStatementsPartially(request, block, data, startIndex = 0, performedAnalysesCount = 0)) {
+                    dataFlowAnalyzer.exitBlock(block)
+                }
+            }
+
+            return block
+        }
+
+        if (state.analyzedPsiStatementCount >= request.targetPsiStatementCount) {
+            // Nothing to analyze
+            return block
+        }
+
+        val resolveSnapshot = state.analysisStateSnapshot
+        checkWithAttachment(resolveSnapshot != null, { "Snapshot should be available for a partially analyzed declaration" }) {
+            withFirEntry("target", declaration)
+            withEntry("state", state) { it.toString() }
+        }
+
+        // Run analysis with the tower data context restored from the snapshot
+        context.withTowerDataContext(resolveSnapshot.towerDataContext) {
+            // Also, restore the previous state of the data flow graph from the snapshot
+            context.dataFlowAnalyzerContext.resetFrom(resolveSnapshot.dataFlowAnalyzerContext)
+
+            val isAnalyzedEntirely = transformStatementsPartially(
+                request, block, data,
+                startIndex = state.analyzedFirStatementCount,
+                performedAnalysesCount = state.performedAnalysesCount
+            )
+
+            if (isAnalyzedEntirely) {
+                dataFlowAnalyzer.exitBlock(block)
+            }
+        }
+
+        return block
+    }
+
+    private fun transformStatementsPartially(
+        request: LLPartialBodyResolveRequest,
+        block: FirBlock,
+        data: ResolutionMode,
+        startIndex: Int,
+        performedAnalysesCount: Int,
+    ): Boolean {
+        val declaration = request.target
+
+        val stopElement = request.stopElement
+        val stopElements = stopElement?.descendantsOfType<KtElement>(childrenFirst = false)?.toList().orEmpty()
+
+        var index = 0
+        val iterator = (block.statements as MutableList<FirStatement>).listIterator()
+
+        while (iterator.hasNext()) {
+            val statement = iterator.next()
+
+            if (index >= startIndex) {
+                if (stopElement != null && !shouldTransform(statement, stopElement, stopElements)) {
+                    // Here we reached a stop element.
+                    // It means all statements up to the target one are now analyzed.
+                    // So now we save the current context and suspend further analysis.
+                    declaration.partialBodyResolveState = LLPartialBodyResolveState(
+                        totalPsiStatementCount = request.totalPsiStatementCount,
+                        analyzedPsiStatementCount = request.targetPsiStatementCount,
+                        analyzedFirStatementCount = index,
+                        performedAnalysesCount = performedAnalysesCount + 1,
+                        analysisStateSnapshot = LLPartialBodyResolveSnapshot(
+                            towerDataContext = context.towerDataContext.createSnapshot(keepMutable = true),
+                            dataFlowAnalyzerContext = context.dataFlowAnalyzerContext
+                        )
+                    )
+                    shouldStop = true
+                    throw PartialBodyAnalysisSuspendedException()
+                    return false
+                }
+
+                val newStatement = statement.transform<FirStatement, ResolutionMode>(transformer, data)
+                if (statement !== newStatement) {
+                    iterator.set(newStatement)
+                }
+            }
+
+            index += 1
+        }
+
+        // Nothing stopped us from analyzing all statements for some reason.
+        // Most likely, we missed the stop element.
+        // Let's still wrap things out – the function is now fully analyzed.
+        block.transformOtherChildren(transformer, data)
+
+        // This makes the compiler think the declaration is fully resolved (see 'FirExpression.isResolved')
+        block.writeResultType(session)
+
+        declaration.partialBodyResolveState = LLPartialBodyResolveState(
+            totalPsiStatementCount = request.totalPsiStatementCount,
+            analyzedPsiStatementCount = request.totalPsiStatementCount,
+            analyzedFirStatementCount = index,
+            performedAnalysesCount = 1,
+            analysisStateSnapshot = null
+        )
+
+        return true
+    }
+
+    private fun transformFully(
+        declaration: FirDeclaration,
+        block: FirBlock,
+        data: ResolutionMode,
+        currentState: LLPartialBodyResolveState?
+    ): FirStatement {
+        if (currentState == null) {
+            // The declaration body is not resolved at all, and a full resolution is requested.
+            // So, here we delegate straight to the non-partial implementation.
+            return super.transformBlock(block, data)
+        }
+
+        val request = LLPartialBodyResolveRequest(
+            target = declaration,
+            totalPsiStatementCount = currentState.totalPsiStatementCount,
+            targetPsiStatementCount = currentState.totalPsiStatementCount,
+            stopElement = null
+        )
+
+        // Otherwise, use the partial resolve to finish the ongoing resolution
+        return transformPartially(request, block, data, currentState)
+    }
+
+    private fun shouldTransform(element: FirElement, stopElement: KtElement, stopElements: List<KtElement>): Boolean {
+        val source = element.source
+        if (source is KtPsiSourceElement) {
+            if (source.psi == stopElement) {
+                return false
+            } else if (source.psi in stopElements) {
+                println("found in children!")
+                return false
+            }
+        }
+
+        return true
+    }
+}
+
 /**
  * This resolver is responsible for [BODY_RESOLVE][FirResolvePhase.BODY_RESOLVE] phase.
  *
@@ -93,17 +318,28 @@
  * @see FirBodyResolveTransformer
  * @see FirResolvePhase.BODY_RESOLVE
  */
-private class LLFirBodyTargetResolver(target: LLFirResolveTarget) : LLFirAbstractBodyTargetResolver(
+private class LLFirBodyTargetResolver(private val target: LLFirResolveTarget) : LLFirAbstractBodyTargetResolver(
     target,
     FirResolvePhase.BODY_RESOLVE,
 ) {
-    override val transformer = object : FirBodyResolveTransformer(
+    override val transformer = BodyTransformerDispatcher()
+
+    inner class BodyTransformerDispatcher : FirAbstractBodyResolveTransformerDispatcher(
         resolveTargetSession,
         phase = resolverPhase,
         implicitTypeOnly = false,
-        scopeSession = resolveTargetScopeSession,
+        scopeSession = resolveTargetSession.getScopeSession(),
         returnTypeCalculator = createReturnTypeCalculator(),
+        expandTypeAliases = true
     ) {
+        override val expressionsTransformer: FirExpressionsResolveTransformer = if (target.target.isPartialBodyResolvable) {
+            FirPartialBodyExpressionResolveTransformer(this, target)
+        } else {
+            FirExpressionsResolveTransformer(this)
+        }
+
+        override val declarationsTransformer: FirDeclarationsResolveTransformer = FirDeclarationsResolveTransformer(this)
+
         override val preserveCFGForClasses: Boolean get() = false
         override val buildCfgForScripts: Boolean get() = false
         override val buildCfgForFiles: Boolean get() = false
@@ -357,7 +593,15 @@
     }
 
     override fun rawResolve(target: FirElementWithResolveState) {
-        super.rawResolve(target)
+        try {
+            super.rawResolve(target)
+        } catch (e: Throwable) {
+            if (e is PartialBodyAnalysisSuspendedException) {
+                // We successfully analyzed some part of the body so need to keep track of it
+                LLFirDeclarationModificationService.bodyResolved(target, resolverPhase)
+            }
+            throw e
+        }
 
         LLFirDeclarationModificationService.bodyResolved(target, resolverPhase)
     }
@@ -368,7 +612,20 @@
         builder.add(FirCodeFragment::block, FirCodeFragment::replaceBlock, ::blockGuard)
     }
 
-    val ANONYMOUS_INITIALIZER: StateKeeper<FirAnonymousInitializer, FirDesignation> = stateKeeper { builder, _, _ ->
+    val PARTIAL_BODY_RESOLVABLE: StateKeeper<FirDeclaration, FirDesignation> = stateKeeper { builder, declaration, context ->
+        builder.add(
+            provider = FirDeclaration::partialBodyResolveState,
+            mutator = { _, _ ->
+                // On exceptions, remove the partial body resolve state so the function will be analyzed from the beginning
+                null
+            }
+        )
+    }
+
+    val ANONYMOUS_INITIALIZER: StateKeeper<FirAnonymousInitializer, FirDesignation> = stateKeeper { builder, initializer, designation ->
+        builder.add(PARTIAL_BODY_RESOLVABLE, designation)
+        preserveResolvedStatements(builder, initializer)
+
         builder.add(FirAnonymousInitializer::body, FirAnonymousInitializer::replaceBody, ::blockGuard)
         builder.add(FirAnonymousInitializer::controlFlowGraphReference, FirAnonymousInitializer::replaceControlFlowGraphReference)
     }
@@ -385,7 +642,8 @@
         builder.add(FirFunction::returnTypeRef, FirFunction::replaceReturnTypeRef)
 
         if (!isCallableWithSpecialBody(function)) {
-            preserveContractBlock(builder, function)
+            builder.add(PARTIAL_BODY_RESOLVABLE, designation)
+            preserveResolvedStatements(builder, function)
 
             builder.add(FirFunction::body, FirFunction::replaceBody, ::blockGuard)
             builder.entityList(function.valueParameters, VALUE_PARAMETER, designation)
@@ -439,7 +697,20 @@
     }
 }
 
-private fun StateKeeperScope<FirFunction, FirDesignation>.preserveContractBlock(builder: StateKeeperBuilder, function: FirFunction) {
+@Suppress("CONTEXT_RECEIVERS_DEPRECATED")
+private fun StateKeeperScope<FirAnonymousInitializer, FirDesignation>.preserveResolvedStatements(
+    builder: StateKeeperBuilder,
+    initializer: FirAnonymousInitializer
+) {
+    preservePartialBodyResolveResult(builder, initializer, FirAnonymousInitializer::body)
+}
+
+@Suppress("CONTEXT_RECEIVERS_DEPRECATED")
+private fun StateKeeperScope<FirFunction, FirDesignation>.preserveResolvedStatements(builder: StateKeeperBuilder, function: FirFunction) {
+    if (preservePartialBodyResolveResult(builder, function, FirFunction::body)) {
+        return
+    }
+
     val oldBody = function.body
     if (oldBody == null || oldBody is FirLazyBlock) {
         return
@@ -477,6 +748,37 @@
     }
 }
 
+@Suppress("CONTEXT_RECEIVERS_DEPRECATED")
+private fun <T : FirDeclaration> StateKeeperScope<T, FirDesignation>.preservePartialBodyResolveResult(
+    builder: StateKeeperBuilder,
+    declaration: T,
+    bodySupplier: (T) -> FirBlock?,
+): Boolean {
+    val oldBody = bodySupplier(declaration)
+    if (oldBody == null || oldBody is FirLazyBlock) {
+        return false
+    }
+
+    val state = declaration.partialBodyResolveState
+    if (state == null) {
+        return false
+    }
+
+    builder.postProcess {
+        val newBody = bodySupplier(declaration)
+        if (newBody != null && newBody.statements.isNotEmpty()) {
+            require(oldBody.statements.size == newBody.statements.size) { "Bodies do not match" }
+
+            val newBodyStatements = newBody.statements as MutableList<FirStatement>
+            for (index in 0..<state.analyzedFirStatementCount) {
+                newBodyStatements[index] = oldBody.statements[index]
+            }
+        }
+    }
+
+    return true
+}
+
 private val FirFunction.isCertainlyResolved: Boolean
     get() {
         if (this is FirPropertyAccessor) {
diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirLazyResolver.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirLazyResolver.kt
index 156271e..3f74ef7 100644
--- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirLazyResolver.kt
+++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirLazyResolver.kt
@@ -5,6 +5,7 @@
 
 package org.jetbrains.kotlin.analysis.low.level.api.fir.transformers
 
+import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.LLFirPartialBodyResolveTarget
 import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.LLFirResolveTarget
 import org.jetbrains.kotlin.analysis.low.level.api.fir.util.checkCanceled
 import org.jetbrains.kotlin.analysis.low.level.api.fir.util.checkPhase
@@ -32,7 +33,10 @@
         )
 
         resolver.resolveDesignation()
-        target.forEachTarget(::checkIsResolved)
+
+        if (target !is LLFirPartialBodyResolveTarget) {
+            target.forEachTarget(::checkIsResolved)
+        }
 
         checkCanceled()
     }
diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/StateKeeper.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/StateKeeper.kt
index b6cd62b..9d577aa 100644
--- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/StateKeeper.kt
+++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/StateKeeper.kt
@@ -272,6 +272,8 @@
         prepareTarget(target)
         preservedState.postProcess()
         return action()
+    } catch (e: PartialBodyAnalysisSuspendedException) {
+        throw e
     } catch (e: Throwable) {
         preservedState?.restore()
         throw e
diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/declarationUtils.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/declarationUtils.kt
index 024676b..2fc03e8 100644
--- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/declarationUtils.kt
+++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/declarationUtils.kt
@@ -17,7 +17,9 @@
 import org.jetbrains.kotlin.analysis.low.level.api.fir.file.builder.LLFirFileBuilder
 import org.jetbrains.kotlin.analysis.low.level.api.fir.providers.LLFirProvider
 import org.jetbrains.kotlin.descriptors.Visibilities
+import org.jetbrains.kotlin.fir.FirElementWithResolveState
 import org.jetbrains.kotlin.fir.declarations.*
+import org.jetbrains.kotlin.fir.expressions.FirBlock
 import org.jetbrains.kotlin.fir.psi
 import org.jetbrains.kotlin.fir.realPsi
 import org.jetbrains.kotlin.fir.resolve.providers.FirProvider
@@ -232,6 +234,20 @@
     }
 }
 
+internal val FirElementWithResolveState.isPartialBodyResolvable: Boolean
+    get() = when (this) {
+        is FirFunction -> this !is FirErrorFunction
+        is FirAnonymousInitializer -> true
+        else -> false
+    }
+
+internal val FirElementWithResolveState.body: FirBlock?
+    get() = when (this) {
+        is FirFunction -> body
+        is FirAnonymousInitializer -> body
+        else -> null
+    }
+
 /**
  * Some "local" declarations are not local from the lazy resolution perspective.
  */
diff --git a/analysis/low-level-api-fir/testData/getOrBuildFir/partialBodyAnalysis/simple.kt b/analysis/low-level-api-fir/testData/getOrBuildFir/partialBodyAnalysis/simple.kt
new file mode 100644
index 0000000..dc63561
--- /dev/null
+++ b/analysis/low-level-api-fir/testData/getOrBuildFir/partialBodyAnalysis/simple.kt
@@ -0,0 +1,7 @@
+fun test() {
+    consume(<expr>1 + 2</expr>)
+    consume(<expr_1>"foo"</expr_1>)
+    consume('b')
+}
+
+fun consume(a: Int) {}
\ No newline at end of file
diff --git a/analysis/low-level-api-fir/testData/getOrBuildFir/partialBodyAnalysis/simple.txt b/analysis/low-level-api-fir/testData/getOrBuildFir/partialBodyAnalysis/simple.txt
new file mode 100644
index 0000000..d59699c
--- /dev/null
+++ b/analysis/low-level-api-fir/testData/getOrBuildFir/partialBodyAnalysis/simple.txt
@@ -0,0 +1,39 @@
+KT element: KtValueArgument
+KT element text:
+1 + 2
+FIR element: FirIntegerLiteralOperatorCallImpl
+FIR source kind: KtRealSourceElementKind
+
+FIR element rendered:
+Int(1).R|kotlin/Int.plus|(Int(2))
+
+FIR FILE:
+FILE: [ResolvedTo(IMPORTS)] simple.kt
+    public final [ResolvedTo(ANNOTATION_ARGUMENTS)] [PartialBodyResolveStateKey=1(1/3) #1] fun test(): R|kotlin/Unit| {
+        R|/consume|(Int(1).R|kotlin/Int.plus|(Int(2)))
+        consume#(String(foo))
+        consume#(Char(b))
+    }
+    public final [ResolvedTo(CONTRACTS)] fun consume([ResolvedTo(CONTRACTS)] a: R|kotlin/Int|): R|kotlin/Unit| {
+    }
+
+=====
+
+KT element: KtValueArgument
+KT element text:
+"foo"
+FIR element: FirLiteralExpressionImpl
+FIR source kind: KtRealSourceElementKind
+
+FIR element rendered:
+String(foo)
+
+FIR FILE:
+FILE: [ResolvedTo(IMPORTS)] simple.kt
+    public final [ResolvedTo(ANNOTATION_ARGUMENTS)] [PartialBodyResolveStateKey=2(2/3) #1] fun test(): R|kotlin/Unit| {
+        R|/consume|(Int(1).R|kotlin/Int.plus|(Int(2)))
+        R|/consume<Inapplicable(INAPPLICABLE): /consume>#|(String(foo))
+        consume#(Char(b))
+    }
+    public final [ResolvedTo(CONTRACTS)] fun consume([ResolvedTo(CONTRACTS)] a: R|kotlin/Int|): R|kotlin/Unit| {
+    }
diff --git a/analysis/low-level-api-fir/testData/getOrBuildFir/partialBodyAnalysis/simpleDataFlow.kt b/analysis/low-level-api-fir/testData/getOrBuildFir/partialBodyAnalysis/simpleDataFlow.kt
new file mode 100644
index 0000000..865cfec
--- /dev/null
+++ b/analysis/low-level-api-fir/testData/getOrBuildFir/partialBodyAnalysis/simpleDataFlow.kt
@@ -0,0 +1,7 @@
+fun test(obj: Any) {
+    if (<expr>obj</expr> !is String) return
+    consume(<expr_1>obj</expr_1>)
+    obj.toString()
+}
+
+fun <T : CharSequence> consume(obj: T) {}
\ No newline at end of file
diff --git a/analysis/low-level-api-fir/testData/getOrBuildFir/partialBodyAnalysis/simpleDataFlow.txt b/analysis/low-level-api-fir/testData/getOrBuildFir/partialBodyAnalysis/simpleDataFlow.txt
new file mode 100644
index 0000000..086f84c
--- /dev/null
+++ b/analysis/low-level-api-fir/testData/getOrBuildFir/partialBodyAnalysis/simpleDataFlow.txt
@@ -0,0 +1,48 @@
+KT element: KtNameReferenceExpression
+KT element text:
+obj
+FIR element: FirPropertyAccessExpressionImpl
+FIR source kind: KtRealSourceElementKind
+
+FIR element rendered:
+R|<local>/obj|
+
+FIR FILE:
+FILE: [ResolvedTo(IMPORTS)] simpleDataFlow.kt
+    public final [ResolvedTo(ANNOTATION_ARGUMENTS)] [PartialBodyResolveStateKey=1(1/3) #1] fun test([ResolvedTo(ANNOTATION_ARGUMENTS)] obj: R|kotlin/Any|): R|kotlin/Unit| {
+        when () {
+            (R|<local>/obj| !is R|kotlin/String|) ->  {
+                ^test Unit
+            }
+        }
+
+        consume#(obj#)
+        obj#.toString#()
+    }
+    public? final? [ResolvedTo(RAW_FIR)] fun <[ResolvedTo(RAW_FIR)] T : CharSequence> consume([ResolvedTo(RAW_FIR)] obj: T): R|kotlin/Unit| { LAZY_BLOCK }
+
+=====
+
+KT element: KtValueArgument
+KT element text:
+obj
+FIR element: FirSmartCastExpressionImpl
+FIR source kind: SmartCastExpression
+
+FIR element rendered:
+R|<local>/obj|
+
+FIR FILE:
+FILE: [ResolvedTo(IMPORTS)] simpleDataFlow.kt
+    public final [ResolvedTo(ANNOTATION_ARGUMENTS)] [PartialBodyResolveStateKey=2(2/3) #1] fun test([ResolvedTo(ANNOTATION_ARGUMENTS)] obj: R|kotlin/Any|): R|kotlin/Unit| {
+        when () {
+            (R|<local>/obj| !is R|kotlin/String|) ->  {
+                ^test Unit
+            }
+        }
+
+        R|/consume|<R|kotlin/String|>(R|<local>/obj|)
+        obj#.toString#()
+    }
+    public final [ResolvedTo(CONTRACTS)] fun <[ResolvedTo(CONTRACTS)] T : R|kotlin/CharSequence|> consume([ResolvedTo(CONTRACTS)] obj: R|T|): R|kotlin/Unit| {
+    }
diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/AbstractGetOrBuildFirTest.kt b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/AbstractGetOrBuildFirTest.kt
index 6aab2f5..1d6e351 100644
--- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/AbstractGetOrBuildFirTest.kt
+++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/AbstractGetOrBuildFirTest.kt
@@ -27,18 +27,43 @@
 
 abstract class AbstractGetOrBuildFirTest : AbstractAnalysisApiBasedTest() {
     override fun doTestByMainFile(mainFile: KtFile, mainModule: KtTestModule, testServices: TestServices) {
-        val selectedElement = testServices.expressionMarkerProvider
-            .getTopmostSelectedElementOfTypeByDirective(mainFile, mainModule) as KtElement
+        val results = resolveWithClearCaches(mainFile) { session ->
+            val elementsToAnalyze = sequence<KtElement> {
+                val expressionMarkerProvider = testServices.expressionMarkerProvider
 
-        val actual = resolveWithClearCaches(mainFile) { session ->
-            renderActualFir(
-                fir = selectedElement.getOrBuildFir(session),
-                ktElement = selectedElement,
-                renderingOptions = testServices.firRenderingOptions,
-                firFile = mainFile.getOrBuildFirFile(session),
-            )
+                val firstCandidate = expressionMarkerProvider
+                    .getTopmostSelectedElementOfTypeByDirective(mainFile, mainModule, qualifier = "")
+
+                yield(firstCandidate as KtElement)
+
+                var index = 1
+                while (true) {
+                    val candidate = expressionMarkerProvider
+                        .getTopmostSelectedElementOfTypeByDirectiveOrNull(mainFile, mainModule, qualifier = "$index")
+                        ?: break
+                    yield(candidate as KtElement)
+                    index += 1
+                }
+            }.toList()
+
+            val renderingOptions = testServices.firRenderingOptions
+                .copy(renderKtText = elementsToAnalyze.size > 1)
+
+            val results = mutableListOf<String>()
+
+            for (element in elementsToAnalyze) {
+                results += renderActualFir(
+                    fir = element.getOrBuildFir(session),
+                    ktElement = element,
+                    renderingOptions = renderingOptions,
+                    firFile = mainFile.getOrBuildFirFile(session),
+                )
+            }
+
+            return@resolveWithClearCaches results
         }
 
+        val actual = results.joinToString(separator = "\n\n=====\n\n")
         testServices.assertions.assertEqualsToTestDataFileSibling(actual)
     }
 }
diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/OutOfContentRootGetOrBuildFirTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/OutOfContentRootGetOrBuildFirTestGenerated.java
index a3869d6..153b6c4 100644
--- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/OutOfContentRootGetOrBuildFirTestGenerated.java
+++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/OutOfContentRootGetOrBuildFirTestGenerated.java
@@ -1217,6 +1217,28 @@
   }
 
   @Nested
+  @TestMetadata("analysis/low-level-api-fir/testData/getOrBuildFir/partialBodyAnalysis")
+  @TestDataPath("$PROJECT_ROOT")
+  public class PartialBodyAnalysis {
+    @Test
+    public void testAllFilesPresentInPartialBodyAnalysis() {
+      KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/low-level-api-fir/testData/getOrBuildFir/partialBodyAnalysis"), Pattern.compile("^(.+)\\.(kt)$"), null, true);
+    }
+
+    @Test
+    @TestMetadata("simple.kt")
+    public void testSimple() {
+      runTest("analysis/low-level-api-fir/testData/getOrBuildFir/partialBodyAnalysis/simple.kt");
+    }
+
+    @Test
+    @TestMetadata("simpleDataFlow.kt")
+    public void testSimpleDataFlow() {
+      runTest("analysis/low-level-api-fir/testData/getOrBuildFir/partialBodyAnalysis/simpleDataFlow.kt");
+    }
+  }
+
+  @Nested
   @TestMetadata("analysis/low-level-api-fir/testData/getOrBuildFir/qualifiedExpressions")
   @TestDataPath("$PROJECT_ROOT")
   public class QualifiedExpressions {
diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/ScriptGetOrBuildFirTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/ScriptGetOrBuildFirTestGenerated.java
index 7098015..939913f 100644
--- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/ScriptGetOrBuildFirTestGenerated.java
+++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/ScriptGetOrBuildFirTestGenerated.java
@@ -743,6 +743,16 @@
   }
 
   @Nested
+  @TestMetadata("analysis/low-level-api-fir/testData/getOrBuildFir/partialBodyAnalysis")
+  @TestDataPath("$PROJECT_ROOT")
+  public class PartialBodyAnalysis {
+    @Test
+    public void testAllFilesPresentInPartialBodyAnalysis() {
+      KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/low-level-api-fir/testData/getOrBuildFir/partialBodyAnalysis"), Pattern.compile("^(.+)\\.(kts)$"), null, true);
+    }
+  }
+
+  @Nested
   @TestMetadata("analysis/low-level-api-fir/testData/getOrBuildFir/qualifiedExpressions")
   @TestDataPath("$PROJECT_ROOT")
   public class QualifiedExpressions {
diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/SourceGetOrBuildFirTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/SourceGetOrBuildFirTestGenerated.java
index d170d84..4a3c432 100644
--- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/SourceGetOrBuildFirTestGenerated.java
+++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/SourceGetOrBuildFirTestGenerated.java
@@ -1217,6 +1217,28 @@
   }
 
   @Nested
+  @TestMetadata("analysis/low-level-api-fir/testData/getOrBuildFir/partialBodyAnalysis")
+  @TestDataPath("$PROJECT_ROOT")
+  public class PartialBodyAnalysis {
+    @Test
+    public void testAllFilesPresentInPartialBodyAnalysis() {
+      KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/low-level-api-fir/testData/getOrBuildFir/partialBodyAnalysis"), Pattern.compile("^(.+)\\.(kt)$"), null, true);
+    }
+
+    @Test
+    @TestMetadata("simple.kt")
+    public void testSimple() {
+      runTest("analysis/low-level-api-fir/testData/getOrBuildFir/partialBodyAnalysis/simple.kt");
+    }
+
+    @Test
+    @TestMetadata("simpleDataFlow.kt")
+    public void testSimpleDataFlow() {
+      runTest("analysis/low-level-api-fir/testData/getOrBuildFir/partialBodyAnalysis/simpleDataFlow.kt");
+    }
+  }
+
+  @Nested
   @TestMetadata("analysis/low-level-api-fir/testData/getOrBuildFir/qualifiedExpressions")
   @TestDataPath("$PROJECT_ROOT")
   public class QualifiedExpressions {
diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/services/FirRenderingOptions.kt b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/services/FirRenderingOptions.kt
index d3dbeaa..90f3f7b 100644
--- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/services/FirRenderingOptions.kt
+++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/services/FirRenderingOptions.kt
@@ -8,7 +8,7 @@
 import org.jetbrains.kotlin.test.services.TestService
 import org.jetbrains.kotlin.test.services.TestServices
 
-internal class FirRenderingOptions(
+internal data class FirRenderingOptions(
     val renderKtText: Boolean = false,
     val renderKtFileName: Boolean = false,
     val renderContainerSource: Boolean = false,
diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt
index 3424c16..29a367f8 100644
--- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt
+++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt
@@ -43,10 +43,13 @@
 import org.jetbrains.kotlin.util.OperatorNameConventions
 
 class DataFlowAnalyzerContext(private val session: FirSession) {
-    val graphBuilder: ControlFlowGraphBuilder = ControlFlowGraphBuilder()
-    internal val variableAssignmentAnalyzer: FirLocalVariableAssignmentAnalyzer = FirLocalVariableAssignmentAnalyzer()
+    internal var graphBuilder: ControlFlowGraphBuilder = ControlFlowGraphBuilder()
+        private set
 
-    var variableStorage: VariableStorage = VariableStorage(session)
+    internal var variableAssignmentAnalyzer: FirLocalVariableAssignmentAnalyzer = FirLocalVariableAssignmentAnalyzer()
+        private set
+
+    internal var variableStorage: VariableStorage = VariableStorage(session)
         private set
 
     private var assignmentCounter = 0
@@ -55,6 +58,15 @@
         return assignmentCounter++
     }
 
+    fun resetFrom(source: DataFlowAnalyzerContext) {
+        reset()
+
+        graphBuilder = source.graphBuilder
+        variableAssignmentAnalyzer = source.variableAssignmentAnalyzer
+        variableStorage = source.variableStorage
+        assignmentCounter = source.assignmentCounter
+    }
+
     fun reset() {
         graphBuilder.reset()
         variableAssignmentAnalyzer.reset()
diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/BodyResolveContext.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/BodyResolveContext.kt
index cbc79db..cf81c62 100644
--- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/BodyResolveContext.kt
+++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/BodyResolveContext.kt
@@ -137,6 +137,17 @@
         }
     }
 
+    @OptIn(PrivateForInline::class)
+    inline fun <T> withTowerDataContext(newContext: FirTowerDataContext, f: () -> T): T {
+        val initialContext = towerDataContext
+        return try {
+            replaceTowerDataContext(newContext)
+            f()
+        } finally {
+            replaceTowerDataContext(initialContext)
+        }
+    }
+
     private inline fun <R> withLambdaBeingAnalyzedInDependentContext(lambda: FirAnonymousFunctionSymbol, l: () -> R): R {
         anonymousFunctionsAnalyzedInDependentContext.add(lambda)
         return try {