Build1 publisher2 min readPublished
Kotlin 2.5.0-Beta1 can compile Multiplatform common code the way IntelliJ analyzes it
JetBrains' Kotlin 2.5.0-Beta1 adds an off-by-default mode that stops Multiplatform common code from compiling against dependencies' platform-only declarations. With the flag on, builds reject the calls IntelliJ already flags as unresolved, so KMP teams are better off finding that breakage on a branch first.
The Engineer · Build desk
Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction
What happened
- Inside one module the compiler and IntelliJ already agree; they diverge when the declaration sits in a dependency such as a sibling module, the standard library or kotlinx-coroutines.
- Under the current scheme, compiling jvmMain also compiles commonMain against the dependencies' platform artifacts, so common code can resolve to a JVM-only declaration.
- The same path lets a module's commonTest call declarations from its own jvmMain; the IDE flags those calls as unresolved, but compilation succeeds.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- cost Every common-code call the IDE already marks unresolved becomes a build error, and each one needs an expect/actual pair or a move into platform code before the flag can stay on.
- constraint Tests in commonTest lose their route to jvmMain, so any JVM-only behavior they exercised has to move to a platform test source set.
- decision JetBrains ships the mode with a known-issues list, so it belongs on a branch while main stays on the current compilation scheme.
Inside a single module, nothing needs fixing. A `fun foo()` declared in jvmMain is an unresolved reference from commonMain in both the IDE and the compiler. The reason is that commonMain also compiles for targets such as Kotlin/JS, and jsMain may have no `foo` [6]. Expect/actual declarations exist to make that link explicit [15].
The split comes from what each tool reads when the declaration lives in a dependency. Each platform source set produces a platform artifact: a .jar on the JVM, a .klib on other targets. Each common or intermediate source set, such as commonMain or nativeMain, produces a metadata KLIB that lists its declarations without bodies [8]. IntelliJ resolves common code against that metadata. So if lib/jvmMain declares a `class Foo` and app/commonMain calls `Foo()`, the IDE reports an unresolved reference [10]. For once the red squiggle is the correct answer. The compiler resolves the same call against the jar and reports no error [9][10].
Separate compilation makes the compiler stricter when it compiles common source sets, so it matches what the IDE already assumes [12]. JetBrains also says the mode flags problem calls from common code into library code more consistently [14]. I think JetBrains tightened the right side. Under the current scheme, common code can compile only because the JVM target supplies a declaration that other targets may lack [6][9].
Opting in takes one line in gradle.properties. JetBrains labels the feature Experimental and points to a list of known issues [2][3]:
``` kotlin.kmp.separateCompilation=true ```
Overload resolution is the case I would test hardest. The post lists overloads and type inference among the current disagreements, with the IDE the stricter of the two [4]. An unresolved reference fails the build loudly. An overload disagreement can compile either way. Suppose a dependency declares `fun f(x: Any)` in commonMain and `fun f(x: String)` in jvmMain. Today's JVM compilation of common code sees both declarations and can pick the String version. The metadata the IDE reads holds only the Any version [8][9]. I'd expect the stricter compiler to pick the Any version and still compile cleanly. The JVM build would then behave differently, and only each target's test suite would catch it. I built that example from the resolution rules the post describes; the post does not include one.
What to watch
- Whether the known-issues list for separate compilation shrinks across the remaining 2.5.0 betas and release candidates.
- Any JetBrains announcement that makes kotlin.kmp.separateCompilation the default, or that ships incremental compilation for common source sets on top of it.
- Library maintainers reporting common-code call sites that break under the flag, especially calls that resolve to platform declarations in the standard library or kotlinx-coroutines.