Eitanos30
Eitanos30

Reputation: 1439

Why as? doesn't cast the variable inside the if block?

I wrote the folowing code:

 class MyRoamable
{
    var r: Roamable = Wolf()
    fun myFunction()
    {
        var x = r as? Wolf
        if (r as? Wolf != null)
        {
            r.wolfNoise()
        }
    }
}

open class Roamable
{
    fun roam():String
    {
        return "awhooooooo"
    }
}

class Wolf:Roamable()
{
    fun wolfNoise():String
    {
        return "wolf Noise"
    }
}





I can't understand why the following doesn't work:

if (r as? Wolf != null) { r.wolfNoise() }

I'm sure that i made such a cast in the past. The error is: Unresolved reference: wolfNoise

Why it's continue to treat r as a Roamable and not as a Wolf

The way it worked for me in the past:

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    if (holder as? ViewHolder != null) {
        
        holder.homeTeamTextView.text = homeTeam
        holder.awayTeamTextView.text = awayTeam
        holder.homeTeamTextView.setTypeface(null,Typeface.NORMAL)
        holder.awayTeamTextView.setTypeface(null,Typeface.NORMAL)
        if (holder.winDescription != null)
        {
            holder.winDescription.text = gameWindowDescription
        }
        else
        {
            holder.winDescription.text = ""
        }


  class ViewHolder (view : View, onRecyclerItemClickListener: OnRecyclerItemClickListener?) : RecyclerView.ViewHolder(view)
{
    val homeTeamTextView : TextView = view.findViewById(R.id.home_club_tv)
    val awayTeamTextView : TextView = view.findViewById(R.id.away_club_tv)
    val homeTeamImageView : ImageView = view.findViewById(R.id.home_club_iv)
    val awayTeamImageView : ImageView = view.findViewById(R.id.away_club_iv)
    val timeTextView : TextView = view.findViewById(R.id.games_status_tv)
    val scoreTextView : TextView = view.findViewById(R.id.score_tv)
    val winDescription: TextView = view.findViewById(R.id.game_window_description)

i could get all the data from the views since if (holder as? ViewHolder != null) succeeded

Upvotes: 0

Views: 223

Answers (3)

Alexey Romanov
Alexey Romanov

Reputation: 170723

Smart casts don't just happen, every pattern has to be explicitly supported in the compiler. If you make r a local variable

fun myFunction() {
    var r: Roamable = Wolf()
    if (r as? Wolf != null) {
        r.wolfNoise()
    }
}

or a val

val r: Roamable = Wolf()
fun myFunction() {
    if (r as? Wolf != null) {
        r.wolfNoise()
    }
}

it works.

Parameters are treated like val local variables in this respect, so the override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) example is actually both.

In your case, if an instance of Roamable was visible to two threads, and one of them executed myFunction, the other one could change r after r as? Wolf was evaluated and before r.wolfNoise() was.

You could say: but this program doesn't have any threads! But the compiler has to compile Roamable so it can be used in other programs as well.

EDIT: I somehow misread your example and thought you had

var r: Roamable = Wolf()
fun myFunction() {
    var x = r as? Wolf
    if (r as? Wolf != null) {
        r.wolfNoise()
    }
}

inside a function, not a class. The below explanation would apply in that case.

If you use a local variable (not a property!) var from outer scope in a local function, as in

fun outer() {
    var r: Roamable = Wolf()
    fun myFunction() {
        var x = r as? Wolf
        if (r as? Wolf != null) {
            r.wolfNoise()
        }
    }
}

the compiler has to turn it into something like

private class RoamableHolder(var value: Roamable)

private fun outer_myFunction(holder: RoamableHolder) {
    if (holder.value as? Wolf != null) {
        holder.value.wolfNoise()
    }
}

fun outer() {
    val r_holder = RoamableHolder(Wolf())
    outer_myFunction(r_holder)
}

behind the scenes, because JVM doesn't have local methods. You can see how it would allow myFunction to change the value of r and make this visible afterwards in outer.

The smart cast wouldn't work with that code, because you can't smart-cast var properties, so it doesn't work with the original code either.

But if r in outer was a val instead of var, there's no need for RoamableHolder and the translation is simpler:

private fun outer_myFunction(r: Roamable) {
    if (r as? Wolf != null) {
        r.wolfNoise()
    }
}

fun outer() {
    val r: Roamable = Wolf()
    outer_myFunction(r)
}

Upvotes: 4

Tenfour04
Tenfour04

Reputation: 93581

In your Roamable example, r is a property. A var property can never be smart-cast because the compiler can't guarantee the property will return the same value in between when you type-check it and when you try to do something with it (call its functions, pass it as a parameter, assign it to a variable, etc.). This is also true of val properties with custom getters.

Now maybe you aren't using this class in a multi-threaded way so you know the cast would be safe, but the compiler isn't smart enough to check for that, so you have to manually cast. I think the reason for this compiler rule is that it is too complicated to check. Maybe it's feasible, but some compiler limitations were put in place so compiling will be fast, not because of logical impossibility.

In the ViewHolder example, holder is a function parameter, so it is not possible for some other thread to modify it. In Kotlin, unlike Java, function parameters are constants so it's not possible to modify them at all. So smart-casts are always possible with function parameters.

You can also smart-cast with local var variables, since the compiler can see in the local code that the variable hasn't been reassigned in between your check and your use of the value. If you use the local variable in a closure, then it is no longer smart-castable, because now it's kind of like a property: it can be modified from somewhere else, so the compiler can no longer make guarantees about its type.

Upvotes: 1

Andrei Tanana
Andrei Tanana

Reputation: 8422

Too complex for the Kotlin's compiler :) I think it will be more idiomatic to write like this:

fun myFunction() {
    val x = r as? Wolf
    x?.wolfNoise()
}

In your example with view holder you don't cast variable. You just check it for null

Upvotes: 0

Related Questions