All paths through this function will call itself

I started to support the new dark mode to an app. So I used to create UIColor extension.

import UIKit

extension UIColor {
    static var groupedBackground: UIColor {
         if #available(iOS 13, *) {
             return UIColor.systemGroupedBackground
         } else {
             return UIColor.groupedBackground
         }
    }
}

You may notice that I have a typo here that I am calling the same variable for iOS versions other than 13. Which will result in crash for less iOS 13 devices.

While swift compiler warns if I write like below.

extension UIColor {
    static var groupedBackground: UIColor {
         return UIColor.groupedBackground
    }
}

The Swift Compiler warning here is All paths through this function will call itself.

So why I am not getting the same warning for the conditional code above?

Probably because there's a path where the function does not call itself: the path where #available returns true. It'd nevertheless be useful to have a warning that says: "All paths through this function will call itself on iOS <13". That's more complicated to detect: paths would need to be checked for all the platform version ranges the function can have. Seems doable though.

4 Likes

tl;dr because nobody taught the compiler that you cannot update iOS while the app is running

There are two kinds of checks in swift, compile time and runtime.

Let's look at the compile time checks:

extension UIColor {
    static var groupedBackground: UIColor {
         #if FOO
             return UIColor.systemGroupedBackground
         #else
             return UIColor.groupedBackground
         #endif
    }
}

If FOO is false, then the second path will be hardcoded into the binary, and the first choice will be deleted, as if it never existed, and the compiler will warn you.

The other kind of checks is made at runtime, and compiler cannot guarantee that the condition won't change

extension UIColor {
    static var groupedBackground: UIColor {
         if foo {
            return UIColor.systemGroupedBackground
         } else {
            return UIColor.groupedBackground
         }
    }
}

System version checks must be done at runtime, because you can run the same binary on different operating system versions, or even update your linux machine without rebooting it.

I can imagine #available being made a special case in a compiler though, because it would be very weird for the OS version to change in the middle of running the app

3 Likes

cukr’s analysis sounds correct—the DiagnoseInfiniteRecursion pass doesn’t understand that #available(iOS 13, *) is effectively constant, and in fact I don’t think it understands at all that a branch on a constant will always go the same way.

Neil, this would make an excellent bug report.

7 Likes

Thanks @beccadax for reply. I have create the bug report here: SR-11842

1 Like

That report looks great—thank you!

2 Likes