Code path analysis with while loop

Consider the following:

let x: Int

while true {
  if Bool.random() {
    x = 42
    break
  } else {
    continue
  }
}

print(x) // ERROR: Constant 'x' used before being initialized

Shouldn't path analysis understand that, in order to get to print(x), x must be initialized?

1 Like

Well, “should” is always a tricky word. I recommend that you file a bug about this. There may be good technical reasons for why this isn’t possible, but it doesn’t hurt to ask (-:

Please post your bug number, just for the record.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like

The weird thing is that it works if you implement the while loop manually:

do {
    let x: Int

    loop: do {
        if Bool.random() {
            x = 42
            break loop
        } else {
            continue loop
        }
    }

    print(x)
}
1 Like

I'm surprised you can use do { … } to write a loop in this way.

If I'm reading The Swift Programming Language correctly, continue is only meant to work in loops (i.e. for in, while, and repeat { … } while. And break is meant to work in loops and if and switch statements.

The key to the above is that the do, break, and continue statement are all labeled; the relevant portion of the book to reference is for Labeled Statements:

You can prefix a loop statement, an if statement, a switch statement, or a do statement with a statement label , which consists of the name of the label followed immediately by a colon (:). Use statement labels with break and continue statements to be explicit about how you want to change control flow in a loop statement or a switch statement, as discussed in Break Statement and Continue Statement below.

From the linked continue section:

When a continue statement is followed by the name of a statement label, it ends program execution of the current iteration of the loop statement named by that label.

When a continue statement isn’t followed by the name of a statement label, it ends program execution of the current iteration of the innermost enclosing loop statement in which it occurs.

The same goes for break.

It's not terribly common, but totally possible to express!

3 Likes

See, I read that wording as excluding the construction above. The book says you can label a "loop statement, an if statement, a switch statement, or a do statement," but the continue section only permits continuing a "loop statement named by that label."

OTOH, the break section explicitly allows other labeled statement types:

When a break statement is followed by the name of a statement label, it ends program execution of the loop, if statement, or switch statement named by that label.

(though, notably, do statements are not mentioned here).

Perhaps this is a documentation bug rather than an implementation bug, but I think the wording here is not at all an endorsement of the behavior above!

I would say that this is implicit and can stand to be written out more explicitly in the book, rather than an implementation bug. The language grammar as documented unambiguously supports this (and, well, the language itself supports it with an implementation), but I think it should be called out more clearly.

The grammar allows loop-statements, if-statements, switch-statements, and do-statements to be labeled with a label-name. The only places a label-name may be referenced is in that statement-label, in break-statements, and in continue-statements:

Grammar of a labeled statement

labeled-statementstatement-label loop-statement
labeled-statementstatement-label if-statement
labeled-statementstatement-label switch-statement
labeled-statementstatement-label do-statement

statement-labellabel-name :
label-nameidentifier

Grammar of a break statement

break-statementbreak label-name opt

Grammar of a continue statement

continue-statementcontinue label-name opt

(The Summary of the Grammar has the full language grammar in one place for searching.)

Since the only thing you can do with a label is break from it or continue to it, there'd really be no point in being allowed to label a do-statement otherwise, if this construction were not allowed.

5 Likes

Feels like goto to me, I'd remove labels altogether (although I might be biased as I haven't used them once).

Re the original question - I believe escape analysis could be better, and it's not the end of the world that it doesn't currently catch this situation as the workaround is trivial.

It's largely a leftover from when repeat { ... } while loops were spelled do { ... } while.

2 Likes

I think I got it, and it's, probably, technically not a bug. The key is that the compiler doesn't seem to take into account some values into the code path analysis, and probably that's the intended behavior.

For example this doesn't compile

let y: Int

if true {
  y = 42
}

print(y)

even if y will surely be initialized when used. The fact that I wrote if true is not taken into account by the compiler. There still seems to be something weird going on because for example in this code

let y: Int

if true {
  y = 42
} else {
  y = 43 // WARNING: Will never be executed
}

print(y)

a warning is emitted because it's impossible to enter the else branch based on the true value.

Anyway, that seems to be the reason. In fact, both in

while true {
  if Bool.random() {
    x = 42
    break
  } else {
    continue
  }
}

and

repeat {
  if Bool.random() {
    x = 42
    break
  } else {
    continue
  }
} while true

the only guarantee that the block will loop until x is initialized is in the true value passed to the while declaration.

As mentioned by @Nobody1707, it works if you manually implement the loop with a labeled do block:

let x: Int

loop: do {
  if Bool.random() {
    x = 42
    break loop
  } else {
    continue loop
  }
}

print(x)

and that's because the loop is guaranteed by very code structure, not due to a specific true value.

2 Likes