4

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?

Sweeper
  • 213,210
  • 22
  • 193
  • 313

2 Answers2

2

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.

idmean
  • 14,540
  • 9
  • 54
  • 83
  • Why would the information of "3 elements" be lost? The compiler needs to translate that to machine code/whatever lower level language, right? How can it do that without knowing the number of elements? Or does it know that, but just doesn't make use of it here? – Sweeper May 09 '20 at 14:59
  • 1
    @Sweeper Semantic analysis only works with types. These are pretty much the same types you declare in code. I'll try to expand my answer on this. – idmean May 09 '20 at 15:01
1

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 {  
  • 2
    Ahh... I haven't thought about the "expanded form" of `for-in` at all! The expanded form involves checking `next() != nil` to decide when to stop iterating, doesn't it? And it doesn't give arrays special treatment - it treats arrays the same as any other `Sequence`! – Sweeper May 09 '20 at 16:39