In a hierarchical KMP project, an `expect class` defined in `commonMain` fails to compile for one target with 'expected declaration has no actual'. Walk through the likely causes and how source-set hierarchy and intermediate source sets affect where the actual may live.
answer
- error = some target has no matching actual
- actual can live in intermediate set (appleMain/nativeMain)
- must be a dependsOn-ancestor of the failing target
- check package+name, members, constructor signature
- place actual as high as valid to avoid duplication
basics
~20 sUsually one target has no matching actual. In hierarchical projects the actual can live in an intermediate source set (like appleMain shared by iOS targets), but every leaf target must still be covered. Check package/name match, that all members are actual, and that the source set is on that target's compilation.
solid answer
~60 sThe 'no actual' error means a compiled target's compilation can't find a matching actual. Causes: (1) you only wrote actuals for some targets (e.g. an actual in `jvmMain` but the build also compiles `iosX64`); (2) a **package or name mismatch** between expect and actual; (3) a **member-level mismatch** — the class actual exists but one expected member isn't actualized or has an incompatible signature; (4) the actual sits in a source set that is **not on that target's compilation path**. In a **hierarchical source-set** setup, an actual placed in an **intermediate** source set (e.g. `appleMain`, shared by `iosX64`/`iosArm64`/`macosArm64`) satisfies all targets under it — you don't need per-leaf duplicates. But the intermediate set must actually be a dependsOn-ancestor of the failing target; if you put it in `iosMain` but the target is `macosArm64`, that target is uncovered. Debug by: confirming every leaf target has an actual reachable via the dependsOn graph, checking the default-hierarchy template, and verifying name/package/member signatures. Note: an actual in an intermediate set is itself an actual for that whole group, not a second expect.
code
kotlin · 14 lines// commonMain
expect class Platform() { val name: String }
// One intermediate actual covers all Apple leaf targets
// appleMain
actual class Platform actual constructor() {
actual val name: String =
platform.Foundation.NSProcessInfo.processInfo.operatingSystemVersionString
}
// jvmMain needs its own actual; if missing, jvm compilation reports 'no actual'
actual class Platform actual constructor() {
actual val name: String = System.getProperty("os.name")
}go deeper
Recognizes the error means a target lacks an actual and that one must be added.
Checks package/name and member matching and knows actuals live in platform source sets.
Reasons about the hierarchical dependsOn graph, intermediate source sets like appleMain, and places actuals to cover target groups.
Designs the source-set hierarchy and target matrix so expect/actual coverage is minimal-duplication, robust to added targets, and clear to the team.
## The error *'Expected declaration has no actual declaration in module ... for ...'* means a specific **target compilation** has no actual matching the expect. KMP compiles each target separately; the expect must be actualized for **every** target you build. ## Source-set hierarchy 101 KMP source sets form a tree via **dependsOn**. `commonMain` is the root. With the **default hierarchy template** you get intermediate sets like: - `nativeMain` (all native), `appleMain` (all Apple), `iosMain` (iOS only), then leaf compilations `iosX64`, `iosArm64`, `iosSimulatorArm64`. An actual placed in an intermediate set is visible to — and satisfies the expect for — **every leaf target beneath it**. ## Where an actual may live An actual for an `expect class` can be written in: - the **leaf** platform source set (`iosArm64Main`), or - any **intermediate** source set that is an ancestor of the target (`iosMain`, `appleMain`, `nativeMain`). Put it as high as it's valid (uses only APIs available to all targets in that group) to avoid duplication. ```kotlin // commonMain expect class Platform() { val name: String } // appleMain — covers iosX64/iosArm64/iosSimulatorArm64/macosArm64 at once actual class Platform actual constructor() { actual val name: String = platform.Foundation.NSProcessInfo.processInfo.operatingSystemVersionString } ``` ## Likely causes of the error, in order 1. **Uncovered target**: you added actuals for some targets but not all that the build compiles. Check your `kotlin { }` targets vs your actuals. 2. **Wrong source set**: the actual is in a set that is NOT a dependsOn-ancestor of the failing target (e.g. in `iosMain` while `macosArm64` is also built — macOS isn't under iosMain). Move it up to `appleMain`. 3. **Name/package mismatch**: actual class in a different package or with a different name. Fix the package or use `actual typealias`. 4. **Member mismatch**: the class is actualized but a member isn't — a missing `actual` on a function/property, a different signature, weaker visibility, or a constructor not actualized. The error may then be member-specific. 5. **Default hierarchy not applied / custom dependsOn wrong**: if you hand-rolled the hierarchy, an intermediate set might not actually `dependsOn` commonMain or be wired to the target. ## Debugging checklist - List every target in the Gradle `kotlin {}` block; ensure each has a reachable actual. - Confirm the actual's source set is an ancestor of the failing target in the dependsOn graph. - Verify package + class name match the expect exactly. - Check each expected member has a matching `actual` (including the constructor) with compatible signature/visibility. - Remember actualization-by-inheritance and `actual typealias` are valid ways to satisfy it. ## Key nuance An actual in an intermediate source set is a **single actual** serving a group of targets — not a partial or a re-declared expect. You cannot, however, split one expect's actualization across two sibling sets for the same target; exactly one actual must be visible per target.
- You put the actual in iosMain but macosArm64 still fails. Why?macosArm64 is not under iosMain in the dependsOn graph. Move the actual up to appleMain (or nativeMain) so it covers macOS too, or add a separate macos actual.
- Can two sibling source sets each provide an actual for the same target?No. Exactly one actual must be visible per target via the dependsOn graph; ambiguous or duplicate actuals for one target are an error.
saying these in an interview costs you the question
- Assuming an actual must always be in a leaf source set
- Putting the actual in a set that isn't an ancestor of the failing target
- Forgetting member-level mismatches also trigger the no-actual family of errors
- Thinking an intermediate actual is a second expect or a partial actual
- Ignoring the dependsOn graph / default hierarchy template when debugging