Smart cast and overridden properties

Smart cast seems not to work in situation like this one:

override var mesh:MeshDisplay? = null
    set(value) {
        super.mesh = value

        if(field == null) {
            texture = null
        }
        else {
            texture = field!!.textures[0] // can't assume 'field' is not null, but NPE is impossible here, isn't it?
        }
    }

It looks like it treats field the same way as calling super.mesh, but shouldn’t field allow for direct backing variable access?

It’s not impossible for the field to be null in the else branch if this is called concurrently.

I think the easiest fix in this case would be

super.mesh = value
texture = field?.textures[0]

This way, if field is null texture is null, otherwise you get texture[0]. You’re right though - if you aren’t used to thinking about concurrency issues (I’m not), it’s confusing.

Actually though, I would’ve thought that a separate thread calling it wouldn’t change what field refers to; it’d be a separate execution “instance”, wouldn’t it?

Actually, ignore the last part of my comment - for some reason I was thinking of field as a parameter (i.e. value instead). Field can definitely get changed by concurrent threads.

(I’m leaving my previous comment instead of editing to remind me to pay a little more attention :slight_smile:)

That’s my point exactly :slight_smile: but compiler does not know that, somehow. I wonder if this is a bug or am I just missing something here? If field was a dynamic property (which it is not, is it?) there would be a possibility that the returned value changes during the get() call (if(field == null)), but I think it’s supposed to be just a field, as the name and docs suggest.

Hmm, I don’t think that creating a thread safe code should be relevant in this case (if I wanted this method/property to be thread safe, I’d add @Synchronized). I think that what the compiler assumes is that the property can change during its accessor call (which is true, but I’m not accessing the property this way here).

edit: @mplatvoet, I somehow missed the “not” in your previous post :slight_smile:

I think I’m missing you’re point then, ergo I don’t understand what you’re saying.

I thought you where saying that in your previous example it was impossible for the value of field in the else branch to be null. That’s the part I reacted to, it can be null if this code is called concurrently. Therefor the compiler can’t/shouldn’t make assumptions about the state of the field.

Hmm, I don’t think that creating a thread safe code should be relevant in this case (if I wanted this method/property to be thread safe, I’d add @Synchronized).

If you make it (provable) threadsafe maybe the compiler could avoid the second null check, though it’s not trivial prove that statically. Even in this case only adding @Synhronized to this overridden property can’t guarantee threadsafety because the super property should be guarded too with the same lock.

A reasonable sound approach for these cases is introducing a local val like so:

override var mesh:MeshDisplay? = null
    set(value) {
        super.mesh = value

        val current = field
        if(current == null) {
            texture = null
        }
        else {
            texture = current.textures[0]
        }
    }

Note that this is just an example to satisfy the compiler, not threadsafety.

This isn’t supposed to be thread-safe :slight_smile: (and I doubt it’s what the compiler complains about) - it’s about smart casts and that they for some reason don’t work in this scenario. The compiler says it’s not guaranteed field is not null in the else-branch, because it’s a dynamic property that could’ve changed. But it couldn’t!

You’re mentioning concurrency, but does the compiler assume everything has to be thread-safe? I don’t think so (this makes very little sense, because, in general, all code doesn’t have to be thread-safe). And apart from that there are no other ways of changing field’s value - it can only be changed by directly assigning another value to it OR by super.get()/set() call - both of these things aren’t there.

The compiler doesn’t know you’re writing code that doesn’t need to be thread safe; it’s working on trying to ensure that your code is thread safe. All it knows is that your trying to dereference a mutable and nullable field, and it doesn’t like that. If you’re not concerned with thread safety (i.e. you know it’s always going to be non-null in that instance), you can force the dereference like you did with !!. An alternative would be to use the safe access operator - field?.textures[0] - which should do the trick and keep the compiler happy.

1 Like