Since Xcode Version 13.3 beta 2 (13E5095k), I'm getting this warning:
ForEach(0..<someInt) { index in // ⚠️ Non-constant range: not an integer range
...
}
I think it's only starting to show this warning with this latest Xcode beta. Looking at the header "source", the type is just Rang<Int>, seem it should just accept any Range<Int>, but it's now showing this warning.
How does this warning raise?
extension ForEach where Data == Range<Int>, ID == Int, Content : View {
/// Creates an instance that computes views on demand over a given constant
/// range.
///
/// The instance only reads the initial value of the provided `data` and
/// doesn't need to identify views across updates. To compute views on
/// demand over a dynamic range, use ``ForEach/init(_:id:content:)``.
///
/// - Parameters:
/// - data: A constant range.
/// - content: The view builder that creates views dynamically.
public init(_ data: Range<Int>, @ViewBuilder content: @escaping (Int) -> Content)
}
That version of ForEach.init has the undocumented annotation @_semantics("swiftui.requires_constant_range"). Xcode doesn't show it in the “generated interface”, but you can find it in the .swiftinterface file.
A let variable isn't a compile time constant. Here is a great explanation (it takes global variable as an example):
Global variables are semantically always lazily initialized: every access does an atomic operation (implemented with something like pthread_once ) to check if the global is initialized. This is silly for something like let foobarCount = 42 . To offset this, the Swift optimizer has heuristics for promoting initializers to constants. These heuristics are currently very simple and they are also opaque to the developer. With constant expressions, we could chose to provide a model like @constexpr let foobarCount = 42 which reliably folds to a constant or produces an error if the initializer is not a constant expression. This would also allow later uses of foobarCount to be treated as a compile-time constant.
Even though a let may not be the same as a literal, its value can never change so it's constant, so the range created from either a let or int literal when pass into ForEach.init(_:) are both the same constant range. So I think @_semantics("swiftui.requires_constant_range") should not issue warning either ways.
The compiler is unable to determine at the relevant stage of compilation that Range.init doesn’t have side effects and gives the same result each time it’s called. It’s just an ordinary initializer.
There’s no reason to insist on writing Range(1...130) because you and I both know it’s 1..<131, even if the compiler isn’t sure.