How to solve: "Conditional branch result is implicitly cast to Any" in streamline-map

The following code produces “Conditional branch result is implicitly cast to Any” for both when-results.

class Value

sealed class Base
class A(val a: Int) : Base()
class B(val b: Value) : Base()

fun foo() {
    val contentList = listOf(A(1), B(Value()), A(3)).map {
        when (it) {
            is A -> it.a
            is B -> it.b
        }
    }
}

How am I supposed to solve this?
When I declare val y: List<Any> the warnings stay.
When I declare map<Base,Any> the warnings vanish, but the IDE complains about unnecessary “explicit type arguments”.

The type of the when expression is the most specific common type of the types of all of its clauses. Given the following clause types:

  • it.a: Int
  • it.b: Value

The most specific common type between these is Any. So that is why the type of contentList will be List<Any>, i.e. all items in the original list are mapped to an instance of Any.

What did you expect the type of contentList would be?

I want a List<Any>, thats totally ok. But I dont want the Warnings.

I tried explicit casting of it.a and it.b, but those casts were also flagged as unnecessary.

The compiler does not consider adding explicit type arguments or casts, to prevent other warnings, a reason for adding them. So in this case you will always end up with a warning.

I agree that that is annoying as I like my source to be completely free of warnings too. Hopefully somebody from JetBrains can explain whether this is as designed, or whether an issue should be created for it.

It seems to me that this is an IDE problem rather than a compiler problem.

When you don’t explicitly specify the type of the Map the compiler (quite reasonably IMO) warns you that the type of each branch is being cast to Any.

When you do specify the type of the Map, these warnings disappear and (if you use the command line compiler) there is no warning about “unnecessary explicit type arguments” either. The latter warning is only generated by the IDE.

I think the warning should dissappear when I declare val contentList: List<Any> and thus, its a compiler problem. Consider this simplified example:

class Value

sealed class Base
class A(val a: Int) : Base()
class B(val b: Value) : Base()

fun foo() {
    val a: Base = A(1)
    val x = when (a) {
        is A -> a.a
        is B -> a.b
    }
}

This also produces the “implicit cast” warning, but it can be solved by declaring val x: Any.

Furthermore, (IMHO) it is also the more common way, to declare explicit types of values instead of specifying types when calling functions.

Even with your revised example, I still don’t think it’s a compiler problem.

As written, the compiler still issues warnings in case you don’t realize that the type of ‘x’ is Any.

When you specify that the type of ‘x’ is Any, the compiler warnings disappear.

Exactly thats the point! You declare x: Any and the warnings disappear.
So I say the problem is, that you cannot declare contentList: List<Any> to make the warnings disappear in the first example. This would be the natural way IMHO.

If I compile the following using the command line compiler (and include an else clause in the when expression to get rid of the “expression must be exhaustive” error), then there are no warnings at all:

class Value

sealed class Base
class A(val a: Int) : Base()
class B(val b: Value) : Base()

fun foo() {
    val contentList = listOf<Any>(A(1), B(Value()), A(3)).map {
        when (it) {
            is A -> it.a
            is B -> it.b
            else -> {}
        }
    }
    println(contentList)
}

fun main(args: Array<String>) = foo()

So, if you’re still seeing warnings, it must be due to the IDE you’re using.

With your example I also dont get errors.
However, it is different to my first example:

If you remove the <Any> from listOf<Any> and then remove the else-Branch, you get my example again. The list is not of type Any, it is of type Base. So why should I declare it to be of type List<Any>?

The point is: you do not need the else-branch, because its a list of Base which is a sealed class with only A or B types.

Well, I was only responding to the point you raised about contentList being changed to a List<Any> in your original example.

It’s beside the point anyway because what we’re concerned with is the type of List which the map function returns, not the type of List to which this function is applied.

Where we seem to differ is that I think it’s reasonable for the compiler to issue warnings when you’re not explicit about the return type being Any but you don’t?

I probably was not clear enough, sorry. I hope the following will eliminate the doubts…

class Value

sealed class Base
class A(val a: Int) : Base()
class B(val b: Value) : Base()
val base: Base = A(1)

fun foo() {

    // raises warning "implicit cast to Any" --> OK
    val untypedVal = when (base) {
        is A -> base.a 
        is B -> base.b 
    }

    // no warning if result type is explicit --> OK
    val typedVal: Any = when (base) {
        is A -> base.a 
        is B -> base.b 
    }

    // raises warning "implicit cast to Any" --> OK
    val untypedList = listOf(A(1), B(Value()), A(3)).map {
        when (it) {
            is A -> it.a 
            is B -> it.b 
        }
    }

    // result type is explicit but still raises warning "implicit cast to Any" --> BUG?
    val typedList: List<Any> = listOf(A(1), B(Value()), A(3)).map {
        when (it) {
            is A -> it.a 
            is B -> it.b 
        }
    }
}

The last case is a bug IMHO.

Possible solution (not very nice):

val typedList: List<Any> = listOf(A(1), B(Value()), A(3)).map {
        val x: Any = when (it) {
            is A -> it.a
            is B -> it.b 
        }
        x
    }

Another solution (may be nicer but requires additional efforts): introduce abstract function inside Base which returns a in class A and b in class B, and use it.

Well, to solve the case I created issue https://youtrack.jetbrains.com/issue/KT-24458