561 lines
23 KiB
Markdown
561 lines
23 KiB
Markdown
# 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)
|
|
- [Fake overrides](#fake-overrides)
|
|
- [Lazy classes](#lazy-classes)
|
|
- [IRActualizer](#iractualizer)
|
|
- [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.
|
|
|
|
### 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
|
|
|
|
Also, the following example shows that it is impossible to build valid fake overrides during common
|
|
module compilation.
|
|
|
|
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) {}
|
|
}
|
|
|
|
```
|
|
|
|
***Note: Approach described below is a target. It's not yet enabled by default. See KT-61514 for details.***
|
|
|
|
In the given example during the actualization, common compilation would produce (1) and (2), which need to be combined
|
|
into one fake override.
|
|
|
|
There is a new scheme, which is enabled by -Xuse-ir-fake-override-builder flag, and should become default at some point.
|
|
|
|
In it, [Fir2Ir](#fir2ir) creates a special symbol for fake overrides calls, and no declarations for fake overrides
|
|
(except one in [Lazy classes](#lazy-classes)). A special symbol, represented by [org.jetbrains.kotlin.ir.symbols.impl.IrFakeOverrideSymbolBase](../../compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/symbols/impl/IrFakeOverrideSymbol.kt) and its inheritors,
|
|
is effectively a pair of real declaration symbol and dispatch receiver class. This symbol can't be bound,
|
|
so backend can't work with it. To fix it, there is a phase, which replaces them with normal ones
|
|
after [actualization](#actualization). The symbol is mapped to single one overriding corresponding
|
|
real symbol within corresponding dispatch receiver class. It can be both a fake override and real declared symbol.
|
|
|
|
In theory, doing the same with other synthetic declarations (delegated members, data class generated members, etc)
|
|
can lead to more consistent behaviour, but we don't do it know, as we are not aware of any problems
|
|
|
|
### Lazy classes
|
|
|
|
Lazy classes represent classes used from compiled sources, but not present in them. This can be classes
|
|
from dependencies, java sources, or sources already compiled on previous round of incremental compilation.
|
|
|
|
Lazy classes are a special case for fake override building. They are now handled with frontend builder.
|
|
It can be done correctly, as they can only exist in platform session.
|
|
|
|
There is a technical issue with them. It's possible to have a lazy class with normal class super-type,
|
|
so it can refer to a fake override symbol in overridden symbols of some methods.
|
|
But we can't fix them all in the same place, as normal ones, as it would trigger lazy computations, which should be avoided.
|
|
|
|
To fix this, the rebuild is delayed while possible, using [org.jetbrains.kotlin.fir.backend.Fir2IrSymbolsMappingForLazyClasses](../../compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrSymbolsMappingForLazyClasses.kt),
|
|
which stores delayed operations to happen, when some read lazy property.
|
|
|
|
### 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.
|
|
- IR can contain unbound fake override symbols
|
|
|
|
**Outputs:**
|
|
- Single IR module.
|
|
- No expect declarations in the IR.
|
|
- No unbound symbols 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 for classifiers is constructed and applied
|
|
- Fake overrides are constructed
|
|
- Expect -> actual binding for classifiers is callables and applied
|
|
- 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
|
|
- 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 symbols are replaced with normal ones
|
|
- Constants are evaluated
|
|
- 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/)
|
|
- Errors can be emitted if some expect-actual matches are bad.
|
|
|
|
There are several restrictions, because of which this order of actions is required:
|
|
|
|
- Fake overrides can't be built before classes are actualized
|
|
- Because we need to know what is real supertype.
|
|
- Callables can't be actulaized before fake overrides are built
|
|
- Some of them can match with fake override
|
|
- Constants can't be evaluated before callables are actualized
|
|
- As some expect function can become possible to evaluate
|
|
- Checkers can't run before constants are evaluated
|
|
- They need to check of some value are equal
|
|
|
|
|
|
## 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).
|
|
|
|
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
|
|
|
|
Expect-actual matching is performed in the `AbstractExpectActualMatcher`.
|
|
|
|
See: [`org.jetbrains.kotlin.resolve.calls.mpp.AbstractExpectActualMatcher`](../../compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualMatcher.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.
|
|
|
|
All possible mismatches that can be reported by `AbstractExpectActualMatcher` are declared in
|
|
[`ExpectActualMatchingCompatibility`](../../core/compiler.common/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectActualCompatibility.kt)
|
|
|
|
#### Checking
|
|
|
|
Expect-actual checking is performed in the `AbstractExpectActualChecker`.
|
|
|
|
See: [`org.jetbrains.kotlin.resolve.calls.mpp.AbstractExpectActualChecker`](../../compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualChecker.kt)
|
|
|
|
Checking is a process of checking that a pair of [already matched declarations](#matching) is correct w.r.t all compatibility requirements.
|
|
If checking failed for a pair, error is reported for the pair.
|
|
It will fail compilation.
|
|
|
|
All possible "checking" incompatibilities that can be reported by `AbstractExpectActualChecker` are declared in
|
|
[`ExpectActualCheckingCompatibility`](../../core/compiler.common/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectActualCompatibility.kt)
|
|
|
|
## 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() {}
|
|
```
|