What is the reason behind Smart Cast being impossible to perform when referenced class is in another module?


#21

@yole Ref discussion with @atomcat1978, perhaps one solution would be to generate the extra type information at compile-time, and embed it in the binary (f.ex. in the META-INF folder of the JAR).

This idea was inspired by how TypeScript libraries often ship type declarations in addition to executable JS.

That being said, I have little insight into the inner workings of the Kotlin compiler / JVM, so it’s hard for me to say if it’s workable, or even a good idea.


#22

I’m not sure how you expect that generating extra information would help. It’s already possible to detect that a property is implemented as a direct access to a final field. The problem is that you can change the implementation, and this would invalidate the assumptions made when compiling the dependent module. Or do you suggest that the compiled code should access the metadata at runtime and refuse to function if the metadata differs from what existed when the module was compiled?


#23

Actually, the new when syntax is going to help a lot in this sense:

// another module:::
data class ClassInOtherModule(val name: String?)

// module using the code:::
val instance = ClassInOtherModule(null)

when (val name = instance.name) {
    null -> println("")
    else -> println(name)
}

#24

Your example is equivalent to much shorter:

println(instance.name ?: "")


#25

It was an oversimplified example. Read the thread, and than you’ll see what I meant. Certainly I would not just write out the name property, but do some more stuff in a real life example.

when(val name = instance.name) {
   null -> doSomedefault()
   else -> callABuisnessMethod(name)
}

Previously it was not possible, you had to use the evil !! operator like this:

when(instance.name) {
   null -> doSomedefault()
   else -> callABuisnessMethod(instance.name!!) // since name is a public API property
}

You can solve this one with let as well, however, I think, the when version reads easier.

instance.name?.let {
   callABuisnessMethod(it)
} ?: doSomeDefault()

#26

This is caused by the poor language design decision to add val get(). Without val get() kotlin could have more strongly declared that val means immutable, now it only means “read-only”. val get()'s are not worth the trouble they cause. In my team we have a convention to always declare functions for non-immutable data. It is a bug in the language in my opinion. I would support a breaking change to get rid of val get()s.


#27

How exactly do you propose to change val to work? It has to be compatible with classes written in java. There is no way of ensuring immutability when working with third party libraries. You would need to drop java interop at least to make this change work.

Also it’s quite a common practise to have a public getter with private/protected setters. Maybe you don’t do that in your team, but I don’t see why kotlins property syntax should not be able to support this and I would most certainly not consider this a bug in the language.

If you propose to add a new keyword for immutable fields, I would like that but maybe that should be a different topic here (as it has nothing to do with the original question).


#28

I would propose to get rid of all the magic around property access and getter functions. A getter function would have no magic, just like java. Property access would just be naked access to the field. Same with property delegates, I don’t understand what we gain by complicating properties instead of just using functions.


#29

As you write yourself, you can already decide to declare functions to access private properties. By removing the properties feature from the language, we can’t gain immutability as you claim it.


#30

If val would mean the same as (public) final <type> in Java, the reference would be immutable and smartcasts could work. I’m with @miguelvargas here. Of course, implementing fields using getters is good style and for that, smartcasts wouldn’t work.


#31

That’s not the reason why it does not work. In fact, smart casts on val properties DO work. But only if the accessed property is in the same module as the accessing code.


#32

That is exactly the reason why they don’t work. And in the local module the smartcasts only work because there Kotlin can be sure that you’re not implementing them using get(). In other modules, kotlin can’t be sure about that because replacing a simple val with a custom getter isn’t a breaking change.


#33

More importantly, changes in classes can be made even at runtime (using classloaders). Even the assumption that within the module the class is stable somewhat exposes the system to ABI change related bugs.


#34

Or Hibernate-like bytecode manipulation.


#35

Thanks. I use the first when version on my JPA repository method.

It is more readable to check null.