[KotlinConf2024] Revamping Kotlin’s Type System: A Vision
At KotlinConf2024, Ross Tate, a programming language researcher, exposed critical flaws in Kotlin’s type system, including undecidability and unsoundness, which can crash compilers or misclassify types. Collaborating with the Kotlin team, he proposed pragmatic restrictions to ensure reliability and introduced extensions like categorized union types for error handling. Ross shared a long-term strategy to make type checking sound, decidable, and extensible, inviting developers to shape Kotlin’s future through feedback, balancing theory with practical needs.
Uncovering Type System Flaws
Kotlin’s type system, while powerful, is flawed. Ross revealed its undecidability, where subtyping questions can encode Turing machines, causing unpredictable compiler behavior. This stems from Java’s similar issues, as proven by Ru Gregori’s research. Unsoundness is equally concerning—Ross demonstrated a program tricking the compiler into treating an Int as a String using type projections and nulls. These flaws, also present in Java and Scala, undermine reliability, making robust type checking a priority for Kotlin’s evolution.
The Dangers of Unsound Programs
Unsoundness risks memory corruption. Ross presented a fast integer-to-string converter that, without proper checks, could introduce vulnerabilities. Initially, Kotlin’s compiler rejected it, as Int isn’t a subtype of String. However, adding a magic configuration with existential type projections bypassed this safeguard, fooling the compiler. Adapted from Java and Scala examples, this highlights a shared problem. Ross stressed that revamping Kotlin involves eliminating such unintentional backdoors, ensuring only explicit casts compromise safety, preserving developer trust.
Type Inference Challenges
Type inference, vital for Kotlin’s usability, struggles with decomposition. Ross showed a tree class for sorting adjectives, which type-checks when whole but fails when split into smaller parts. The compiler couldn’t infer the branch type B, violating the principle that breaking programs into smaller units shouldn’t break type checking. Co-variance adjustments revealed a principal type (Nothing), but Java’s undecidable subtyping influenced Kotlin’s conservative design. Ross aims to fix this, ensuring inference supports modular, predictable codebases.
Pragmatic Restrictions for Decidability
To address undecidability, Ross proposed separating interfaces into “shapes” (type constraints, like Comparable) and “materials” (data types, like function interfaces). Analyzing 135 million lines of Java code, he found all interfaces fit one category, making subtyping decidable in practice. By embedding this pattern into Kotlin, type checking becomes reliable and efficient, running in polynomial time. This separation also improves usability, as hovering over a variable avoids irrelevant types like Comparable<*>, aligning with developer expectations.
Categorized Union Types for Errors
Ross previewed categorized union types, restricted to prevent exponential type-checking costs. Types are grouped into categories (e.g., Null, Any, Error), allowing unions only across categories, like T | NoSuchValue. This enables distinguishing custom errors from null, as shown in a lastOrError function. Operators like !. (propagate error), !: (replace error), and !! (throw exception) mirror nullable syntax, simplifying libraries. Q&A clarified errors remain manipulable values, enhancing flexibility without compromising efficiency.
Enhancing Error Handling
The proposed error system differentiates errors (values) from exceptions (control flow). Error classes include a throw method for conversion to exceptions, while Throwable subclasses form distinct categories, enabling multi-catch via union types. A try-catch variant infers the union of thrown types, supporting exhaustive checks with Java’s typed exceptions. This design, inspired by Rust’s result pattern, balances explicit error handling with backward compatibility, addressing interoperability concerns raised in Q&A about Java’s ecosystem.
Shaping Kotlin’s Future
Ross emphasized that these changes are experimental, requiring prototypes, trials, and community input. Challenges like name resolution and method overloading need strategies, and features must cohere. He invited feedback via issue KT-68296, especially on error naming (e.g., “Error” vs. “Sentinel”) to avoid Java confusion. The talk underscored Kotlin’s shift toward optimizing its own experience, even at the cost of some Java interop precision, ensuring a reliable, extensible type system for future developers.