From 9a200505e6beb66f2bbe6de20606c81b3c71bcc9 Mon Sep 17 00:00:00 2001 From: aviroop Date: Thu, 30 Oct 2025 17:52:58 +0000 Subject: [PATCH 1/7] Add more tests for header compilation. ^KT-78422 --- .../headerMode/classDeclaration.fir.txt | 6 +++ .../resolve/headerMode/classDeclaration.kt | 5 ++ .../headerMode/constructorDeclaration.fir.txt | 45 +++++++++++++++++ .../headerMode/constructorDeclaration.kt | 22 +++++++++ .../headerMode/enumClassDeclaration.fir.txt | 49 +++++++++++++++++++ .../headerMode/enumClassDeclaration.kt | 21 ++++++++ .../headerMode/functionDeclaration.fir.txt | 20 ++++++++ .../resolve/headerMode/functionDeclaration.kt | 30 ++++++++++++ .../headerMode/objectDeclaration.fir.txt | 6 +++ .../resolve/headerMode/objectDeclaration.kt | 5 ++ 10 files changed, 209 insertions(+) create mode 100644 compiler/fir/analysis-tests/testData/resolve/headerMode/constructorDeclaration.fir.txt create mode 100644 compiler/fir/analysis-tests/testData/resolve/headerMode/constructorDeclaration.kt create mode 100644 compiler/fir/analysis-tests/testData/resolve/headerMode/enumClassDeclaration.fir.txt create mode 100644 compiler/fir/analysis-tests/testData/resolve/headerMode/enumClassDeclaration.kt diff --git a/compiler/fir/analysis-tests/testData/resolve/headerMode/classDeclaration.fir.txt b/compiler/fir/analysis-tests/testData/resolve/headerMode/classDeclaration.fir.txt index a13d70a2e206f..c78ee1ce45990 100644 --- a/compiler/fir/analysis-tests/testData/resolve/headerMode/classDeclaration.fir.txt +++ b/compiler/fir/analysis-tests/testData/resolve/headerMode/classDeclaration.fir.txt @@ -21,6 +21,12 @@ FILE: classDeclaration.kt public final fun funD(): R|kotlin/Int| + public final inline fun funE(): R|kotlin/String| { + local final fun funF(): R|kotlin/String| + + ^funE R|/funF|() + } + } public abstract interface B : R|kotlin/Any| { public open fun funA(): R|kotlin/String| diff --git a/compiler/fir/analysis-tests/testData/resolve/headerMode/classDeclaration.kt b/compiler/fir/analysis-tests/testData/resolve/headerMode/classDeclaration.kt index 8bf2a50c4177c..32d1560181af8 100644 --- a/compiler/fir/analysis-tests/testData/resolve/headerMode/classDeclaration.kt +++ b/compiler/fir/analysis-tests/testData/resolve/headerMode/classDeclaration.kt @@ -25,6 +25,11 @@ class A { } fun funD() = 1 + 2 + + inline fun funE(): String { + fun funF() = "funF body" + return funF() + } } interface B { diff --git a/compiler/fir/analysis-tests/testData/resolve/headerMode/constructorDeclaration.fir.txt b/compiler/fir/analysis-tests/testData/resolve/headerMode/constructorDeclaration.fir.txt new file mode 100644 index 0000000000000..bf834d5d96643 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/headerMode/constructorDeclaration.fir.txt @@ -0,0 +1,45 @@ +FILE: constructorDeclaration.kt + public final class A : R|kotlin/Any| { + public constructor(a: R|kotlin/String|, b: R|kotlin/Int|): R|A| { + super() + } + + public final val a: R|kotlin/String| = R|/a| + public get(): R|kotlin/String| + + public final val b: R|kotlin/Int| = R|/b| + public get(): R|kotlin/Int| + + public constructor(e: R|kotlin/String|): R|A| { + this(R|/e|, Int(0)) + lval message: R|kotlin/String| = String(Secondary constructor body) + } + + public constructor(): R|A| { + this(String()) + lval message: R|kotlin/String| = String(Secondary constructor body with delegated constructor call) + } + + } + public final class B : R|kotlin/Any| { + private constructor(a: R|kotlin/String|, b: R|kotlin/Int|): R|B| { + super() + } + + public final val a: R|kotlin/String| = R|/a| + public get(): R|kotlin/String| + + public final val b: R|kotlin/Int| = R|/b| + public get(): R|kotlin/Int| + + public constructor(e: R|kotlin/String|): R|B| { + this(R|/e|, Int(0)) + lval message: R|kotlin/String| = String(Secondary constructor body) + } + + public constructor(): R|B| { + this(String()) + lval message: R|kotlin/String| = String(Secondary constructor body with delegated constructor call) + } + + } diff --git a/compiler/fir/analysis-tests/testData/resolve/headerMode/constructorDeclaration.kt b/compiler/fir/analysis-tests/testData/resolve/headerMode/constructorDeclaration.kt new file mode 100644 index 0000000000000..af45301414b84 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/headerMode/constructorDeclaration.kt @@ -0,0 +1,22 @@ +// RUN_PIPELINE_TILL: BACKEND +// FIR_DUMP +class A(val a: String, val b: Int) { + constructor(e: String): this(e, 0) { + val message = "Secondary constructor body" + } + constructor(): this("") { + val message = "Secondary constructor body with delegated constructor call" + } +} + +class B private constructor(val a: String, val b: Int) { + constructor(e: String): this(e, 0) { + val message = "Secondary constructor body" + } + constructor(): this("") { + val message = "Secondary constructor body with delegated constructor call" + } +} + +/* GENERATED_FIR_TAGS: classDeclaration, functionDeclaration, primaryConstructor, propertyDeclaration, +secondaryConstructor */ diff --git a/compiler/fir/analysis-tests/testData/resolve/headerMode/enumClassDeclaration.fir.txt b/compiler/fir/analysis-tests/testData/resolve/headerMode/enumClassDeclaration.fir.txt new file mode 100644 index 0000000000000..bd9e7fca1df69 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/headerMode/enumClassDeclaration.fir.txt @@ -0,0 +1,49 @@ +FILE: enumClassDeclaration.kt + public final enum class A : R|kotlin/Enum| { + private constructor(): R|A| { + super|>() + } + + public final static enum entry EAST: R|A| + public final static enum entry WEST: R|A| + public final static fun values(): R|kotlin/Array| + + public final static fun valueOf(value: R|kotlin/String|): R|A| + + public final static val entries: R|kotlin/enums/EnumEntries| + public get(): R|kotlin/enums/EnumEntries| + + } + public final enum class B : R|kotlin/Enum| { + private constructor(): R|B| { + super|>() + } + + public final static enum entry NORTH: R|B| = object : R|B| { + private constructor(): R|| { + super() + } + + public open override fun getString(): R|kotlin/String| + + } + + public final static enum entry SOUTH: R|B| = object : R|B| { + private constructor(): R|| { + super() + } + + public open override fun getString(): R|kotlin/String| + + } + + public abstract fun getString(): R|kotlin/String| + + public final static fun values(): R|kotlin/Array| + + public final static fun valueOf(value: R|kotlin/String|): R|B| + + public final static val entries: R|kotlin/enums/EnumEntries| + public get(): R|kotlin/enums/EnumEntries| + + } diff --git a/compiler/fir/analysis-tests/testData/resolve/headerMode/enumClassDeclaration.kt b/compiler/fir/analysis-tests/testData/resolve/headerMode/enumClassDeclaration.kt new file mode 100644 index 0000000000000..3dfbb88e069ea --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/headerMode/enumClassDeclaration.kt @@ -0,0 +1,21 @@ +// RUN_PIPELINE_TILL: BACKEND +// FIR_DUMP +enum class A { + EAST, + WEST +} + +enum class B { + NORTH { + override fun getString() = "north" + }, + SOUTH { + override fun getString(): String { + return "south" + } + }; + + abstract fun getString(): String +} + +/* GENERATED_FIR_TAGS: enumDeclaration, enumEntry */ diff --git a/compiler/fir/analysis-tests/testData/resolve/headerMode/functionDeclaration.fir.txt b/compiler/fir/analysis-tests/testData/resolve/headerMode/functionDeclaration.fir.txt index 437bfa60dc812..374a0e3a14c8d 100644 --- a/compiler/fir/analysis-tests/testData/resolve/headerMode/functionDeclaration.fir.txt +++ b/compiler/fir/analysis-tests/testData/resolve/headerMode/functionDeclaration.fir.txt @@ -11,3 +11,23 @@ FILE: functionDeclaration.kt private final fun funC(): R|kotlin/String| public final fun funD(): R|kotlin/Int| + public final inline fun funE(): R|kotlin/String| { + local final fun funF(): R|kotlin/String| + + ^funE R|/funF|() + } + public final inline fun funG(): R|kotlin/String| { + local final class classA : R|kotlin/Any| { + public constructor(): R|/classA| { + super() + } + + public final fun funH(): R|kotlin/String| + + } + + lval a: R|/classA| = R|/classA.classA|() + ^funG R|/a|.R|/funH|() + } + public final fun funI(): R|kotlin/Int| + public final fun funJ(): R|kotlin/String| diff --git a/compiler/fir/analysis-tests/testData/resolve/headerMode/functionDeclaration.kt b/compiler/fir/analysis-tests/testData/resolve/headerMode/functionDeclaration.kt index b079cdb85db35..682707fac2a28 100644 --- a/compiler/fir/analysis-tests/testData/resolve/headerMode/functionDeclaration.kt +++ b/compiler/fir/analysis-tests/testData/resolve/headerMode/functionDeclaration.kt @@ -3,14 +3,17 @@ import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract +// Public function fun funA(): String { return "funA body" } +// Inline function inline fun funB(): String { return "funB body" } +// Function with contract @OptIn(ExperimentalContracts::class) fun isNotNull(value: Any?): Boolean { contract { @@ -19,11 +22,38 @@ fun isNotNull(value: Any?): Boolean { return value != null } +// Private function private fun funC(): String { return "funC body" } +// Implicit return type fun funD() = 1 + 2 +// Function inside a function +inline fun funE(): String { + fun funF(): String { + return "funF body" + } + return funF() +} + +// Class inside a function +inline fun funG(): String { + class classA { + fun funH() = "funH body" + } + val a = classA() + return a.funH() +} + +// Implicit type reference from another function. +fun funI() = funD() + +fun funJ(): String { + inline fun funK() = "funK body" + return funK() +} + /* GENERATED_FIR_TAGS: classReference, contractConditionalEffect, contracts, functionDeclaration, inline, nullableType, stringLiteral */ diff --git a/compiler/fir/analysis-tests/testData/resolve/headerMode/objectDeclaration.fir.txt b/compiler/fir/analysis-tests/testData/resolve/headerMode/objectDeclaration.fir.txt index 3da4fa38b5a1f..05714b225b1bc 100644 --- a/compiler/fir/analysis-tests/testData/resolve/headerMode/objectDeclaration.fir.txt +++ b/compiler/fir/analysis-tests/testData/resolve/headerMode/objectDeclaration.fir.txt @@ -21,4 +21,10 @@ FILE: objectDeclaration.kt public final fun funD(): R|kotlin/Int| + public final inline fun funE(): R|kotlin/String| { + local final fun funF(): R|kotlin/String| + + ^funE R|/funF|() + } + } diff --git a/compiler/fir/analysis-tests/testData/resolve/headerMode/objectDeclaration.kt b/compiler/fir/analysis-tests/testData/resolve/headerMode/objectDeclaration.kt index 93a679fc9f311..69a04868ddb4a 100644 --- a/compiler/fir/analysis-tests/testData/resolve/headerMode/objectDeclaration.kt +++ b/compiler/fir/analysis-tests/testData/resolve/headerMode/objectDeclaration.kt @@ -25,6 +25,11 @@ object A { } fun funD() = 1 + 2 + + inline fun funE(): String { + fun funF() = "funF body" + return funF() + } } /* GENERATED_FIR_TAGS: classReference, contractConditionalEffect, contracts, functionDeclaration, inline, nullableType, From 5e7de6b9277b73e3af46b50db5fc7cbbfd0cf10d Mon Sep 17 00:00:00 2001 From: aviroop Date: Thu, 30 Oct 2025 17:55:13 +0000 Subject: [PATCH 2/7] Fix header compilation for function / class declarations nested inside inline functions. ^KT-78422 --- .../testData/resolve/headerMode/classDeclaration.fir.txt | 4 +++- .../resolve/headerMode/enumClassDeclaration.fir.txt | 8 ++++++-- .../resolve/headerMode/functionDeclaration.fir.txt | 8 ++++++-- .../testData/resolve/headerMode/objectDeclaration.fir.txt | 4 +++- .../converter/LightTreeRawFirDeclarationBuilder.kt | 3 ++- .../jetbrains/kotlin/fir/builder/AbstractRawFirBuilder.kt | 5 ++++- .../src/org/jetbrains/kotlin/fir/builder/Context.kt | 2 ++ .../body/resolve/FirDeclarationsResolveTransformer.kt | 2 +- 8 files changed, 27 insertions(+), 9 deletions(-) diff --git a/compiler/fir/analysis-tests/testData/resolve/headerMode/classDeclaration.fir.txt b/compiler/fir/analysis-tests/testData/resolve/headerMode/classDeclaration.fir.txt index c78ee1ce45990..139efa8e23e1b 100644 --- a/compiler/fir/analysis-tests/testData/resolve/headerMode/classDeclaration.fir.txt +++ b/compiler/fir/analysis-tests/testData/resolve/headerMode/classDeclaration.fir.txt @@ -22,7 +22,9 @@ FILE: classDeclaration.kt public final fun funD(): R|kotlin/Int| public final inline fun funE(): R|kotlin/String| { - local final fun funF(): R|kotlin/String| + local final fun funF(): R|kotlin/String| { + ^funF String(funF body) + } ^funE R|/funF|() } diff --git a/compiler/fir/analysis-tests/testData/resolve/headerMode/enumClassDeclaration.fir.txt b/compiler/fir/analysis-tests/testData/resolve/headerMode/enumClassDeclaration.fir.txt index bd9e7fca1df69..14808e22c656b 100644 --- a/compiler/fir/analysis-tests/testData/resolve/headerMode/enumClassDeclaration.fir.txt +++ b/compiler/fir/analysis-tests/testData/resolve/headerMode/enumClassDeclaration.fir.txt @@ -24,7 +24,9 @@ FILE: enumClassDeclaration.kt super() } - public open override fun getString(): R|kotlin/String| + public open override fun getString(): R|kotlin/String| { + ^getString String(north) + } } @@ -33,7 +35,9 @@ FILE: enumClassDeclaration.kt super() } - public open override fun getString(): R|kotlin/String| + public open override fun getString(): R|kotlin/String| { + ^getString String(south) + } } diff --git a/compiler/fir/analysis-tests/testData/resolve/headerMode/functionDeclaration.fir.txt b/compiler/fir/analysis-tests/testData/resolve/headerMode/functionDeclaration.fir.txt index 374a0e3a14c8d..87a70f6f4f335 100644 --- a/compiler/fir/analysis-tests/testData/resolve/headerMode/functionDeclaration.fir.txt +++ b/compiler/fir/analysis-tests/testData/resolve/headerMode/functionDeclaration.fir.txt @@ -12,7 +12,9 @@ FILE: functionDeclaration.kt private final fun funC(): R|kotlin/String| public final fun funD(): R|kotlin/Int| public final inline fun funE(): R|kotlin/String| { - local final fun funF(): R|kotlin/String| + local final fun funF(): R|kotlin/String| { + ^funF String(funF body) + } ^funE R|/funF|() } @@ -22,7 +24,9 @@ FILE: functionDeclaration.kt super() } - public final fun funH(): R|kotlin/String| + public final fun funH(): R|kotlin/String| { + ^funH String(funH body) + } } diff --git a/compiler/fir/analysis-tests/testData/resolve/headerMode/objectDeclaration.fir.txt b/compiler/fir/analysis-tests/testData/resolve/headerMode/objectDeclaration.fir.txt index 05714b225b1bc..d5bb11b458b18 100644 --- a/compiler/fir/analysis-tests/testData/resolve/headerMode/objectDeclaration.fir.txt +++ b/compiler/fir/analysis-tests/testData/resolve/headerMode/objectDeclaration.fir.txt @@ -22,7 +22,9 @@ FILE: objectDeclaration.kt public final fun funD(): R|kotlin/Int| public final inline fun funE(): R|kotlin/String| { - local final fun funF(): R|kotlin/String| + local final fun funF(): R|kotlin/String| { + ^funF String(funF body) + } ^funE R|/funF|() } diff --git a/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/LightTreeRawFirDeclarationBuilder.kt b/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/LightTreeRawFirDeclarationBuilder.kt index 5e8d542232816..d0e01aa682df8 100644 --- a/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/LightTreeRawFirDeclarationBuilder.kt +++ b/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/LightTreeRawFirDeclarationBuilder.kt @@ -2063,7 +2063,8 @@ class LightTreeRawFirDeclarationBuilder( } val allowLegacyContractDescription = outerContractDescription == null - val bodyWithContractDescription = withForcedLocalContext { + val forceKeepingTheBodyInHeaderMode = context.forceKeepingTheBodyInHeaderMode || functionBuilder.status.isInline + val bodyWithContractDescription = withForcedLocalContext(forceKeepingTheBodyInHeaderMode) { convertFunctionBody(block, expression, allowLegacyContractDescription) } this.body = bodyWithContractDescription.first diff --git a/compiler/fir/raw-fir/raw-fir.common/src/org/jetbrains/kotlin/fir/builder/AbstractRawFirBuilder.kt b/compiler/fir/raw-fir/raw-fir.common/src/org/jetbrains/kotlin/fir/builder/AbstractRawFirBuilder.kt index d2f5f3acff73c..7cb26f0f4a0a7 100644 --- a/compiler/fir/raw-fir/raw-fir.common/src/org/jetbrains/kotlin/fir/builder/AbstractRawFirBuilder.kt +++ b/compiler/fir/raw-fir/raw-fir.common/src/org/jetbrains/kotlin/fir/builder/AbstractRawFirBuilder.kt @@ -126,7 +126,9 @@ abstract class AbstractRawFirBuilder(val baseSession: FirSession, val c } } - inline fun withForcedLocalContext(block: () -> R): R { + inline fun withForcedLocalContext(forceKeepingTheBodyInHeaderMode: Boolean = false, block: () -> R): R { + val oldForceKeepingTheBodyInHeaderMode = context.forceKeepingTheBodyInHeaderMode + context.forceKeepingTheBodyInHeaderMode = oldForceKeepingTheBodyInHeaderMode || forceKeepingTheBodyInHeaderMode val oldForcedLocalContext = context.inLocalContext context.inLocalContext = true val oldClassNameBeforeLocalContext = context.classNameBeforeLocalContext @@ -141,6 +143,7 @@ abstract class AbstractRawFirBuilder(val baseSession: FirSession, val c context.classNameBeforeLocalContext = oldClassNameBeforeLocalContext context.inLocalContext = oldForcedLocalContext context.className = oldClassName + context.forceKeepingTheBodyInHeaderMode = oldForceKeepingTheBodyInHeaderMode } } diff --git a/compiler/fir/raw-fir/raw-fir.common/src/org/jetbrains/kotlin/fir/builder/Context.kt b/compiler/fir/raw-fir/raw-fir.common/src/org/jetbrains/kotlin/fir/builder/Context.kt index 71370b38ed8a0..407a383a182a3 100644 --- a/compiler/fir/raw-fir/raw-fir.common/src/org/jetbrains/kotlin/fir/builder/Context.kt +++ b/compiler/fir/raw-fir/raw-fir.common/src/org/jetbrains/kotlin/fir/builder/Context.kt @@ -59,6 +59,8 @@ class Context { val dispatchReceiverTypesStack: MutableList = mutableListOf() var containerIsExpect: Boolean = false + var forceKeepingTheBodyInHeaderMode: Boolean = false + var containingScriptSymbol: FirScriptSymbol? = null var containingReplSymbol: FirReplSnippetSymbol? = null diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirDeclarationsResolveTransformer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirDeclarationsResolveTransformer.kt index 9f467bf9437fb..ae43ac2edb14a 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirDeclarationsResolveTransformer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirDeclarationsResolveTransformer.kt @@ -1039,7 +1039,7 @@ open class FirDeclarationsResolveTransformer( } result.transformReturnTypeRef(transformer, ResolutionMode.UpdateImplicitTypeRef(returnTypeRef)) } - if (session.languageVersionSettings.getFlag(AnalysisFlags.headerMode) && !function.isInline) { + if (session.languageVersionSettings.getFlag(AnalysisFlags.headerMode) && !function.isInline && !function.isLocal) { // Header mode: once the return type for non-inline function is known, the body can be removed. result.replaceBody(null) } From 010ebc68222ea2f5511a7652507df94c279e45b7 Mon Sep 17 00:00:00 2001 From: aviroop Date: Thu, 30 Oct 2025 17:57:09 +0000 Subject: [PATCH 3/7] Elide function / method bodies in LT2FIR during header compilation. ^KT-78422 --- .../headerMode/constructorDeclaration.fir.txt | 4 -- .../headerMode/enumClassDeclaration.fir.txt | 1 - .../LightTreeRawFirDeclarationBuilder.kt | 54 +++++++++++++------ .../FirStatusResolveTransformer.kt | 12 +++++ .../fir/session/FirSessionFactoryHelper.kt | 2 +- 5 files changed, 51 insertions(+), 22 deletions(-) diff --git a/compiler/fir/analysis-tests/testData/resolve/headerMode/constructorDeclaration.fir.txt b/compiler/fir/analysis-tests/testData/resolve/headerMode/constructorDeclaration.fir.txt index bf834d5d96643..2af9dd0e03743 100644 --- a/compiler/fir/analysis-tests/testData/resolve/headerMode/constructorDeclaration.fir.txt +++ b/compiler/fir/analysis-tests/testData/resolve/headerMode/constructorDeclaration.fir.txt @@ -12,12 +12,10 @@ FILE: constructorDeclaration.kt public constructor(e: R|kotlin/String|): R|A| { this(R|/e|, Int(0)) - lval message: R|kotlin/String| = String(Secondary constructor body) } public constructor(): R|A| { this(String()) - lval message: R|kotlin/String| = String(Secondary constructor body with delegated constructor call) } } @@ -34,12 +32,10 @@ FILE: constructorDeclaration.kt public constructor(e: R|kotlin/String|): R|B| { this(R|/e|, Int(0)) - lval message: R|kotlin/String| = String(Secondary constructor body) } public constructor(): R|B| { this(String()) - lval message: R|kotlin/String| = String(Secondary constructor body with delegated constructor call) } } diff --git a/compiler/fir/analysis-tests/testData/resolve/headerMode/enumClassDeclaration.fir.txt b/compiler/fir/analysis-tests/testData/resolve/headerMode/enumClassDeclaration.fir.txt index 14808e22c656b..5cf6febb0b612 100644 --- a/compiler/fir/analysis-tests/testData/resolve/headerMode/enumClassDeclaration.fir.txt +++ b/compiler/fir/analysis-tests/testData/resolve/headerMode/enumClassDeclaration.fir.txt @@ -36,7 +36,6 @@ FILE: enumClassDeclaration.kt } public open override fun getString(): R|kotlin/String| { - ^getString String(south) } } diff --git a/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/LightTreeRawFirDeclarationBuilder.kt b/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/LightTreeRawFirDeclarationBuilder.kt index d0e01aa682df8..4d2c5c54aafed 100644 --- a/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/LightTreeRawFirDeclarationBuilder.kt +++ b/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/LightTreeRawFirDeclarationBuilder.kt @@ -56,6 +56,7 @@ import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes import org.jetbrains.kotlin.util.getChildren import org.jetbrains.kotlin.utils.addToStdlib.runIf import org.jetbrains.kotlin.utils.addToStdlib.shouldNotBeCalled +import org.jetbrains.kotlin.config.AnalysisFlags class LightTreeRawFirDeclarationBuilder( session: FirSession, @@ -65,6 +66,7 @@ class LightTreeRawFirDeclarationBuilder( ) : AbstractLightTreeRawFirBuilder(session, tree, context) { private val expressionConverter = LightTreeRawFirExpressionBuilder(session, tree, this, context) + private val headerMode = session.languageVersionSettings.getFlag(AnalysisFlags.headerMode) /** * [org.jetbrains.kotlin.parsing.KotlinParsing.parseFile] @@ -133,21 +135,33 @@ class LightTreeRawFirDeclarationBuilder( /** * @see org.jetbrains.kotlin.parsing.KotlinParsing.parseBlockExpression */ - fun convertBlockExpression(block: LighterASTNode): FirBlock { - return convertBlockExpressionWithoutBuilding(block).build() + fun convertBlockExpression( + block: LighterASTNode, + convertOnlyFirstStatement: Boolean = false, + ): FirBlock { + return convertBlockExpressionWithoutBuilding(block, convertOnlyFirstStatement = convertOnlyFirstStatement).build() } - fun convertBlockExpressionWithoutBuilding(block: LighterASTNode, kind: KtFakeSourceElementKind? = null): FirBlockBuilder { + /** + * @param convertOnlyFirstStatement Convert only the first statement of the block, which can be a contract, for header generation. + */ + fun convertBlockExpressionWithoutBuilding( + block: LighterASTNode, + kind: KtFakeSourceElementKind? = null, + convertOnlyFirstStatement: Boolean = false + ): FirBlockBuilder { val firStatements = block.forEachChildrenReturnList { node, container -> - when (node.tokenType) { - CLASS, OBJECT_DECLARATION -> container += convertClass(node) as FirStatement - FUN -> container += convertFunctionDeclaration(node) - KtNodeTypes.PROPERTY -> container += convertPropertyDeclaration(node) as FirStatement - DESTRUCTURING_DECLARATION -> container += - convertDestructingDeclaration(node).toFirDestructingDeclaration(this, baseModuleData) - TYPEALIAS -> container += convertTypeAlias(node) as FirStatement - CLASS_INITIALIZER -> shouldNotBeCalled("CLASS_INITIALIZER expected to be processed during class body conversion") - else -> if (node.isExpression()) container += expressionConverter.getAsFirStatement(node) + if (!convertOnlyFirstStatement || container.isEmpty()) { + when (node.tokenType) { + CLASS, OBJECT_DECLARATION -> container += convertClass(node) as FirStatement + FUN -> container += convertFunctionDeclaration(node) + KtNodeTypes.PROPERTY -> container += convertPropertyDeclaration(node) as FirStatement + DESTRUCTURING_DECLARATION -> container += + convertDestructingDeclaration(node).toFirDestructingDeclaration(this, baseModuleData) + TYPEALIAS -> container += convertTypeAlias(node) as FirStatement + CLASS_INITIALIZER -> shouldNotBeCalled("CLASS_INITIALIZER expected to be processed during class body conversion") + else -> if (node.isExpression()) container += expressionConverter.getAsFirStatement(node) + } } } return FirBlockBuilder().apply { @@ -2104,9 +2118,10 @@ class LightTreeRawFirDeclarationBuilder( expression: LighterASTNode?, allowLegacyContractDescription: Boolean ): Pair { + val generateHeader = headerMode && !context.forceKeepingTheBodyInHeaderMode return when { blockNode != null -> { - val block = convertBlock(blockNode) + val block = convertBlock(blockNode, convertOnlyFirstStatement = generateHeader) val contractDescription = runIf(allowLegacyContractDescription) { val blockSource = block.source val diagnostic = when { @@ -2116,7 +2131,11 @@ class LightTreeRawFirDeclarationBuilder( } processLegacyContractDescription(block, diagnostic) } - block to contractDescription + if (generateHeader) { + return buildEmptyExpressionBlock() to contractDescription + } else { + block to contractDescription + } } expression != null -> FirSingleExpressionBlock( expressionConverter.getAsFirExpression(expression, "Function has no body (but should)").toReturn() @@ -2138,7 +2157,10 @@ class LightTreeRawFirDeclarationBuilder( /** * @see org.jetbrains.kotlin.parsing.KotlinParsing.parseBlock */ - fun convertBlock(block: LighterASTNode?): FirBlock { + fun convertBlock( + block: LighterASTNode?, + convertOnlyFirstStatement: Boolean = false, + ): FirBlock { if (block == null) return buildEmptyExpressionBlock() if (block.tokenType != BLOCK) { return FirSingleExpressionBlock( @@ -2146,7 +2168,7 @@ class LightTreeRawFirDeclarationBuilder( ) } - return convertBlockExpression(block) + return convertBlockExpression(block, convertOnlyFirstStatement) } /** diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/FirStatusResolveTransformer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/FirStatusResolveTransformer.kt index de59e3320c3e6..6950f40a95db6 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/FirStatusResolveTransformer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/FirStatusResolveTransformer.kt @@ -5,11 +5,14 @@ package org.jetbrains.kotlin.fir.resolve.transformers +import org.jetbrains.kotlin.config.AnalysisFlags import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.fir.* import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.utils.componentFunctionSymbol +import org.jetbrains.kotlin.fir.declarations.utils.isInline import org.jetbrains.kotlin.fir.declarations.utils.isInlineOrValue +import org.jetbrains.kotlin.fir.declarations.utils.isNonLocal import org.jetbrains.kotlin.fir.declarations.utils.visibility import org.jetbrains.kotlin.fir.expressions.FirBlock import org.jetbrains.kotlin.fir.expressions.FirStatement @@ -18,6 +21,7 @@ import org.jetbrains.kotlin.fir.resolve.toSymbol import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.LocalClassesNavigationInfo import org.jetbrains.kotlin.fir.symbols.impl.* import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase +import org.jetbrains.kotlin.fir.types.FirImplicitTypeRef import org.jetbrains.kotlin.fir.types.FirTypeRef import org.jetbrains.kotlin.fir.types.coneType import org.jetbrains.kotlin.fir.utils.exceptions.withFirEntry @@ -426,6 +430,14 @@ abstract class AbstractFirStatusResolveTransformer( isLocal = false, overriddenFunctions.map { it.status as FirResolvedDeclarationStatus }, ) + // Once the modality is determined, we can remove the body. + if (session.languageVersionSettings.getFlag(AnalysisFlags.headerMode) && + namedFunction.isNonLocal && + !namedFunction.isInline && + namedFunction.returnTypeRef !is FirImplicitTypeRef + ) { + namedFunction.replaceBody(null) + } namedFunction.transformStatus(this, resolvedStatus) transformDeclaration(namedFunction, data) as FirStatement diff --git a/compiler/tests-compiler-utils/testFixtures/org/jetbrains/kotlin/fir/session/FirSessionFactoryHelper.kt b/compiler/tests-compiler-utils/testFixtures/org/jetbrains/kotlin/fir/session/FirSessionFactoryHelper.kt index 88c5067141bbc..24f6536ba2b36 100644 --- a/compiler/tests-compiler-utils/testFixtures/org/jetbrains/kotlin/fir/session/FirSessionFactoryHelper.kt +++ b/compiler/tests-compiler-utils/testFixtures/org/jetbrains/kotlin/fir/session/FirSessionFactoryHelper.kt @@ -108,7 +108,7 @@ object FirSessionFactoryHelper { override fun isPreRelease(): Boolean = stub() - override fun getFlag(flag: AnalysisFlag): T = stub() + override fun getFlag(flag: AnalysisFlag): T = flag.defaultValue override val apiVersion: ApiVersion get() = stub() From a327aff34975c93fb86eb6a6ada4ac7a7c657735 Mon Sep 17 00:00:00 2001 From: aviroop Date: Fri, 31 Oct 2025 18:39:18 +0000 Subject: [PATCH 4/7] Disable Psi-based tests in header mode ^KT-78422 --- .../configuration/BaseCodegenConfiguration.kt | 3 +++ .../BaseDiagnosticConfiguration.kt | 2 ++ .../directives/FirDiagnosticsDirectives.kt | 4 +++ .../fir/FirSpecificParserSuppressor.kt | 27 +++++++++++++++++++ 4 files changed, 36 insertions(+) create mode 100644 compiler/tests-common-new/testFixtures/org/jetbrains/kotlin/test/services/fir/FirSpecificParserSuppressor.kt diff --git a/compiler/tests-common-new/testFixtures/org/jetbrains/kotlin/test/configuration/BaseCodegenConfiguration.kt b/compiler/tests-common-new/testFixtures/org/jetbrains/kotlin/test/configuration/BaseCodegenConfiguration.kt index a6ef42e697963..a770bfacdff53 100644 --- a/compiler/tests-common-new/testFixtures/org/jetbrains/kotlin/test/configuration/BaseCodegenConfiguration.kt +++ b/compiler/tests-common-new/testFixtures/org/jetbrains/kotlin/test/configuration/BaseCodegenConfiguration.kt @@ -32,6 +32,7 @@ import org.jetbrains.kotlin.test.frontend.fir.FirOutputArtifact import org.jetbrains.kotlin.test.model.* import org.jetbrains.kotlin.test.services.AdditionalSourceProvider import org.jetbrains.kotlin.test.services.configuration.* +import org.jetbrains.kotlin.test.services.fir.FirSpecificParserSuppressor import org.jetbrains.kotlin.test.services.sourceProviders.AdditionalDiagnosticsSourceFilesProvider import org.jetbrains.kotlin.test.services.sourceProviders.CoroutineHelpersSourceFilesProvider import org.jetbrains.kotlin.utils.bind @@ -92,6 +93,8 @@ fun TestConfigurationBuilder.commonServicesConfigurationForCodegenAndDebugTest(t ::AdditionalDiagnosticsSourceFilesProvider, ::CoroutineHelpersSourceFilesProvider, ) + + useMetaTestConfigurators(::FirSpecificParserSuppressor) } /** diff --git a/compiler/tests-common-new/testFixtures/org/jetbrains/kotlin/test/configuration/BaseDiagnosticConfiguration.kt b/compiler/tests-common-new/testFixtures/org/jetbrains/kotlin/test/configuration/BaseDiagnosticConfiguration.kt index 878b3c76c80b7..906d9bb2a1744 100644 --- a/compiler/tests-common-new/testFixtures/org/jetbrains/kotlin/test/configuration/BaseDiagnosticConfiguration.kt +++ b/compiler/tests-common-new/testFixtures/org/jetbrains/kotlin/test/configuration/BaseDiagnosticConfiguration.kt @@ -21,6 +21,7 @@ import org.jetbrains.kotlin.test.builders.irHandlersStep import org.jetbrains.kotlin.test.cli.CliDirectives.CHECK_COMPILER_OUTPUT import org.jetbrains.kotlin.test.directives.ConfigurationDirectives.WITH_STDLIB import org.jetbrains.kotlin.test.directives.DiagnosticsDirectives.DIAGNOSTICS +import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives.DISABLE_WITH_PARSER import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives.DUMP_VFIR import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives.TEST_ALONGSIDE_K1_TESTDATA import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives.USE_LATEST_LANGUAGE_VERSION @@ -298,6 +299,7 @@ fun TestConfigurationBuilder.configureCommonDiagnosticTestPaths( forTestsMatching("compiler/fir/analysis-tests/testData/resolve/headerMode/*") { defaultDirectives { +HEADER_MODE + DISABLE_WITH_PARSER with FirParser.Psi } } } diff --git a/compiler/tests-common-new/testFixtures/org/jetbrains/kotlin/test/directives/FirDiagnosticsDirectives.kt b/compiler/tests-common-new/testFixtures/org/jetbrains/kotlin/test/directives/FirDiagnosticsDirectives.kt index d37c3db79871e..05a65fdc09d23 100644 --- a/compiler/tests-common-new/testFixtures/org/jetbrains/kotlin/test/directives/FirDiagnosticsDirectives.kt +++ b/compiler/tests-common-new/testFixtures/org/jetbrains/kotlin/test/directives/FirDiagnosticsDirectives.kt @@ -157,6 +157,10 @@ object FirDiagnosticsDirectives : SimpleDirectivesContainer() { val DISABLE_GENERATED_FIR_TAGS by directive( description = "Disables generating and checking for GENERATED_FIR_TAGS" ) + + val DISABLE_WITH_PARSER by enumDirective( + description = "Disables the test if it's analyzed with specified parser" + ) } object DumpCfgOption { diff --git a/compiler/tests-common-new/testFixtures/org/jetbrains/kotlin/test/services/fir/FirSpecificParserSuppressor.kt b/compiler/tests-common-new/testFixtures/org/jetbrains/kotlin/test/services/fir/FirSpecificParserSuppressor.kt new file mode 100644 index 0000000000000..a776f7ebec635 --- /dev/null +++ b/compiler/tests-common-new/testFixtures/org/jetbrains/kotlin/test/services/fir/FirSpecificParserSuppressor.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2010-2025 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.test.services.fir + +import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives +import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives.DISABLE_WITH_PARSER +import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives.FIR_PARSER +import org.jetbrains.kotlin.test.directives.model.DirectivesContainer +import org.jetbrains.kotlin.test.directives.model.singleOrZeroValue +import org.jetbrains.kotlin.test.services.MetaTestConfigurator +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.moduleStructure + +class FirSpecificParserSuppressor(testServices: TestServices) : MetaTestConfigurator(testServices) { + override val directiveContainers: List + get() = listOf(FirDiagnosticsDirectives) + + override fun shouldSkipTest(): Boolean { + val directives = testServices.moduleStructure.allDirectives + val suppressedParser = directives.singleOrZeroValue(DISABLE_WITH_PARSER) ?: return false + val currentParser = directives.singleOrZeroValue(FIR_PARSER) ?: return false + return currentParser == suppressedParser + } +} From 8361fa8ac874b2580f3cd9e953f0a80f4d6f10b3 Mon Sep 17 00:00:00 2001 From: aviroop Date: Tue, 4 Nov 2025 17:16:32 +0000 Subject: [PATCH 5/7] Add data class tests for header compilation. ^KT-78422 --- .../headerMode/dataClassDeclaration.fir.txt | 19 +++++++++++++++++++ .../headerMode/dataClassDeclaration.kt | 5 +++++ 2 files changed, 24 insertions(+) create mode 100644 compiler/fir/analysis-tests/testData/resolve/headerMode/dataClassDeclaration.fir.txt create mode 100644 compiler/fir/analysis-tests/testData/resolve/headerMode/dataClassDeclaration.kt diff --git a/compiler/fir/analysis-tests/testData/resolve/headerMode/dataClassDeclaration.fir.txt b/compiler/fir/analysis-tests/testData/resolve/headerMode/dataClassDeclaration.fir.txt new file mode 100644 index 0000000000000..5610c90334714 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/headerMode/dataClassDeclaration.fir.txt @@ -0,0 +1,19 @@ +FILE: dataClassDeclaration.kt + public final data class User : R|kotlin/Any| { + public constructor(name: R|kotlin/String|, age: R|kotlin/Int|): R|User| { + super() + } + + public final val name: R|kotlin/String| = R|/name| + public get(): R|kotlin/String| + + public final val age: R|kotlin/Int| = R|/age| + public get(): R|kotlin/Int| + + public final operator fun component1(): R|kotlin/String| + + public final operator fun component2(): R|kotlin/Int| + + public final fun copy(name: R|kotlin/String| = this@R|/User|.R|/User.name|, age: R|kotlin/Int| = this@R|/User|.R|/User.age|): R|User| + + } diff --git a/compiler/fir/analysis-tests/testData/resolve/headerMode/dataClassDeclaration.kt b/compiler/fir/analysis-tests/testData/resolve/headerMode/dataClassDeclaration.kt new file mode 100644 index 0000000000000..569f76922e90a --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/headerMode/dataClassDeclaration.kt @@ -0,0 +1,5 @@ +// RUN_PIPELINE_TILL: BACKEND +// FIR_DUMP +data class User(val name: String, val age: Int) + +/* GENERATED_FIR_TAGS: classDeclaration, data, primaryConstructor, propertyDeclaration */ From 87278c516c5e6f99c3cca801038d9398e2c1ea3e Mon Sep 17 00:00:00 2001 From: aviroop Date: Tue, 4 Nov 2025 17:25:32 +0000 Subject: [PATCH 6/7] Remove changes to resolve transformer for header compilation since it's a no-op. ^KT-78422 --- .../transformers/FirStatusResolveTransformer.kt | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/FirStatusResolveTransformer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/FirStatusResolveTransformer.kt index 6950f40a95db6..de59e3320c3e6 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/FirStatusResolveTransformer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/FirStatusResolveTransformer.kt @@ -5,14 +5,11 @@ package org.jetbrains.kotlin.fir.resolve.transformers -import org.jetbrains.kotlin.config.AnalysisFlags import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.fir.* import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.utils.componentFunctionSymbol -import org.jetbrains.kotlin.fir.declarations.utils.isInline import org.jetbrains.kotlin.fir.declarations.utils.isInlineOrValue -import org.jetbrains.kotlin.fir.declarations.utils.isNonLocal import org.jetbrains.kotlin.fir.declarations.utils.visibility import org.jetbrains.kotlin.fir.expressions.FirBlock import org.jetbrains.kotlin.fir.expressions.FirStatement @@ -21,7 +18,6 @@ import org.jetbrains.kotlin.fir.resolve.toSymbol import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.LocalClassesNavigationInfo import org.jetbrains.kotlin.fir.symbols.impl.* import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase -import org.jetbrains.kotlin.fir.types.FirImplicitTypeRef import org.jetbrains.kotlin.fir.types.FirTypeRef import org.jetbrains.kotlin.fir.types.coneType import org.jetbrains.kotlin.fir.utils.exceptions.withFirEntry @@ -430,14 +426,6 @@ abstract class AbstractFirStatusResolveTransformer( isLocal = false, overriddenFunctions.map { it.status as FirResolvedDeclarationStatus }, ) - // Once the modality is determined, we can remove the body. - if (session.languageVersionSettings.getFlag(AnalysisFlags.headerMode) && - namedFunction.isNonLocal && - !namedFunction.isInline && - namedFunction.returnTypeRef !is FirImplicitTypeRef - ) { - namedFunction.replaceBody(null) - } namedFunction.transformStatus(this, resolvedStatus) transformDeclaration(namedFunction, data) as FirStatement From f9a7533c35ecf5d4e7bfd662ca0306561a148051 Mon Sep 17 00:00:00 2001 From: aviroop Date: Tue, 4 Nov 2025 20:26:51 +0000 Subject: [PATCH 7/7] Add property declaration tests for header compilation. ^KT-78422 --- .../resolve/headerMode/propertyDeclaration.fir.txt | 8 ++++++++ .../resolve/headerMode/propertyDeclaration.kt | 11 +++++++++++ 2 files changed, 19 insertions(+) create mode 100644 compiler/fir/analysis-tests/testData/resolve/headerMode/propertyDeclaration.fir.txt create mode 100644 compiler/fir/analysis-tests/testData/resolve/headerMode/propertyDeclaration.kt diff --git a/compiler/fir/analysis-tests/testData/resolve/headerMode/propertyDeclaration.fir.txt b/compiler/fir/analysis-tests/testData/resolve/headerMode/propertyDeclaration.fir.txt new file mode 100644 index 0000000000000..f4bf8a6276eb2 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/headerMode/propertyDeclaration.fir.txt @@ -0,0 +1,8 @@ +FILE: propertyDeclaration.kt + public final val a: R|kotlin/String| = String(A) + public get(): R|kotlin/String| + public final val b: R|kotlin/String| = String(B) + public get(): R|kotlin/String| + public final fun getC(): R|kotlin/String| + public final val c: + public get(): diff --git a/compiler/fir/analysis-tests/testData/resolve/headerMode/propertyDeclaration.kt b/compiler/fir/analysis-tests/testData/resolve/headerMode/propertyDeclaration.kt new file mode 100644 index 0000000000000..389733f9482f6 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/headerMode/propertyDeclaration.kt @@ -0,0 +1,11 @@ +// RUN_PIPELINE_TILL: BACKEND +// FIR_DUMP +// Public property with explicit type +//val a: String = "A" +// Public property with implicit type +//val b = "B" +// Property with overriden getter and implicit type. +fun getC() = "C" +val c get() = getC() + +/* GENERATED_FIR_TAGS: propertyDeclaration, stringLiteral */