JetBrains/kotlin
Debugging
Compilers are notoriously hard to debug. The Kotlin compiler is no exception, but the codebase has accumulated tooling that makes most pain points navigable once you know where to look.
Where errors come from
| Symptom | Likely source |
|---|---|
| Compile error or warning shown to the user | A diagnostic. Search for the message ID in compiler/fir/checkers/gen/.../FirErrors.kt (K2) or compiler/frontend/.../diagnostics (K1). |
java.lang.IllegalStateException from the compiler |
Usually a contract violation in IR (compiler/ir/ir.validation/) or FIR (FirResolvePhase invariants). |
| Wrong bytecode | A JVM lowering or codegen pass in compiler/ir/backend.jvm/ or compiler/backend/. |
| Wrong JS/Wasm/Native output | The corresponding backend (compiler/ir/backend.{js,wasm}/, kotlin-native/backend.native/). |
| Daemon-related issue | compiler/daemon/. |
| Gradle / KGP issue | libraries/tools/kotlin-gradle-plugin*/. |
| IDE issue | The IntelliJ Kotlin plugin lives in a different repo (JetBrains/intellij-community). The Analysis API in this repo (analysis/) is the surface it uses. |
Reproducing a compiler issue
- Get the smallest possible Kotlin source that triggers the problem.
- Drop it under an appropriate
testData/directory and add the right directives. Inspecting an existing fixture in the same directory is the fastest way to pick the right ones. - Run the test class that processes that directory:
./gradlew :compiler:test --tests "<FQCN>" -q - If the test runner doesn't see your file, run
./gradlew generateTestsand re-run.
IR / FIR dumps
The fastest way to understand what the compiler is doing is to dump the intermediate representation:
- FIR dump — many K2 tests already write
.fir.txtfiles alongside the input. The dump shows the resolved FIR tree. - IR dump — most codegen tests have an
.ir.txtcompanion showing IR before/after lowering. - For ad-hoc investigation, the standalone
Fir*andIr*Rendererclasses incompiler/fir/tree/andcompiler/ir/ir.tree/produce text dumps.
Run a test with -Pkotlin.test.update.test.data=true to write the dumps to disk, then git diff shows what changed. This is a key debugging technique — dump current behavior, change one thing, dump again, diff.
Diagnostics: K1 vs K2
K1 and K2 use different diagnostic registration paths:
- K1 — diagnostics defined in
compiler/frontend/.../diagnostics, surfaced viaBindingContext. Usually edited together with the resolution code that reports them. - K2 — diagnostics defined in
compiler/fir/checkers/checkers-component-generator/src/.../FirDiagnosticsList.kt, regenerated intocompiler/fir/checkers/gen/.../FirErrors.ktandFirErrorsDefaultMessages.kt. The Analysis API mirrors them inanalysis/analysis-api-fir/gen/.../KaFirDiagnostics*.
If you change the diagnostic surface, also run the regeneration tooling (compiler/fir/checkers/checkers-component-generator/) — the gen output is committed.
Common pitfalls
From .ai/guidelines.md:
- Don't edit
*Generated.javatest files — regenerate them. - The IDE plugin is a different repo —
JetBrains/intellij-community. If your repro touches the IDE, you may need to bisect across both repos. - The K1 vs K2 boundary — many features only land in K2. If a behavior differs between
kotlinc -language-version 1.9and the default, you're hitting the frontend boundary.
Logging
The compiler uses org.jetbrains.kotlin.util.KotlinLogger (and area-specific loggers). Verbose output for a CLI invocation:
./dist/kotlinc/bin/kotlinc -verbose <args>For the daemon, enable JMX/JVM args via KOTLIN_DAEMON_JVM_OPTIONS or the IDE's "Compiler" settings. The daemon writes logs to a per-session file under the system temp directory.
Attaching a debugger
When invoking kotlinc directly:
KOTLIN_OPTS="-Xdebug -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" \
./dist/kotlinc/bin/kotlinc <args>When debugging tests in IntelliJ, just run the test class with the standard "Debug" action. For Gradle-launched processes (e.g., daemon-mode tests, KGP integration tests) use --debug-jvm:
./gradlew :compiler:test --tests "..." --debug-jvmThe Gradle worker waits on port 5005 until a debugger attaches.
When something is wrong with bytecode
If the compiler accepts the source but the produced .class file is wrong:
- Run a
boxtest (incompiler/testData/codegen/box/) that exercises the runtime behavior — these compile + execute and assert outputs. - Use
javap -c -p -von the produced class file to compare against expected bytecode. - The "writeFlags" / "writeBytecode" trace (search for
BytecodeListingTextCollectingVisitor) emits readable bytecode dumps.
When something is wrong with JS or Wasm output
- For JS, run
js/js.tests/and inspect the.jsoutput next to the test input. - For Wasm, run
wasm/wasm.tests/. The Wasm IR is text-printable; seewasm/wasm.ir.
When something is wrong with Native
The Native backend is a different beast. Read kotlin-native/HACKING.md first. The most useful tool is kotlinc-native -Xverbose-phases=ALL to see each lowering pass, and the --print-ir family of compiler flags.
Asking for help
#compilerand#analysis-apichannels on the Kotlin Slack are the fast paths for bug repros.- File a YouTrack ticket if you can isolate a reproducer. CI artifacts and test data can be attached.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.