From 024e94a4794993c05d7f3bcb69660f2d1bf23471 Mon Sep 17 00:00:00 2001 From: Simon Ogorodnik Date: Fri, 15 Sep 2023 17:34:01 +0200 Subject: [PATCH] [K2] Document KMP implementation in the K2 Compiler Add comprehensive documentation explaining the implementation and functions of the Kotlin multiplatform (KMP) support in the K2 Compiler. --- .../kotlin/cli/metadata/K2MetadataCompiler.kt | 8 + .../kotlin/fir/symbols/ConeLookupTags.kt | 13 + .../fir/backend/Fir2IrCommonMemberStorage.kt | 7 + .../fir/declarations/declarationUtils.kt | 21 + .../kotlin/fir/resolve/LookupTagUtils.kt | 15 + .../kotlin/fir/resolve/TypeExpansionUtils.kt | 20 + .../mpp/FirExpectActualMatcherTransformer.kt | 8 + .../declarations/ExpectActualAttributes.kt | 54 ++ .../ConeClassifierLookupTagWithFixedSymbol.kt | 3 + .../backend/common/actualizer/IrActualizer.kt | 7 + ...bstractExpectActualCompatibilityChecker.kt | 6 + docs/fir/k2_kmp.md | 499 ++++++++++++++++++ 12 files changed, 661 insertions(+) create mode 100644 docs/fir/k2_kmp.md diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/metadata/K2MetadataCompiler.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/metadata/K2MetadataCompiler.kt index 7314f49394e..47b63ec2464 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/metadata/K2MetadataCompiler.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/metadata/K2MetadataCompiler.kt @@ -37,6 +37,14 @@ import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil import org.jetbrains.kotlin.utils.KotlinPaths import java.io.File +/** + * This class is the entry-point for compiling Kotlin code into a metadata KLib. + * + * **Note: `2` in the name stands for Kotlin `TO` metadata compiler. + * This entry-point used by both K1 and K2.** + * + * Please see `/docs/fir/k2_kmp.md` for more info on the K2/FIR implementation. + */ class K2MetadataCompiler : CLICompiler() { override val defaultPerformanceManager: CommonCompilerPerformanceManager = K2MetadataCompilerPerformanceManager() diff --git a/compiler/fir/cones/src/org/jetbrains/kotlin/fir/symbols/ConeLookupTags.kt b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/symbols/ConeLookupTags.kt index 10defd9d344..7ceaff78b57 100644 --- a/compiler/fir/cones/src/org/jetbrains/kotlin/fir/symbols/ConeLookupTags.kt +++ b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/symbols/ConeLookupTags.kt @@ -9,6 +9,16 @@ import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.types.model.TypeConstructorMarker +/** + * The main purpose of the lookup tag is to provide a reference to concrete classifier that + * allows obtaining FirClassifierSymbol within a given use-site session. + * + * During the deserialization we use lookup tags to avoid loading the entire class-type hierarchy. + * + * Use-site lookup of classifiers that are referenced by lookup tags allows type refinement that is needed for the expect/actual support. + * + * See `/docs/fir/k2_kmp.md` + */ abstract class ConeClassifierLookupTag : TypeConstructorMarker { abstract val name: Name @@ -17,6 +27,9 @@ abstract class ConeClassifierLookupTag : TypeConstructorMarker { } } +/** + * @see ConeClassifierLookupTag + */ abstract class ConeClassLikeLookupTag : ConeClassifierLookupTag() { abstract val classId: ClassId diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrCommonMemberStorage.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrCommonMemberStorage.kt index 8a9dfac0a0f..f61774feeb8 100644 --- a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrCommonMemberStorage.kt +++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrCommonMemberStorage.kt @@ -14,6 +14,13 @@ import org.jetbrains.kotlin.ir.util.IdSignatureComposer import org.jetbrains.kotlin.ir.util.SymbolTable import java.util.concurrent.ConcurrentHashMap +/** + * This storage contains the shared state of FIR2IR. + * + * State is shared between the conversions of different modules in the same platform compilation. + * + * See `/docs/fir/k2_kmp.md` + */ class Fir2IrCommonMemberStorage( signatureComposer: IdSignatureComposer, firMangler: FirMangler diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/declarations/declarationUtils.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/declarations/declarationUtils.kt index eed6ab8009e..cc1a595737e 100644 --- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/declarations/declarationUtils.kt +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/declarations/declarationUtils.kt @@ -93,6 +93,27 @@ inline val FirBasedSymbol<*>.isJavaOrEnhancement: Boolean private fun FirFunction.containsDefaultValue(index: Int): Boolean = valueParameters[index].defaultValue != null +/** + * Checks, if the value parameter has a default value w.r.t expect/actuals. + * + * Requires [FirResolvePhase.EXPECT_ACTUAL_MATCHING] + * + * In expect/actual declarations, the presence of default values can be determined by the expect declaration. + * + * ```kotlin + * // MODULE: common + * expect fun foo(bar: Int = 42) + * + * // MODULE: platform()(common) + * actual fun foo(bar: Int) { ... } + * ``` + * + * See `/docs/fir/k2_kmp.md` + * + * @param index Index of the value parameter to check + * @return `true` if a parameter has defined default value, or if there is a default value defined on the expect declaration + * for this actual. + */ fun FirFunction.itOrExpectHasDefaultParameterValue(index: Int): Boolean = containsDefaultValue(index) || symbol.getSingleExpectForActualOrNull()?.fir?.containsDefaultValue(index) == true diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/LookupTagUtils.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/LookupTagUtils.kt index 8941d2d0e27..4854d0952ed 100644 --- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/LookupTagUtils.kt +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/LookupTagUtils.kt @@ -29,6 +29,15 @@ import org.jetbrains.kotlin.types.ConstantValueKind import org.jetbrains.kotlin.util.WeakPair import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment +/** + * Main operation on the [ConeClassifierLookupTag] + * + * Lookups the tag into its target within the given [useSiteSession] + * + * The second step of type refinement, see `/docs/fir/k2_kmp.md` + * + * @see ConeClassifierLookupTag + */ fun ConeClassifierLookupTag.toSymbol(useSiteSession: FirSession): FirClassifierSymbol<*>? = when (this) { is ConeClassLikeLookupTag -> toSymbol(useSiteSession) @@ -37,6 +46,9 @@ fun ConeClassifierLookupTag.toSymbol(useSiteSession: FirSession): FirClassifierS else -> null } +/** + * @see toSymbol + */ @OptIn(LookupTagInternals::class) fun ConeClassLikeLookupTag.toSymbol(useSiteSession: FirSession): FirClassLikeSymbol<*>? { if (this is ConeClassLookupTagWithFixedSymbol) { @@ -49,6 +61,9 @@ fun ConeClassLikeLookupTag.toSymbol(useSiteSession: FirSession): FirClassLikeSym } } +/** + * @see toSymbol + */ fun ConeClassLikeLookupTag.toFirRegularClassSymbol(session: FirSession): FirRegularClassSymbol? = toSymbol(session) as? FirRegularClassSymbol diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/TypeExpansionUtils.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/TypeExpansionUtils.kt index 336b3986661..50f35b69eee 100644 --- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/TypeExpansionUtils.kt +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/TypeExpansionUtils.kt @@ -19,6 +19,17 @@ import org.jetbrains.kotlin.util.WeakPair import org.jetbrains.kotlin.util.component1 import org.jetbrains.kotlin.util.component2 +/** + * Compute the recursive type-alias expansion in the given type. + * + * A type of an expect class, that is actualized by a typealias will be expanded to the expansion of the typealias, + * when supplied with the session of actual. + * + * See `/docs/fir/k2_kmp.md` + * + * @param useSiteSession Session to be used for classifier lookups, see [toSymbol] + * @return Type, that is expanded to the concrete class type w.r.t to the [useSiteSession] + */ fun ConeClassLikeType.fullyExpandedType( useSiteSession: FirSession, expandedConeType: (FirTypeAlias) -> ConeClassLikeType? = { alias -> @@ -40,6 +51,9 @@ fun ConeClassLikeType.fullyExpandedType( return fullyExpandedTypeNoCache(useSiteSession, expandedConeType) } +/** + * @see fullyExpandedType + */ fun ConeKotlinType.fullyExpandedType( useSiteSession: FirSession ): ConeKotlinType = when (this) { @@ -50,6 +64,9 @@ fun ConeKotlinType.fullyExpandedType( else -> this } +/** + * @see fullyExpandedType + */ fun ConeSimpleKotlinType.fullyExpandedType( useSiteSession: FirSession ): ConeSimpleKotlinType = when (this) { @@ -167,6 +184,9 @@ private fun mapTypeAliasArguments( return substitutor.substituteOrSelf(resultingType) } +/** + * @see fullyExpandedType + */ fun FirTypeAlias.fullyExpandedConeType(useSiteSession: FirSession): ConeClassLikeType? { return expandedConeType?.fullyExpandedType(useSiteSession) } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/mpp/FirExpectActualMatcherTransformer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/mpp/FirExpectActualMatcherTransformer.kt index b4ccfc0c057..5a57057cd79 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/mpp/FirExpectActualMatcherTransformer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/mpp/FirExpectActualMatcherTransformer.kt @@ -31,6 +31,14 @@ class FirExpectActualMatcherProcessor( } } +/** + * This transformer populates [expectForActual] mapping for actual declarations. + * Also, populates it [memberExpectForActual] mapping + * + * Should run before any kind of body resolution, since [expectForActual] is used there. + * + * See `/docs/fir/k2_kmp.md` + */ open class FirExpectActualMatcherTransformer( final override val session: FirSession, private val scopeSession: ScopeSession, diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/ExpectActualAttributes.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/ExpectActualAttributes.kt index 3c68239cf89..774114109ab 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/ExpectActualAttributes.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/ExpectActualAttributes.kt @@ -17,12 +17,27 @@ private object ExpectForActualAttributeKey : FirDeclarationDataKey() typealias ExpectForActualData = Map>, List>> +/** + * Actual declaration -> (many) expect declaration mapping. For top-level declarations. + * + * Populated by [FirResolvePhase.EXPECT_ACTUAL_MATCHING] phase for every top-level actual declaration. + * + * **Note: Mapping is computed from the declaration-site session of the actual declaration and not refined on the use-sites** + * + * See `/docs/fir/k2_kmp.md` + */ @SymbolInternals var FirDeclaration.expectForActual: ExpectForActualData? by FirDeclarationDataRegistry.data(ExpectForActualAttributeKey) +/** + * @see expectForActual + */ fun FirFunctionSymbol<*>.getSingleExpectForActualOrNull(): FirFunctionSymbol<*>? = (this as FirBasedSymbol<*>).getSingleExpectForActualOrNull() as? FirFunctionSymbol<*> +/** + * @see expectForActual + */ fun FirBasedSymbol<*>.getSingleExpectForActualOrNull(): FirBasedSymbol<*>? { val expectForActual = expectForActual ?: return null val compatibleOrWeakCompatible: List> = @@ -30,6 +45,9 @@ fun FirBasedSymbol<*>.getSingleExpectForActualOrNull(): FirBasedSymbol<*>? { return compatibleOrWeakCompatible.singleOrNull() } +/** + * @see expectForActual + */ val FirBasedSymbol<*>.expectForActual: ExpectForActualData? get() { lazyResolveToPhase(FirResolvePhase.EXPECT_ACTUAL_MATCHING) @@ -45,4 +63,40 @@ typealias MemberExpectForActualData = Map, /* expect class */ FirRegularClassSymbol>, Map, ExpectActualCompatibility<*>>> +/** + * Actual class + expect class + actual member declaration -> (many) expect member declaration mapping. + * + * This attribute allows finding complex actualizations through type-aliases. + * + * ```kotlin + * // MODULE: common + * expect class A { + * fun foo() // expect.1 + * } + * expect class B { + * fun foo() // expect.2 + * } + * + * // MODULE: platform()(common) + * + * actual typealias A = Impl + * actual typealias B = Impl + * + * // attribute: memberExpectForActual + * // value: { + * // (symbol: expect class A, symbol: impl.1) => { symbol: expect.1 => Compatible }, + * // (symbol: expect class B, symbol: impl.1) => { symbol: expect.2 => Compatible }, + * // } + * class Impl { + * fun foo() {} // impl.1 + * } + * ``` + * + * Populated by [FirResolvePhase.EXPECT_ACTUAL_MATCHING] phase for every top-level class declaration that is either actual class or + * expansion of actual type-alias. + * + * **Note: Mapping is computed from the declaration-site session of the actual declaration and not refined on the use-sites** + * + * See `/docs/fir/k2_kmp.md` + */ var FirRegularClass.memberExpectForActual: MemberExpectForActualData? by FirDeclarationDataRegistry.data(MemberExpectForActualAttributeKey) diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/symbols/ConeClassifierLookupTagWithFixedSymbol.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/symbols/ConeClassifierLookupTagWithFixedSymbol.kt index 90cef84f5e6..8bfb67ded26 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/symbols/ConeClassifierLookupTagWithFixedSymbol.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/symbols/ConeClassifierLookupTagWithFixedSymbol.kt @@ -7,6 +7,9 @@ package org.jetbrains.kotlin.fir.symbols import org.jetbrains.kotlin.fir.symbols.impl.FirClassifierSymbol +/** + * @see ConeClassifierLookupTag + */ abstract class ConeClassifierLookupTagWithFixedSymbol : ConeClassifierLookupTag() { abstract val symbol: FirClassifierSymbol<*> } \ No newline at end of file diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/actualizer/IrActualizer.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/actualizer/IrActualizer.kt index 71b913a7bee..15eeddf895e 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/actualizer/IrActualizer.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/actualizer/IrActualizer.kt @@ -19,6 +19,13 @@ import org.jetbrains.kotlin.ir.util.* data class IrActualizedResult(val actualizedExpectDeclarations: List) +/** + * IrActualizer is responsible for performing actualization. + * + * The main action is the replacement of references to expect declarations with corresponding actuals on the IR level. + * + * See `/docs/fir/k2_kmp.md` + */ object IrActualizer { fun actualize( mainFragment: IrModuleFragment, diff --git a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualCompatibilityChecker.kt b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualCompatibilityChecker.kt index 4b76a4b3530..7b53b3a4d9e 100644 --- a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualCompatibilityChecker.kt +++ b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualCompatibilityChecker.kt @@ -23,6 +23,12 @@ import org.jetbrains.kotlin.utils.keysToMap import org.jetbrains.kotlin.utils.zipIfSizesAreEqual import java.util.* +/** + * This object is responsible for matching and checking of + * expect-actual pairs + * + * See `/docs/fir/k2_kmp.md` for details + */ object AbstractExpectActualCompatibilityChecker { fun getClassifiersCompatibility( expectClassSymbol: RegularClassSymbolMarker, diff --git a/docs/fir/k2_kmp.md b/docs/fir/k2_kmp.md new file mode 100644 index 00000000000..8bd6b712a4d --- /dev/null +++ b/docs/fir/k2_kmp.md @@ -0,0 +1,499 @@ +# K2 KMP (Kotlin multiplatform) implementation documentation + +This document describes the implementation of the KMP support in the K2 Compiler along components. + +**Note**: The current compilation model is temporary, and should be changed to final in [KT-57327](https://youtrack.jetbrains.com/issue/KT-57327) + +## Document structure + +- [Glossary](#glossary) +- [Metadata Compilation](#metadata-compilation) +- [Platform Compilation](#platform-compilation) + - [FIR2IR](#fir2ir) + - [IRActualizer](#iractualizer) + - [Fake overrides](#fake-overrides) +- [Frontend support for expect/actual](#frontend-support-for-expectactual) + - [Type refinement](#type-refinement) + - [Default propagation](#default-propagation) + - [Actual -> expect binding construction](#fir-actual---expect-binding-construction) +- [Frontend checkers](#frontend-checkers) + +## Glossary + +### Source-set + +A bunch of source files along with its depends-on relations with other source-sets. + +See: https://kotlinlang.org/docs/multiplatform-discover-project.html#source-sets + +**Note:** Binary dependencies are provided on the [compilation](#compilation) level and not considered a part of the source-set definition +by the compiler. + +### Module + +A compiler module, entity with source-code and dependencies. + - One-to-one mapped to the corresponding FirSession. + - One-to-one mapped to the FirModuleData. + - Compiler representation of a [source-set](#source-set) + +In a [platform compilation](#platform-compilation), +[multiple modules are created from the input](#source-set-and-module-relation) [source-sets](#source-set). + +### Compilation + +A single invocation of the compiler entry-point + +### Metadata dependencies/KLibs + +- KLib that only contains frontend metadata, without IR. It can only be used to analyze dependent code. + +### HMPP + +Hierarchical multi-platform projects +- See https://kotlinlang.org/docs/multiplatform-hierarchy.html + +### Actualization + +The process of replacing references to expect declarations with corresponding actual declarations. +See [IRActualizer](#iractualizer) + +### actual->expect binding + +A relation between two declarations that form an expect-actual pair. +- For classes: defined as identical ClassId. +- For callables: defined as complex matching rule, which is similar to overloading. + +In general case, the relation is many-to-many. +However, in correct code it is either one-to-one, or one-to-many in case of type-aliases. + +See [FIR: actual -> expect binding construction](#fir-actual---expect-binding-construction) + +### Common source-set + +A [source-set] that may contain expect declarations. +As opposed to a platform [source-sets](#source-set) that must only contain actual or regular declarations. + +Common source-sets usually contain code that is shared across different targets, and by that is included +in multiple [platform compilations](#platform-compilation). + +However, one may define [HMPP](#hmpp) hierarchy with only one target while still having common source-sets. + +## Metadata Compilation + +- Aimed to check code platform-independent compilation. +- Each [common source set](#common-source-set) is compiled with its metadata binary dependencies. +- Artifacts aren't used for subsequent platform compilations but for the IDE and other metadata compilations. +- In this compilation, we analyze code as if it is fully isolated from the corresponding actualizations. + - Doing so will allow us to check that code can be compiled for multiple targets. + - Only the [common source-set](#common-source-set) and its [metadata binary dependencies](#metadata-dependenciesklibs) are taken into account. + - No access to the target-specific dependencies or source-sets. + - Source-sets from depends-on are provided as [metadata binary dependencies](#metadata-dependenciesklibs) in the form of unpacked klib + - See `-Xrefines-path` + +**Entry-point:** [`org.jetbrains.kotlin.cli.metadata.K2MetadataCompiler`](../../compiler/cli/src/org/jetbrains/kotlin/cli/metadata/K2MetadataCompiler.kt) + +Also, see: [`org.jetbrains.kotlin.cli.metadata.FirMetadataSerializer`](../../compiler/cli/src/org/jetbrains/kotlin/cli/metadata/FirMetadataSerializer.kt) + +**Inputs:** +- [Metadata KLibs for dependencies](#metadata-dependenciesklibs) +- [Metadata KLibs for dependsOn source-sets](#metadata-dependenciesklibs) +- Source files of a single [common source-set](#common-source-set), such as "commonMain" + +**Outputs:** +- [Metadata KLibs](#metadata-dependenciesklibs) + +In this mode, we analyze only common sources. +This compilation mode is very similar to a usual compilation pipeline with the exception that no IR is serialized to the output. + +Actual-expect [matching](#frontend-support-for-expectactual) and [checking](#frontend-checkers) are performed, +since in [HMPP](#hmpp) we can have actuals in metadata compilation inputs. + + +**Note:** The full set of actual declarations can't be determined at that point. +The only actual -> expect binding could be obtained and checked. + +**Note:** In this compilation mode [actual->expect binding](#actual-expect-binding) may point to a deserialized element, +since expect declarations from depends-on source-sets will be present as [metadata binary dependencies](#metadata-dependenciesklibs). + +## Platform Compilation + +- Invoked per each target. +- Input includes [source-sets](#source-set) from entire target depends-on graph. +- Outputs a platform artifact (jar file or klib). +- [Source-sets](#source-set) form a directed acyclic graph. + - The graph must be terminated by one leaf [source-set](#source-set) +- Compilation requires a platform classpath. See: [binary dependencies](#platform-compilation-binary-dependencies) + +Please consult with https://kotlinlang.org/docs/multiplatform-hierarchy.html for more info on source-set structure. + +This is the main part where the pipeline diverges from simple compilation. + +Ex: +```kotlin +// MODULE: common +expect fun a1(): String +expect fun a2(): String + +// MODULE: intermediate()()(common) +actual fun a1(): String = "" +expect fun a4(): String +expect fun a5(): String + +// MODULE: unrelated +expect fun a3(): String +expect fun a5(): String + +// MODULE: platform()()(intermediate, unrelated) +actual fun a2(): String = "" +actual fun a3(): String = "" +actual fun a4(): String = "" +actual fun a5(): String = "" // ambiguous +``` + +In the given example, modules aka source-sets form the following dependency graph: +``` +common +^ +| +intermediate unrelated +^ ^ +| / +| / +platform +``` + +### Source-set and module relation + +During the platform compilation, the entire [source-set](#source-set) graph is passed to the compiler CLI. + +See: `-Xfragments`, `-Xfragment-sources`, `-Xfragment-refines`. + +When combined with [binary dependencies](#platform-compilation-binary-dependencies) passed, it allows constructing graph of +compiler [modules](#module). + +Compiler [module](#module) is created for each passed [source-set](#source-set). + +### Platform compilation binary dependencies + +The K2 uses shared **platform binary dependencies** for analysis of **all** source-sets in the platform compilation. + +The key reason for that is that we are unable to provide full KLibs for [common source-sets](#common-source-set) yet. + +Observed behavior is the following: +```kotlin +// MODULE: common_dep +// library: dep +fun foo(a: Any) {} // (dep.1) + +// MODULE: platform_dep +// library: dep +fun foo(a: String) {} // (dep.2) + +// MODULE: common +// dependencies { implementation("dep") } +fun bar(a: Any) {} // (a.1) + +fun test() { + bar("") // (a.1) + foo("") // (dep.2) !!! While platform compilation for this module +} + +// MODULE: platform +// depends-on: common +fun bar(a: String) {} // (a.2) +``` + +### Platform compilation pipeline + +Given that, source analysis order is defined as the following: + +Analyze from the most common module to the platform modules. +So that all `depends-on` modules of the module are analyzed before the module itself. + +In order to achieve it, [modules](#module) are sorted topologically over depends-on relation graph. + +Platform compilation pipeline consists of the following steps: +1. For each [module](#module) in source analysis order + - [Use a shared set of binary dependencies](#platform-compilation-binary-dependencies) + - [Run the frontend, store FIR representation.](#frontend-support-for-expectactual) + - Use FIR representations of depends on modules that were obtained before as dependencies. + - *[Resolution requires knowledge of actual->expect binding.](#fir-actual---expect-binding-construction) + - [Run the frontend checkers for module FIR.](#frontend-checkers) + - [Run the FIR2IR, store the resulting IR module fragment.](#fir2ir) + - *[Re-use the FIR2IR state](#fir2ir-shared-state) +2. [Combine all resulting IR module fragments](#iractualizer) + - Perform [actualization](#actualization) + - Check that every expect declaration has corresponding actual and was actualized. + - *Reconstruct fake-overrides. + - Remove expect declarations. + - Merge IR module fragments into last fragment + - Now, we have complete IR for module that doesn't reference any expects and could be passed to backend. +3. Pass the resulting IR to the backend or serialize it into KLib. + - Backend compiler plugins are invoked before running the backend + +### FIR2IR + +FIR2IR is applied to the modules in the topological order. + +**Inputs:** +- FIR of one of the [modules](#module) +- [Compilation shared state](#fir2ir-shared-state) + +**Outputs:** +- IR for the module +- Mutation of [shared state](#fir2ir-shared-state) + +#### FIR2IR shared state + +The FIR2IR state, represented by `Fir2IrCommonMemberStorage` is shared across entire [platform compilation](#platform-compilation). + +See: [`org.jetbrains.kotlin.fir.backend.Fir2IrCommonMemberStorage`](../../compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrCommonMemberStorage.kt) + +Since FIR2IR is invoked over each [module](#module) over depends-on graph, +we need to avoid creating IR for declarations in a [common source-sets](#common-source-set) multiple times. + +The same applies to the declarations from [binary dependencies](#platform-compilation-binary-dependencies), which are shared among the entire compilation. + +Therefore, FIR2IR uses shared storage for get-or-create operations for declarations. + +### IRActualizer + +The IR actualizer is a component performing [actualization](#actualization) over IR + +See: [`org.jetbrains.kotlin.backend.common.actualizer.IrActualizer`](../../compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/actualizer/IrActualizer.kt) + +In the current model, IR actualizer is used during the [platform compilation](#platform-compilation), to produce complete IR that is correct +from the backend standpoint. + +**Inputs:** +- IR for each of the modules. +- IR might contain expect declarations and references to it. + +**Outputs:** +- Single IR module. +- No expect declarations in the IR. +- IR is in the usual state, as for non-multiplatform projects + +**Constraints:** +- No access to FIR or frontend state allowed +- Must operate over IR +- Diagnostic reporting is complicated and won't work for the IDE without special support + +The following actions are preformed: +- Expect -> actual binding is constructed + - We have all expects and that's why we can construct it as opposed to the [actual -> expect binding](#actual-expect-binding) + - We can report diagnostics at that moment +- Top-level expect declarations are detached from IrModuleFragment + - Member expect declarations can only be contained within top-level expects, and is detached together with its container + - Modulo [`@OptionalExpectation`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-optional-expectation/) +- IR for default values is copied from expects to actuals. See [default propagation](#default-propagation) for the frontend part of work. +- All module fragments are merged into one +- [Fake-override actualization is performed](#fake-overrides) + +### Fake overrides + +FIR doesn't use fake-overrides in the frontend, and that's why we construct them during FIR2IR in order to match backend expectations +By that, we require special handling of fake-overrides during the [platform compilation](#platform-compilation). + +during the analysis and couldn't construct fake-overrides properly. + +Ex: +```kotlin +// MODULE: common +expect class A +expect class B + + +interface I { + fun foo(q: A) +} + +interface J { + fun foo(q: B) +} + +interface K : I, J { + // FIR2IR F/O fun foo(q: A) (1) + // FIR2IR F/O fun foo(q: B) (2) +} + +// MODULE: platform()()(common) + +actual typealias A = Int +actual typealias B = Int + +class Impl : K { + override fun foo(q: Int) {} +} + +``` + +In the given example during the actualization, we need to combine (1) and (2) into one fake-override. + +## Frontend support for expect/actual + +During the resolution of each module, a special set of measures is implemented to allow proper resolution. + +### Type refinement + +Type refinement is a process of obtaining use-site view to the declaration. +Since we re-use FIR of each module during the analysis of its dependants, we need to perform +type refinement. + +```kotlin +// MODULE: common +expect class Foo + +val foo: Foo = TODO() +// declaration-site type: expect class Foo + +// MODULE: platform()()(common) +actual class Foo { + fun bar() { } +} + +fun test() { + foo.bar() // use-site type: actual class Foo +} +``` + +**It is possible due to the following principles:** +- It isn't possible to obtain the scope of a type without knowing the use-site +- It isn't possible to obtain the type declaring class without knowing the use-site +- Every type is subject to the refinement before use +- ClassId can only be used to identify class within one module + +Refinement of a type happens in two stages: +1. `ConeKotlinType.fullyExpandedType(useSiteSession: FirSession): ConeKotlinType` + - At that stage, we unwrap any type-aliases in the type using useSiteSession as a point of view. +2. `ConeClassifierLookupTag.toSymbol(useSiteSession: FirSession): FirClassifierSymbol<*>` + - All classifier types contain lookupTag inside them. + - To obtain reference to the type declaring class, one should call toSymbol on it providing the useSiteSession as a point of view. + - Then, the lookup of the class in the use-site module will be performed based on the class id stored within the lookup tag. + +**WARNING:** Type refinement is also needed for general dependency substitution algorithm, such as classpath order substitution. + +**It works for expect/actual classes only because [actual->expect binding](#actual-expect-binding) is defined as a matching of ClassId** + +### Default propagation + +We need to know a set of arguments that should be provided for a call to resolve it due to the Kotlin resolution and overloading rules. + +```kotlin +// MODULE: common +expect fun foo(a: String = "") +expect class Bar { + fun buz(a: String = "") +} +// MODULE: intermediate()()(common) +actual fun foo(a: String) {} +actual class Bar { + fun buz(a: String) {} +} + +fun test() { + foo() // use-site + Bar().buz() // use-site +} +``` + +In the given example, we need to know [actual->expect binding](#actual-expect-binding) in order to resolve use-site calls. + +As during the resolution on the use-site, we will resolve to the actual declarations, we need to obtain its default argument positions from +actuals. + +### FIR: actual -> expect binding construction + +We compute that binding by analyzing module with `FirExpectActualMatcherTransformer`. + +See: [`org.jetbrains.kotlin.fir.resolve.transformers.mpp.FirExpectActualMatcherTransformer`](../../compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/mpp/FirExpectActualMatcherTransformer.kt) + +**The responsibility** of this phase is to perform [expect/actual matching](#matching-vs-checking). + +This phase happens before body/implicit type resolution +as the binding is required to perform [call resolution in case of defaults](#default-propagation) + +Binding is stored in the `FirDeclaration.expectForActual` attribute and allow to determine expects that corresponds to the +actual declaration during the analysis of `intermediate` module. + +**Hard-constraint:** +`FirExpectActualMatcherTransformer` cannot use return types of declarations to bind actual with expect. + +Since the return type of actual function might be not yet resolved before the implicit type body resolve phase. + +```kotlin +// MODULE: common +expect fun foo(a: String = "") + +// MODULE: platform()()(common) +actual fun foo(a: String) = run { + foo() // In order to resolve this call, we need to have actual -> expect binding +} +``` + +**Explanation:** While it is possible to compute the binding on-demand and remove this constraint, +we chose to have it as a separate phase to avoid further complication of body resolve. + +There are no overloads by return type in Kotlin, and it makes it possible to avoid return type matching as a part of +[actual->expect binding construction](#fir-actual---expect-binding-construction). +However, we still [check](#frontend-checkers) that return type matches afterward. + +#### Matching vs checking + +Currently, both matching and a part of checking are performed in the `AbstractExpectActualCompatibilityChecker`. + +See: [`org.jetbrains.kotlin.resolve.calls.mpp.AbstractExpectActualCompatibilityChecker`](../../compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualCompatibilityChecker.kt) + +Matching is a process of finding expect-actual pairs. +For classes, it is matching of class-ids. +For callables, it is a complex rule, similar to overloading. +If declarations don't match, no pair is formed and **matching continues** over other declarations. + +Checking is a process of checking that a pair is correct w.r.t all compatibility requirements. +If checking failed for a pair, error is reported for the pair. +It will fail compilation. + +## Frontend checkers + +Frontend checkers are executed in the context of each module (use-site), after its resolution. + +**Inputs:** +- FIR of one of the [modules](#module) + - It can contain both expects and actuals + +**Constraints:** +- The full set of actuals aren't known yet +- [Actual -> expect binding](#actual-expect-binding) is available +- [Limitations](#limitations) + +### Limitations + +The key limitation is the fact that frontend checkers are run in the context of declaration site. + +Thus, it is impossible to observe member scopes of classes with respect to actualization since the corresponding actuals +are contained in further modules. + +Type checks can also be performed with knowledge available on expect declaration site. + +In order to reduce the need to perform additional checks on the backend and in the [IRActualizer](#iractualizer) it is advised to +follow the [LSP](https://en.wikipedia.org/wiki/Liskov_substitution_principle) in the design. + +By that, meaning that actual declaration must be as compatible with the corresponding expect declaration, as that +it could replace the expect declaration, without the appearance of errors. + +**Note:** There are compiler checks that violate LSP to the extent that it is reported on certain leaf types. +It is expected that such checks won't trigger in case of actualization. + +E.g: +```kotlin +// MODULE: common +expect class E() + +fun foo() { + E() // It's OK, since it isn't deprecated in common +} + +// MODULE: platform()()(common) +@Deprecated("Not OK") +actual class E actual constructor() {} +```