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


#1

I’m not nitpicking, I would just like to understand the reasoning behind this. Imagine the following code:

public class JavaFoo {
    @Nullable
    public final String foo;
    ...
}

And, in another Kotlin file:

val j = JavaFoo(null)
if (j.foo != null) {
  val foo: String = j.foo
}

This compiles okay and IDE is happy. Yet, if I move the JavaFoo class to another jar file and depend on it, suddenly IDE shows an error that Smart Cast is impossible, because j.foo is a public API property declared in different module (this is on up-to-date IDEA 2016.3.2 with newest Kotlin plugin 1.0.6). There is a bug because Gradle Kotlin cmdline compiles this flawlessly, but that’s not currently the point.

My question is what’s the reasoning behind this error appearing when the class is moved to a different jar? Is the IDE plugin worried that the class in the different jar may be changed?


Smart cast from different module compiles in Eclipse
#2

A smart cast is only valid when multiple accesses of the same property are guaranteed to return the same value. If the property being accessed is defined in a different module from the access location, the module containing the property can be recompiled separately from the module where it’s accessed, breaking the key requirement of the smart cast. Therefore, cross-module smart casts are not allowed.


#3

Understood, thank you for explaining. In that case the CLI kotlin compiler should complain as well - currently it actually does the opposite and prints a warning regarding unnecessary !! operator. Should I open a bug in this regard?


#4

Created a bug report: https://youtrack.jetbrains.com/issue/KT-15558


#5

It is such a common use case to put your Data classes in a separate module/project/library that it feels like we should find some way to allow this.

I understand the general case not being safe, but maybe a compiler directive saying “I know what I’m doing, I’m not going to change the contract on my self” or… I don’t know, but smart casting is so essential to being able to write nice clean code this seems like a use case there should be some way of supporting.


#6

what about for null smart casts?

if i do something like:

    if(objectInOtherModule.something != null)
        functionThatDoesntTakeNulls(objectInOtherModule.something)

Even if the other module is recompiled and the type is changed, it will still not be null when the second line executes. I shouldnt have to add !!


#7

The problem is not that the type is changed; the problem is that the property implementation can be changed so that it does not return the same value on every invocation:

class ObjectInOtherModule {
    val something get() = if (Math.random() < 0.5) "" else null
}