Paulo Buchsbaum
Paulo Buchsbaum

Reputation: 2649

Why Kotlin compiler gives an error in a valid IF statement?

It started to appear for me a very weird compiler error in Kotlin

So I have reduced my code to the simplest form. The code doesn't make sense, but it is much easier to try to understand where the error comes from.

I've posted my code in Kotlin Playground. So I don't depend on any local configuration of my computer and the error can be reproducible instantly anywhere for anyone.

My complete code

data class Pilha(
    var funs: ArrayList<Int> 
          = arrayListOf<Int>()  
)

var AP: Array<Pilha> = Array<Pilha>(5) { Pilha() }
fun main() {
    var ele: String=""
    var cl: String = ""
    when (ele) {
        "soul" -> {
            with(AP[0]) {
                when {
                    (cl == "%") -> {
                       if (funs[0]==1)   // <= error in this line
                            cl = "a"
                    }

                    cl == "xU" -> {
           // If I comment this line, the error disappears
                        funs.add(2) 
                    }
                    else -> { cl="b" } // else of inner when
                } // when
            } // with

        } // "soul"
    else->{ cl="c"}  // else of outer when
    } // when
    println("ok")
}   // main

The error message

if must have both main and 'else' branches if used as an expression

This error gives in this line

if (funs[0]==1)

If I comment this line, the error disappears

funs.add(2) 

it is a big mistery because it's obvious that is a valid if flow control statement. I'm not using if as an expression.

Obviously putting a dead else (else {}) on this if also solve the error, but don't explain it.

Kotlin Playground:

Kotlin Playground

Update: As Pawel has pointed out to me, it's a question of type return conflict, linked to with statement. As I am a classic Pascal programmer, I was using with, how Pascal users did it.

For this purpose, it is best, like Pawel said, using object.apply {}. So one can, as in the case of with, use the properties and methods of the object directly without the accessor dot inside brackets ({}).

Upvotes: 1

Views: 1371

Answers (1)

Pawel
Pawel

Reputation: 17248

You're using with(AP[0]) { ... } function with lambda signature of T.() -> R. It REQUIRES a return value from within the lambda, and last statement inside it is when block so it is used as an expression to determine return value.

Error is caused because in case of cl == % and funs[0]!=1 it's not possible to determine value to return.

Fix it by replacing your with with a scoping function that does not need return value, for example AP[0].apply { ... } with signature of T.() -> Unit.

Edit: why commenting out funs.add(2) "fixes" the error?

It's because that call returns Boolean value which changes signature of entire when block:

// this when block can be used as an expression because it returns value of type Any
when {
    (cl == "%") -> if (funs[0]==1)  cl = "a"  // if true returns Unit, else undefined
    cl == "xU" ->  { funs.add(2) }    // returns Boolean
    else ->  cl="b"        // does not return value (Unit)
}

Now when you comment out funs.add(2) or change second case to {funs.add(2) ; Unit } this makes return type of entire when block a Unit, which means it's no longer used as an expression and returning value is not required.

// this when block cannot be used as an expression because it always returns Unit (void)
when {
    (cl == "%") -> if (funs[0]==1)  cl = "a"  // does not return value (Unit)
    cl == "xU" ->  { funs.add(2); Unit }    // ignore add return value by returning Unit
    else ->  cl="b"        // does not return value (Unit)
}

Upvotes: 2

Related Questions