Yes, I know this is probably what is happening internally. Still, I’m not convinced it should behave like this. I guess for most developers val foo: String = "foo"
is just a short way of declaring and initializing a variable and they would be really surprised if I told them this one-liner is not exactly the same as its two-lines alternative.
Besides, I think it is almost never beneficial to not smart cast - it could/should be performed whenever possible.
Yes, they are. In fact, smart casts for nullability and for subtypes are exactly the same, because nullability in Kotlin is represented by the type system. String
is a subtype of String?
, so smart casting of String?
to String
is very similar to smart casting S
to A
.
But I don’t see why this is a problem. We know how to merge two types, we do this all the time both in Kotlin and Java. We just need to find a closest common supertype. Kotlin does this e.g. for type inference. Naive implementation could just merge A
+ A
into A
and any other combination would disable smart casting.
I ran some additional tests for types merging and results are even more surprising, at least to me 
fun test1(cond: Boolean) {
var any: Any
var a: A
any = if (cond) C() else D()
any.b // ok, Any smart casted to B
a = if (cond) C() else D()
a.b // error, just A
val b = if (cond) C() else D()
a = b
a.b // ok, A smart casted to B
}
open class A
open class B : A() {
val b = "b"
}
open class C : B()
open class D : B()
There are two conclusions. First, regular type inference uses more advanced techniques of inferring the type than smart casts. Assigning a value to intermediate variable allowed to guess the type more accurately than when assigning it directly. Regarding the first and second example - I have no idea, it doesn’t make any sense to me that Any
was smart casted to B
, but A
wasn’t.
Regarding do ... while
and loops in general I think the problem may be different. If we would like to infer types at the beginning of the loop by utilizing type information at the end of the loop, then we would get into two-way dependency problem. This is definitely solvable, but it is not as trivial as type merging in simple if ... else
case.