Open-Source Wikis

/

Kotlin

/

How to contribute

/

Patterns and conventions

JetBrains/kotlin

Patterns and conventions

This page documents recurring patterns in the codebase: naming prefixes, generated-vs-handwritten boundaries, error handling, and the conventions that span multiple subsystems. New contributors save time by recognizing these patterns before diving into a specific area.

Naming prefixes

The repository uses several short prefix conventions that act as a navigation aid. Knowing the prefix tells you what subsystem a type belongs to:

Prefix Meaning Example Lives in
Kt PSI element (Kotlin AST node) KtFile, KtClass, KtNamedFunction compiler/psi/
Fir K2 / FIR IR node or service FirRegularClass, FirResolvePhase, FirSymbol compiler/fir/
Ka Analysis API public type KaSession, KaSymbol, KaCallableSymbol analysis/analysis-api*/
Ir Backend IR node IrClass, IrFunction, IrCall compiler/ir/ir.tree/
LL Low-level FIR API (IDE-facing, on-demand) LLFirSession, LLFirResolveSession analysis/low-level-api-fir/
Cone K2 type system primitive ConeKotlinType, ConeClassLikeType compiler/fir/cones/
Js JS backend / AST JsExpression, JsStatement js/js.ast/
Wasm Wasm backend / IR WasmModule, WasmFunction wasm/wasm.ir/

When you see KaFirX, that's an Analysis API type (Ka) with a FIR-backed implementation (Fir).

K1 / K2 dual-implementation pattern

Many language features must be implemented twice — once for K1 and once for K2 — until K1 is fully retired:

  • K1 diagnostic checkers in compiler/frontend/.../checkers
  • K2 diagnostic checkers in compiler/fir/checkers/
  • K1 → IR via compiler/ir/ir.psi2ir/ (PSI + descriptors → IR)
  • K2 → IR via compiler/fir/fir2ir/ (FIR → IR)
  • Analysis API K1 backend in analysis/analysis-api-fe10/
  • Analysis API K2 backend in analysis/analysis-api-fir/

When adding a new language feature, expect to do work on both sides for the deprecation window, with K2 as the primary target.

Generated code

A non-trivial fraction of the source tree is generated. The convention is to keep generated outputs in a gen/ directory next to handwritten src/:

compiler/fir/checkers/
├── src/                           # handwritten
└── gen/                           # generated by checkers-component-generator

Generators themselves live near their outputs (e.g., compiler/fir/checkers/checkers-component-generator/) or in the top-level generators/ directory for cross-cutting generation. The repo also has standalone generators for FIR tree definitions, IR tree definitions, compiler arguments, and Analysis API diagnostics.

The rule: edit the generator and the input definition, not the generated output. Generated files start with a header comment indicating their generator; some have Generated.kt/Generated.java suffixes.

Phase invariants in FIR

K2's correctness depends on a small but strict invariant in compiler/fir/resolve:

Within phase B that follows phase A, every FIR element visible to B has been resolved through phase A.

(See compiler/AGENTS.md for the canonical statement.) This is checked in tests; violating it is one of the most common causes of K2 crashes. When introducing a new phase or moving work between phases, audit every FirSymbol/FirElement access for what phase must have completed.

Error handling

The compiler distinguishes:

  • Diagnostics — user-visible compile errors and warnings. Reported via DiagnosticReporter (K2) or DiagnosticSink (K1). Always have a stable error code (KaFirDiagnostic, FirError*) and a localized message in *ErrorsDefaultMessages.kt.
  • Internal errorsIllegalStateException, IllegalArgumentException, errorWithAttachment(...) (in compiler/utils/compiler/util). These crash the compiler and indicate a bug, not user error. Wrap user-facing diagnostics around plausible-but-recoverable conditions; reserve internal exceptions for "this should not happen".

errorWithAttachment includes structured attachment information (FIR/IR dumps of the offending element, file paths, etc.) in crash reports — it's the preferred way to crash, because the report is rich enough to debug from.

Symbol resolution

The K2 resolve pipeline is a series of incremental refinements over FirSymbol. The rule is: never call code that needs phase N+1 information from a checker running in phase N. The Analysis API's low-level FIR (analysis/low-level-api-fir/) sits on top of the same machinery and exposes on-demand resolve via LLFirResolveSession.

Coding style

The Kotlin team follows the Kotlin coding conventions (https://kotlinlang.org/docs/coding-conventions.html), enforced via the IntelliJ formatter and the .editorconfig at the repo root. Highlights:

  • 4-space indentation in Kotlin and Java sources.
  • 120-column soft limit (some files hold to 100, especially older code).
  • One top-level declaration per file is encouraged but not enforced.
  • Internal types use internal rather than package-private when they need to be hidden from external module consumers.

The Native subtree (kotlin-native/) follows its own clang-format style for C/C++ (kotlin-native/.clang-format).

Multiplatform code organization

Standard library and any Kotlin Multiplatform module follow a standard source-set layout:

src/
├── commonMain/        # shared expect declarations + common implementations
├── jvmMain/           # JVM actuals
├── jsMain/            # JS actuals
├── nativeMain/        # Native actuals
├── wasmMain/          # Wasm actuals
└── ...Test/           # corresponding test source sets

The convention used inside libraries/stdlib/ is slightly different (top-level common/, jvm/, js/, native-wasm/, wasm/ directories, each with their own source roots) due to the bootstrapping needs of the stdlib build.

Public API surface management

Modules with a public API (KGP, KGP-API, BTAPI, etc.) commit a .api file capturing every exported symbol. Changing public API → updating the .api file in the same commit. CI fails if they drift.

Files to know:

  • libraries/tools/kotlin-gradle-plugin/api/all/kotlin-gradle-plugin.api
  • compiler/build-tools/kotlin-build-tools-api/api/kotlin-build-tools-api.api
  • klib ABI dumps under libraries/tools/abi-validation/

Internal vs experimental

The codebase distinguishes:

  • @Experimental / @RequiresOptIn annotated APIs — usable but require opt-in.
  • @InternalKotlinGradlePluginApi, @InternalKotlinNativeApi, etc. — usable across modules in this repo, not stable for external use.
  • internal modifier — limited to the module.

Public APIs without an opt-in are stable. Don't change them without a deprecation cycle.

Cross-references

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Patterns and conventions – Kotlin wiki | Factory