Kotlin Multiplatform¶
KMP checks: source-set complexity, target bloat, expect/actual consistency, and cross-module version drift.
15 checks.
kmp-common-code-fragmented¶
Severity: Low
Fragmenting common code across many intermediate source sets makes it hard to locate logic and raises the cost of every cross-platform change.
How to fix: Collapse redundant intermediate source sets; prefer the default hierarchy template for common→platform sharing.
kmp-deep-source-set-nesting¶
Severity: Low
Deeply nested intermediate source sets make it hard to know where code belongs and complicate adding/removing a target.
How to fix: Flatten the hierarchy using the Kotlin default hierarchy template instead of hand-rolled intermediate source sets.
kmp-empty-target-source-set¶
Severity: Low
Empty source-set blocks are dead configuration: they enlarge the source-set graph and confuse readers without holding any platform code.
How to fix: Remove the empty source-set block, or move shared code into it. Default target source sets do not need an explicit empty block.
kmp-excessive-targets¶
Severity: Low
Every extra target compiles and tests separately — build/CI time and the source-set matrix grow with the target count. Unused targets are pure overhead.
How to fix: Declare only the targets you actually ship. Drop unused platforms (or gate them behind a flag) to shrink the build matrix.
kmp-ios-targets-without-integration¶
Severity: Low
Apple targets that nothing integrates still cost build/CI time and signal an abandoned or externally-consumed iOS surface that may be unintentional.
How to fix: Add the iOS app/integration (Xcode project, SwiftPM, or CocoaPods consuming the framework), or remove the Apple targets if they are not shipped from this repo.
kmp-kotlin-plugin-version-drift¶
Severity: Low
Mixed Kotlin compiler versions across modules produce incompatible klib/metadata and fail the multiplatform build or cause subtle ABI mismatches.
How to fix: Pin one Kotlin version (e.g. via a version catalog or the root plugins block) and reference it from every module.
kmp-large-source-set-matrix¶
Severity: Low
A sprawling source-set matrix increases cognitive load, build graph size, and the chance of misplaced platform code.
How to fix: Consolidate intermediate source sets and rely on the default hierarchy template where possible.
kmp-legacy-native-memory-api¶
Severity: Low
The new Kotlin/Native memory manager is the default since 1.7.20 and the only one since 1.9.20. freeze()/isFrozen/@SharedImmutable/ensureNeverFrozen are no-ops or removed — the code still compiles but the freezing guarantees are gone. It is dead weight that misleads anyone reasoning about cross-thread sharing.
How to fix: Remove freeze()/isFrozen/ensureNeverFrozen calls and the @SharedImmutable annotation; share state directly under the new memory manager. If you are still on the legacy manager, migrate (kotlin.native.binary.memoryModel=experimental is no longer needed).
kmp-library-version-drift¶
Severity: Low
Different versions of the same library across modules invite classpath conflicts, duplicated transitive graphs, and runtime errors that depend on resolution order.
How to fix: Declare shared dependency versions once (version catalog / platform BOM) and reference them from all modules.
kmp-missing-actual¶
Severity: Medium
Every expect needs a matching actual per target. With none, the build fails to compile — or, if hidden by a missing target, ships a broken platform.
How to fix: Provide an actual for each expect in the relevant platform source sets (e.g. androidMain, iosMain), or delete the expectation.
kmp-native-cache-disabled¶
Severity: Medium–Low
Disabling the cache is the standard workaround for a specific Kotlin/Native compiler bug, and it is almost always meant to be temporary. The bug gets fixed in a later Kotlin release; the property stays. From then on every debug build of the iOS app pays a full recompile that nothing reports as a problem — the build is correct, just minutes slower than it should be, permanently.
How to fix: Remove kotlin.native.cacheKind=none and check whether the compiler bug it worked around is fixed in the Kotlin version now in use. If it is still needed, scope it to the affected target and leave the issue id next to it so it can be retired.
kmp-orphan-actual¶
Severity: Low
An actual with no corresponding expect is dead or mismatched code — usually a leftover from a removed expectation.
How to fix: Remove the orphan actual, or restore the expect declaration it implements.
kmp-partial-actual-coverage¶
Severity: Low
A platform source set with expectations upstream but no actuals can fail to compile for that target while others build fine.
How to fix: Confirm each platform implements its expectations (directly or via a shared intermediate source set); add the missing actuals.
kmp-toolchain-version-drift¶
Severity: Low
Mismatched JVM targets across modules can produce class-version errors at runtime and inconsistent desugaring/behavior.
How to fix: Configure a single jvmToolchain version in a convention plugin or the root build and apply it everywhere.
kmp-transitive-export-enabled¶
Severity: Medium
The framework and its generated Objective-C header both grow with the whole transitive graph, which slows Xcode down and lengthens link time — and every iOS consumer can now see and call symbols the shared module never intended to publish, so removing the flag later becomes a source-breaking change for them.
How to fix: Drop transitiveExport = true and export(...) exactly the dependencies whose types appear in the shared module's public API. Keep the exported set explicit so an accidental new dependency does not silently widen the framework.