Files
werkcrappit/docs/adr/0009-kotlin-jvm.md
claude-code 325e9e42a9
werkator build succeeded after 02:45
werkator/mutation build succeeded after 04:43
werkator/publish build succeeded after 00:14
Kotlin/JVM basis support checked, limits documented (0.26.6) (#61)
Co-authored-by: claude-code <claude-code@javagil.de>
2026-10-11 16:16:47 +02:00

3.4 KiB

ADR 0009: Kotlin/JVM Basis Support

  • Status: accepted
  • Date: 2026-10-11
  • PR: #61

Context

The specification makes Kotlin/JVM a limited, optional goal (section 7). werkcrappit joins PIT's mutants and JaCoCo's complexity by JVM class, method name and descriptor, never by source lines. The Kotlin compiler turns one source function into several JVM methods, or into none of its own, so the question is what each construct gives in the report. Werkator, written in Kotlin, has run werkcrappit every night since PR#30 of werkcrappit, with a limit of 15.

Decision

werkcrappit supports Kotlin/JVM as it is, without code of its own for Kotlin. KotlinBasicTest runs PIT with the packaged plugin on the fixture kotlin-basic and pins what each construct gives; the versions are those in compatibility.md.

Kotlin construct JVM methods In the report
ordinary method, when, overloads one per function, overloads by descriptor SCORED, each overload apart
top-level function a static method of <File>Kt SCORED
companion object methods of <Class>$Companion SCORED; its constructors EXCLUDED by JaCoCo
property with a getter get…/is…, set… SCORED like any method; a setter without mutants has no score
default arguments the function and a static …$default the function SCORED; …$default NO_MUTATION_RESULTS
data class equals, hashCode, toString, copy, componentN EXCLUDED, jacoco-filtered:other or :synthetic
bridge of a generic override a synthetic bridge method EXCLUDED, jacoco-filtered:bridge
lambda passed to an inline function none: inlined into the caller counts for the caller
other lambda a static …$lambda$0 SCORED apart; the function around it keeps only its own code
inline function the function, and a copy in every caller its own body never runs: its mutants stay NO_COVERAGE
suspend function the function and a continuation class SCORED; the continuation EXCLUDED; see the limits

Limits

  • Inline functions: PIT mutates the inline function itself, which no test runs, as every caller runs its own copy. Its score is then C² + C, whatever the tests do. The copies count for the callers, which carry those mutants.
  • Suspend functions with a suspension point: JaCoCo leaves the compiler's state machine out of the complexity, but PIT mutates it; some of these mutants are never covered or survive, which lowers the mutation score. A suspend function can so score above its complexity although its tests check its logic.
  • Kotlin's extra branches: JaCoCo counts branches the compiler adds, e.g. the else of an exhaustive when or an inlined let. This raises the complexity, so a Kotlin project may set a higher limit than a Java one: Werkator uses 15, against 10 in hsadmin-ng.
  • Arcmutate's Kotlin support for PIT is not part of the tested setup.

Consequences

  • The report can be read for Kotlin as for Java, with the limits above in mind; no Kotlin method gets an invented score.
  • A new Kotlin, PIT or JaCoCo version can change the table: their filters change; KotlinBasicTest shows it.
  • The specification's IDs @MC-032 and @MC-033 for Kotlin are taken by the progress scenarios of 04-pit-lifecycle.feature; the Kotlin scenarios use @MC-KT-… in 11-kotlin-jvm.feature.