Is there *any* situation under which "for _ in [1,2,3]" will not loop at all?

Viewed 80

I was writing some code and made a mistake that simplifies to:

func f() -> Int {
    for _ in [1,2,3] {
        return 1
    }
}

And the compiler shows me an error saying that f is missing an return, which caused me to realise my mistake. I forgot to put an if statement around the return!

But then I realised that the compiler is actually lying! The function will always return a value. Or will it? Is there any situation under which the for loop will not loop?

I'm asking this because other tautological constructs compiles fine:

if 1 < 2 {
    return 1
}

while true {
    return 1
}

And I also understand that the compiler can't evaluate every expression at compile time to see if they are tautologies. I know properties accesses and method calls usually don't get evaluated at compile time, so this is not expected to compile:

if "".isEmpty {
    return 1
}

But generally literals are ok, right? After all, the compiler has to evaluate the literal [1,2,3] to translate it into machine code that says "create an array with 1, 2, 3".

So why is it not smart enough to figure out the for loop? Will the for loop not get run in some rare situation?

2 Answers

While for a human it is trivial to see that the loop will always repeat three times, because the list literal is a constant with three elements, this is a non-trivial thing to see for a compiler at the level of semantic analysis.

During semantic analysis, the compiler will evaluate "a generic list literal" ([1,2,3]) and determine that it is an expression of type Array<Int>. But now, of course, the information that this is a constant or that this array contains three elements is lost.

Semantic analysis is normally performed using the same (or a very similar) type system the programmer is using. Since there would be little benefit from attaching the number of elements in an array to the type (the number of elements is normally not known at compile time) compared to the cost, this is normally not done. On the other hand, constant folding (if 1 < 2 {) is easier to implement and occurs more often.

While on a lower-level the compiler will likely unroll this loop and use constant values, this happens much later – in Swift after generating the Swift Intermediate Language representation – and probably only just during code generation – after SIL has been emitted to LLVM IR and when LLVM optimizations are run.

for-in doesn't know if all it's going to get from an iterator is nil. You'll get the same error message, Missing return in a function expected to return 'Int', no matter what the sequence is.

extension Bool: Sequence, IteratorProtocol {
  public func next() -> Void? { () }
}
for _ in true {  
Related