tmpa-2015: kotlin: from null dereference to smart casts
TRANSCRIPT
Mikhail Glukhikh mailto: [email protected]
JetBrains, Senior Software Developer
Developed by JetBrains since 2011
Open-Source since 2012
Targets JVM and JavaScript ◦ Java 6 / 7 / 8 / Android
Statically Typed
Object-Oriented ◦ + Functional features
◦ + Procedural features
Java- and Scala-Compatible
Plugins for IDEA and Eclipse
Kotlin: from null dereference to
smart casts 2
14 minor releases passed in 2012 – 2015
1.0 beta released (Nov 2015)
1.0 coming soon
Lines of Code ◦ ~250 KLOC in Kotlin project itself
◦ ~250 KLOC in other JetBrains projects
◦ ~1000 KLOC in non-JetBrains projects on GitHub
Kotlin: from null dereference to smart casts 3
Working in Kotlin project since March 2015
Before: author of various research static analysis tools
Also: associate professor in SPbPU
Kotlin: from null dereference to smart casts 4
Full Java interoperability Safer than Java ◦ Null safety ◦ No raw types ◦ Invariant arrays ◦ Read-only collections
More concise and expressive than Java ◦ Type inference ◦ Higher-order functions (closures) ◦ Extension functions ◦ Class delegation
Compiler is at least as fast as Java Simpler than Scala
Kotlin: from null dereference to smart casts 5
fun main(args: Array<String>) {
val name = if (args.isNotEmpty()) args[0]
else "Kotlin"
println("Hello, $name")
}
val / var name : Type or = …
fun name(a: TypeA, b: TypeB): Type { … } or = …
Array<>, String
if … else … expressions
"Hello, $name" or even "Hello, ${my.name}"
Kotlin: from null dereference to smart casts 6
No primitive types, everything is an object ◦ Standard classes Int, Long, Char, Boolean, String, etc. ◦ Unit as the single-value type
fun main(args: Array<String>): Unit { … }
◦ Nothing as the type that never exists fun todo(): Nothing = throw AssertionError()
◦ null has the type of Nothing? ◦ Any as the very base type for everything (not-null)
fun equals(other: Any?): Boolean = …
Interfaces and classes Nullable and not-null types ◦ E.g. String? and String, Any? and Any, etc.
Kotlin: from null dereference to smart casts 7
interface Food interface Animal : Food { enum class Kind { HERBIVORE,OMNIVORE,CARNIVORE } val kind: Kind fun eat(food: Food): Boolean fun die() = println("It dies") } class Lion : Animal { override val kind = Animal.Kind.CARNIVORE override fun eat(food: Food) = if (food is Animal) { food.die() true } else false } }
Kotlin: from null dereference to smart casts 8
fun String.greet() = println("Hello, $this")
fun greet(args: List<String>) = args.filter { it.isNotEmpty() } .sortBy { it } .forEach { it.greet() }
Kotlin: from null dereference to smart casts 9
sealed class Tree { object Empty: Tree() class Leaf(val x: Int): Tree() class Node(val left: Tree, val right: Tree): Tree() fun max(): Int = when (this) { Empty -> Int.MIN_VALUE is Leaf -> this.x is Node -> Math.max(this.left.max(), this.right.max()) } }
Kotlin: from null dereference to smart casts 10
Distinct nullable types Type? and not-null types Type
Null checks and smart casts fun foo(s: String?) {
// Error: Only safe or not-null asserted // calls allowed
println(s.length)
// Ok, smart cast to String
if (s != null) println(s.length)
if (s == null) return
println(s.length) // Smart cast also here
}
Kotlin: from null dereference to
smart casts 11
Distinct nullable types Type? and not-null types Type
Safe calls, not-null asserted calls fun foo(s: String?) {
// Error: Only safe or not-null asserted // calls allowed
println(s.length)
// Ok, safe call (s.length or null)
println(s?.length)
// Ok, not-null assertion (unsafe!)
println(s!!.length)
}
Kotlin: from null dereference to
smart casts 12
Distinct nullable types Type? and not-null types Type
Safe call with Elvis operator fun foo(s: String?) {
// Error: Only safe or // not-null asserted calls allowed
println(s.length)
// Ok, safe call + Elvis (s.length or NOTHING)
println(s?.length ?: "NOTHING")
}
Kotlin: from null dereference to smart casts 13
Flexible Types like Type! ◦ Type in Java Type! in Kotlin
◦ Type! is not a syntax, just notation
◦ Assignable to both Type and Type?
◦ Dot is applicable to a variable of Type!
◦ Annotations are taken into account
@NotNull Type in Java Type in Kotlin
Kotlin: from null dereference to smart casts 14
is or !is to check, as or as? to convert class StringHolder(val s: String) {
override fun equals(o: Any?): Boolean {
// Error: unresolved reference (o.s)
return s == o.s
if (o !is StringHolder) return false
// Ok, smart cast to StringHolder
return s == o.s
}
}
Kotlin: from null dereference to smart casts 15
is or !is to check, as or as? to convert class StringHolder(val s: String) {
override fun equals(o: Any?): Boolean {
// Ok, unsafe (ClassCastException)
return s == (o as StringHolder).s
// Ok, safe
return s == (o as? StringHolder)?.s
}
}
Kotlin: from null dereference to smart casts 16
Top-down analysis on AST
For each relevant expression E, possible types of E: T(E) are determined at each AST node
T(null) = Nothing?
Example fun foo(s: String?) { // T(s) = {String?} = {String,Nothing?} if (s != null) { // T(s) = {Nothing?} println(s.length) } }
Kotlin: from null dereference to smart casts 17
For local values, function parameters, special this variable
For member or top-level values if: ◦ They are not overridable and have no custom getter AND
They are private or internal (not a part of public API) OR They are protected or public, and used in the same
compilation module when declared class My { private val a: String? // safe public val b: String? // same module only internal open val c: String? // unsafe private val d: String? // unsafe get() = null }
Kotlin: from null dereference to smart casts 18
For local variables if ◦ a smart cast is performed not in the loop which changes the
variable after the smart cast
fun foo() { var s: String? // T(s) = String U Nothing? s = "abc" // T(s) = String s.length // smart cast while (s != "x") { // Unsafe: changed later in the loop if (s.length > 0) s = s.substring(1) else s = null } }
Kotlin: from null dereference to smart casts 19
For local variables if ◦ a smart cast is performed in the same function when the
variable is declared, not inside some closure ◦ no closure that changes the variable exists before the
location of the smart cast
fun indexOfMax(a: IntArray): Int? { var maxI: Int? = null a.forEachIndexed { i, value -> if (maxI == null || value >= a[maxI]) { maxI = i } } return maxI }
Kotlin: from null dereference to smart casts 20
val / var x: Type : T(x) = Type
val / var x: Type? : T(x) = Type U Nothing?
Statement: Tin(x) Tout(x)
If: Tin(x) Ttrue(x), Tfalse(x)
Merge: Tin1(x), T in2(x) Tout(x)
Kotlin: from null dereference to
smart casts 21
if (x != null) or while (x != null) Ttrue (x) = T in(x) \ {Nothing?} Tfalse(x) = T in(x) & {Nothing?}
if (x is Something) Ttrue (x) = T in(x) & {Something} Tfalse(x) = T in(x) \ {Something}
if (x == y) Ttrue (x,y) = T in(x) & T in(y) Tfalse(x) = T in(x) \ T in(y) Tfalse(y) = T in(y) \ T in(x)
Kotlin: from null dereference to smart casts 22
if (A && B) True: TrueA & TrueB False: FalseA U FalseB
if (A || B) True: TrueA U TrueB False: FalseA & FalseB
Example: if (x == null && y != null) Ttrue (x) = T in(x) & {Nothing?} Ttrue (y) = T in(y) \ {Nothing?} Tfalse(x) = T in(x) Tfalse(y) = T in(y)
Kotlin: from null dereference to smart casts 23
x!! Tout(x) = T in(x) \ {Nothing?}
x as Something Tout(x) = T in(x) & {Something}
x?.foo(...) Tfoo(x) = T in(x) \ {Nothing?} Tout(x) = T in(x)
Kotlin: from null dereference to smart casts 24
if (…) { … } else { … } Out = In1 U In2
when (…) { … -> …; … -> …; else -> …;} Out = In1 U In2 U … U In(else)
Kotlin: from null dereference to smart casts 25
Assignment: x = y or initialization: var x = y ◦ Tout(x) = Tin(y)
Initialization with given type: var x: Type = y ◦ Tout(x) = { Type }
Before entering a loop ◦ For all X changed inside: T out(X) = Type(X) data class Node(val s: String, val next: Node?) fun foo(node: Node) { var current: Node? // T = {Node,Nothing?} current = node // T = {Node} while (true) { // T = {Node,Nothing?} println(current.s) // Error if (current == null) break println(current.s) // Ok: T = {Node} current = current.next // T = {Node,Nothing?} } // T = {Nothing?} }
Kotlin: from null dereference to smart casts 26
Classic data flow analysis on CFG ◦ Variable initialization analysis
◦ Variable usage analysis
◦ Code reachability analysis
◦ …
Kotlin: from null dereference to smart casts 27
Total smart casts: 8500 Unsafe operations ◦ !! : 1500 (mostly provoked by implicit conventions) ◦ as : 1500
Checks: ◦ != null : 2000 ◦ == null : 1000
Safe operations: ◦ ?. : 2500 ◦ as? : 1000 ◦ ?: : 2500
Kotlin: from null dereference to smart casts 28
More precise loop analysis
More precise closure analysis
Extra helper annotations like @Pure or @Consistent for mutable properties
…
Kotlin: from null dereference to smart casts 29
http://kotlinlang.org – language information, API reference, tutorials, koans, installation instructions, community, etc.
http://github.com/JetBrains/Kotlin – main compiler & plugin repository
http://blog.jetbrains.com/kotlin/ – Kotlin blog
Questions?
Kotlin: from null dereference to smart casts 30
Java \ Kotlin ◦ Checked exceptions ◦ Primitive types that are not classes ◦ Static members ◦ Non-private fields
Kotlin \ Java ◦ Extension functions ◦ Null-safety, smart casts ◦ String templates ◦ Properties ◦ Primary constructors ◦ Class delegation ◦ Type inference ◦ Singletons ◦ Operator overloading, infix functions ◦ …
Kotlin: from null dereference to smart casts 31