data class Something(val text: String?) {
val hasText get() = text != null
}
and then this code
val s = Something("something")
if (s.hasText) {
val text: String = s.text
}
Which will fail to compile because one can’t smart cast String? to String even though it is obvious (to a human) it could.
I wonder whether there is a way to help smart cast being smarter in cases like this. In comparison, C# has few attributes that help with static analysis (i.e. NotNullWhenAttribute).
Contracts can get you there, but it’s annoying to write and pollutes your types:
import kotlin.contracts.*
data class SomethingT<out Text: String?>(val text: Text)
typealias Something = SomethingT<*>
val Something.hasText get(): Boolean {
contract { returns(true) implies (this@hasText is SomethingT<String>) }
return text != null
}
fun main() {
val s: Something = Something("something")
if (s.hasText) {
val text: String = s.text
}
}
The trick is to move the variable-level information (e.g. the nullity of text) into a type parameter, so that it’s tracked on the level of Something itself and thus can be influenced by contracts on it.
Interesting, thanks. And I agree with you, it pollutes quite heavily. Also it’s not a proper solution, feels more like a hack. I mean what if one has a more complicated class…
It’s a simple sample. What about data class Result<T>(val valid: Boolean, val result: T?) or data class Person(val name: String?, val surname: String?) { val valid: Boolean get() = name != null && surname != null }.
Then there are functions, like string? GetTopLevelDomainFromFullUrl(string? url) where result is not null when url is not null etc.
I skimmed the article… it does seem like Kotlin’s contracts could replicate some of what C# is doing. I also think that Kotlin’s existing null-handling covers the majority of use cases, so I don’t think that this feature would pass the -100 points rule to warrant adding to the language. But maybe if you’ve got a really specific use case that you’ve experienced where you’ve gone “man there’s really no way to do this in Kotlin like there is in C#”, then I could be convinced.
I’d say there’s a big difference between “type casts by hand” and between having to extract properties manually, to then smart cast them normally.
I’m in agreeance with you that it’d be nice to have smart casts for such properties and be able to have contracts and such for them.
FWIW, I doubt that your original code exactly as written would work. Kotlin prefers functions and properties to be explicit about their guarantees, so something like a property implying something about another would likely be a contract that you’d write out somehow. This is already the case for something like:
val String?.hasNonEmptyText get() = this != null && isNotBlank()
which doesn’t provide a smart cast right now because instead you have to write:
val String?.hasNonEmptyText: Boolean get() {
contract { returns(true) implies (this@hasNonEmptyText != null) }
return this != null && isNotBlank()
}