"Swift 4.1 or Swift 3.3"

Hi, all. Swift 4.1 is off on its own branch and going well, but we never quite came up with an answer for a particular problem developers might have: "am I running a Swift 4.1 compiler?".

#if swift(>=3.2)
// Swift 3.2 (4.0 in compatibility mode)
// Swift 3.3 (4.1 in compatibility mode)
// Swift 4.0
// Swift 4.1
#endif

#if swift(>=3.3)
// Swift 3.3 (4.1 compatibily mode)
// Swift 4.0
// Swift 4.1
// this one is probably not very useful
#endif

#if swift(>=4.0)
// Swift 4.0
// Swift 4.1
#endif

#if ???
// Swift 3.3
// Swift 4.1
#endif

I don't think this is going to come up a lot, but given that we do have changes to the standard library and to the language, I can see people wanting it. Right now the only way to do it is the rather unwieldy:

#if swift(>=4.1) || (swift(>=3.3) && !swift(>=4.0))
print("new")
#else
print("old")
#endif

Do we need something better here, or do you think people will be okay with this? I'm realizing I don't really know how many people try to keep their libraries working across Swift versions and run into compatibility issues.

(Strictly speaking this problem is already present with Swift 4.0.2 with 3.2.2 compatibility mode, but that's much less likely to come up.)

Jordan

Hey Jordan,

We tend to have to do major migrations of our Swift codebase, so I might be able to help. For context at last count (February 2017 was the last time I checked) we had 1.4m lines of Swift across our apps.

This means that when we migrate, we tend to have to chose the smallest possible scope for necessity reasons. For example when Swift 3.0 was available, we went to 2.3 first and it took a few months before we were on 3.0.

When 4.0 was available we went to 3.2 first (with many, many #if statements) and it took a few months before we went to 4.0; which we did by landing many months worth of #if statements to switch between 3.2/4.0 right up until the 4.0 switch.

To answer the last question first, the larger the codebase the more likely you will be to want to do your swift migrations piecemeal with many small changes until you can build a fully–compatible–with–the–new–version executable.

This is even more important for open source libraries, who struggle with either having entire forked branches for swift versions (which means fixes sometimes have to be committed to more than one branch), or many #if statements littered in their codebase.

As for this issue, the main improvement here would be to be able to use > and < in these statements. The first time I had to do one of these large migrations I found it more difficult to reason and read the cases where you had to do this:

#if !swift(>=3.2)
/* code */
#endif

…when it would be a little easier to grok what’s happening if it were possible to do this:

#if swift(<3.2)
/* code */
#endif

In your example, it could be a little simpler if the unary requirement here was relaxed (though I’m sure there’s a good reason why it’s the way it is!).

If I’m reading this correctly (sorry if my interval notation is wrong it’s been a while) the bounds here would be:

        // n >= 4.1
        // or
        // 3.3 >= n < 4.0
        #if swift(>=4.1) || (swift(>=3.3) && !swift(>=4.0))
        print("asdf")
        #endif

But if it were possible to use > and <, it would read:

        #if (swift(>=3.3) && swift(<4.0)) || swift(>=4.1)
        print("asdf")
        #endif

(I don’t know why Mail.app is making the < above look like a less than or equal to symbol ≤)

I find it a little easier to read it this way, because the negation with ! changes my thinking from ‘interval’ to ‘interval plus… not something in this interval’.

Overall, while I think that most people here know that these compatibility versions are really ‘the old code built with the latest compiler’, most tend to ignore this fact so we try not to worry about it much and trust the compiler a lot here to do the right thing. We also try to only support one Xcode version at Uber at a time (and so one swift compiler toolchain), which tends to narrow the scope of the ‘what version of Swift will this code compile for’ question.

Hope this helps!

Alan

···

On Jan 5, 2018, at 4:19 PM, Jordan Rose via swift-build-dev <swift-build-dev@swift.org> wrote:

Hi, all. Swift 4.1 is off on its own branch and going well, but we never quite came up with an answer for a particular problem developers might have: "am I running a Swift 4.1 compiler?".

#if swift(>=3.2)
// Swift 3.2 (4.0 in compatibility mode)
// Swift 3.3 (4.1 in compatibility mode)
// Swift 4.0
// Swift 4.1
#endif

#if swift(>=3.3)
// Swift 3.3 (4.1 compatibily mode)
// Swift 4.0
// Swift 4.1
// this one is probably not very useful
#endif

#if swift(>=4.0)
// Swift 4.0
// Swift 4.1
#endif

#if ???
// Swift 3.3
// Swift 4.1
#endif

I don't think this is going to come up a lot, but given that we do have changes to the standard library and to the language, I can see people wanting it. Right now the only way to do it is the rather unwieldy:

#if swift(>=4.1) || (swift(>=3.3) && !swift(>=4.0))
print("new")
#else
print("old")
#endif

Do we need something better here, or do you think people will be okay with this? I'm realizing I don't really know how many people try to keep their libraries working across Swift versions and run into compatibility issues.

(Strictly speaking this problem is already present with Swift 4.0.2 with 3.2.2 compatibility mode, but that's much less likely to come up.)

Jordan
_______________________________________________
swift-build-dev mailing list
swift-build-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-build-dev

I was really surprised when I saw that the release of 4.0 introduced this 3.2 version to mean “the 4.0 compiler running in 3.1 compatibility mode”. So of course, I’m even more surprised to see a new 3.3 version. I find it very counter-intuitive. It also means we will continue to have to increment all previous Swift language versions from now on whenever a new compiler is released:

Swift 3.4 = Swift 5 compiler in Swift 3 compatibility mode
Swift 4.2 = Swift 5 compiler in Swift 4 compatibility mode
Swift 3.5 = Swift 5.1 compiler in Swift 3 compatibility mode
Swift 4.3 = Swift 5.1 compiler in Swift 4 compatibility mode

I have the impression that what we really need is a different directive to test for the compiler version:

#if compiler(>=4.1)
// Swift 3.3
// Swift 4.1
#endif

···

On 6 Jan 2018, at 01:19, Jordan Rose via swift-build-dev <swift-build-dev@swift.org> wrote:

Hi, all. Swift 4.1 is off on its own branch and going well, but we never quite came up with an answer for a particular problem developers might have: "am I running a Swift 4.1 compiler?".

#if swift(>=3.2)
// Swift 3.2 (4.0 in compatibility mode)
// Swift 3.3 (4.1 in compatibility mode)
// Swift 4.0
// Swift 4.1
#endif

#if swift(>=3.3)
// Swift 3.3 (4.1 compatibily mode)
// Swift 4.0
// Swift 4.1
// this one is probably not very useful
#endif

#if swift(>=4.0)
// Swift 4.0
// Swift 4.1
#endif

#if ???
// Swift 3.3
// Swift 4.1
#endif

I don't think this is going to come up a lot, but given that we do have changes to the standard library and to the language, I can see people wanting it. Right now the only way to do it is the rather unwieldy:

#if swift(>=4.1) || (swift(>=3.3) && !swift(>=4.0))
print("new")
#else
print("old")
#endif

Do we need something better here, or do you think people will be okay with this? I'm realizing I don't really know how many people try to keep their libraries working across Swift versions and run into compatibility issues.

(Strictly speaking this problem is already present with Swift 4.0.2 with 3.2.2 compatibility mode, but that's much less likely to come up.)

Jordan
_______________________________________________
swift-build-dev mailing list
swift-build-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-build-dev

Hi, all. Swift 4.1 is off on its own branch and going well, but we never quite came up with an answer for a particular problem developers might have: "am I running a Swift 4.1 compiler?”.

I agree, this is getting bad. Ted mentioned that something like __has_feature in clang is probably the best way to go, so people could check the specific thing they care about, instead of a set of global version numbers.

Another thing that could help is something along the lines of:

  #if swift_stdlib(>=5.0)

which would presumably be active in any language mode when the 5.0 standard library is available. It would be even better to use a generalized availability system for this: the standard library version could be treated just like Foundation versions are handled, for example.

-Chris

···

On Jan 5, 2018, at 4:19 PM, Jordan Rose via swift-dev <swift-dev@swift.org> wrote:

#if swift(>=3.2)
// Swift 3.2 (4.0 in compatibility mode)
// Swift 3.3 (4.1 in compatibility mode)
// Swift 4.0
// Swift 4.1
#endif

#if swift(>=3.3)
// Swift 3.3 (4.1 compatibily mode)
// Swift 4.0
// Swift 4.1
// this one is probably not very useful
#endif

#if swift(>=4.0)
// Swift 4.0
// Swift 4.1
#endif

#if ???
// Swift 3.3
// Swift 4.1
#endif

I don't think this is going to come up a lot, but given that we do have changes to the standard library and to the language, I can see people wanting it. Right now the only way to do it is the rather unwieldy:

#if swift(>=4.1) || (swift(>=3.3) && !swift(>=4.0))
print("new")
#else
print("old")
#endif

Do we need something better here, or do you think people will be okay with this? I'm realizing I don't really know how many people try to keep their libraries working across Swift versions and run into compatibility issues.

(Strictly speaking this problem is already present with Swift 4.0.2 with 3.2.2 compatibility mode, but that's much less likely to come up.)

Jordan
_______________________________________________
swift-dev mailing list
swift-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-dev

Thanks for the info, Alan!

On `swift(<4.0)`: I think originally we were even more restrictive about how people used `#if swift`, not even allowing !, &&, and ||. Without those restrictions, allowing a `swift(<x.y)` seems reasonable. However, I don't think that really makes the compound condition significantly nicer. (> and <= are very deliberately not included, because people always forget point releases.)

Jordan

···

On Jan 5, 2018, at 17:22, Alan Zeino <alanz@uber.com> wrote:

Hey Jordan,

We tend to have to do major migrations of our Swift codebase, so I might be able to help. For context at last count (February 2017 was the last time I checked) we had 1.4m lines of Swift across our apps.

This means that when we migrate, we tend to have to chose the smallest possible scope for necessity reasons. For example when Swift 3.0 was available, we went to 2.3 first and it took a few months before we were on 3.0.

When 4.0 was available we went to 3.2 first (with many, many #if statements) and it took a few months before we went to 4.0; which we did by landing many months worth of #if statements to switch between 3.2/4.0 right up until the 4.0 switch.

To answer the last question first, the larger the codebase the more likely you will be to want to do your swift migrations piecemeal with many small changes until you can build a fully–compatible–with–the–new–version executable.

This is even more important for open source libraries, who struggle with either having entire forked branches for swift versions (which means fixes sometimes have to be committed to more than one branch), or many #if statements littered in their codebase.

As for this issue, the main improvement here would be to be able to use > and < in these statements. The first time I had to do one of these large migrations I found it more difficult to reason and read the cases where you had to do this:

#if !swift(>=3.2)
/* code */
#endif

…when it would be a little easier to grok what’s happening if it were possible to do this:

#if swift(<3.2)
/* code */
#endif

In your example, it could be a little simpler if the unary requirement here was relaxed (though I’m sure there’s a good reason why it’s the way it is!).

If I’m reading this correctly (sorry if my interval notation is wrong it’s been a while) the bounds here would be:

        // n >= 4.1
        // or
        // 3.3 >= n < 4.0
        #if swift(>=4.1) || (swift(>=3.3) && !swift(>=4.0))
        print("asdf")
        #endif

But if it were possible to use > and <, it would read:

        #if (swift(>=3.3) && swift(<4.0)) || swift(>=4.1)
        print("asdf")
        #endif

(I don’t know why Mail.app is making the < above look like a less than or equal to symbol ≤)

I find it a little easier to read it this way, because the negation with ! changes my thinking from ‘interval’ to ‘interval plus… not something in this interval’.

Overall, while I think that most people here know that these compatibility versions are really ‘the old code built with the latest compiler’, most tend to ignore this fact so we try not to worry about it much and trust the compiler a lot here to do the right thing. We also try to only support one Xcode version at Uber at a time (and so one swift compiler toolchain), which tends to narrow the scope of the ‘what version of Swift will this code compile for’ question.

Hope this helps!

Alan

On Jan 5, 2018, at 4:19 PM, Jordan Rose via swift-build-dev <swift-build-dev@swift.org <mailto:swift-build-dev@swift.org>> wrote:

Hi, all. Swift 4.1 is off on its own branch and going well, but we never quite came up with an answer for a particular problem developers might have: "am I running a Swift 4.1 compiler?".

#if swift(>=3.2)
// Swift 3.2 (4.0 in compatibility mode)
// Swift 3.3 (4.1 in compatibility mode)
// Swift 4.0
// Swift 4.1
#endif

#if swift(>=3.3)
// Swift 3.3 (4.1 compatibily mode)
// Swift 4.0
// Swift 4.1
// this one is probably not very useful
#endif

#if swift(>=4.0)
// Swift 4.0
// Swift 4.1
#endif

#if ???
// Swift 3.3
// Swift 4.1
#endif

I don't think this is going to come up a lot, but given that we do have changes to the standard library and to the language, I can see people wanting it. Right now the only way to do it is the rather unwieldy:

#if swift(>=4.1) || (swift(>=3.3) && !swift(>=4.0))
print("new")
#else
print("old")
#endif

Do we need something better here, or do you think people will be okay with this? I'm realizing I don't really know how many people try to keep their libraries working across Swift versions and run into compatibility issues.

(Strictly speaking this problem is already present with Swift 4.0.2 with 3.2.2 compatibility mode, but that's much less likely to come up.)

Jordan
_______________________________________________
swift-build-dev mailing list
swift-build-dev@swift.org <mailto:swift-build-dev@swift.org>
https://lists.swift.org/mailman/listinfo/swift-build-dev

I was really surprised when I saw that the release of 4.0 introduced this 3.2 version to mean “the 4.0 compiler running in 3.1 compatibility mode”. So of course, I’m even more surprised to see a new 3.3 version. I find it very counter-intuitive. It also means we will continue to have to increment all previous Swift language versions from now on whenever a new compiler is released:

Swift 3.4 = Swift 5 compiler in Swift 3 compatibility mode
Swift 4.2 = Swift 5 compiler in Swift 4 compatibility mode
Swift 3.5 = Swift 5.1 compiler in Swift 3 compatibility mode
Swift 4.3 = Swift 5.1 compiler in Swift 4 compatibility mode

The most common use of `#if swift` is `#if swift(>=4.0)`, to check for things that actually changed in Swift 4 and not in the 3 compatibility modes. I agree that the "subtraction" going on here is very weird, though, and of course it leads to this problem when you're actually trying to test the compiler release rather than the language mode.

(Your predictions are right, though, if nothing else here changes: https://github.com/apple/swift/pull/13767)

I have the impression that what we really need is a different directive to test for the compiler version:

#if compiler(>=4.1)
// Swift 3.3
// Swift 4.1
#endif

That certainly seems reasonable. If that's the direction we want to go, the next question is whether it's important enough to put through swift-evolution in time for Swift 4.1, or whether it could wait for Swift 5. (I'd also love if someone else shepherded that proposal and implementation through. I could put it up as a StarterBug/StarterProposal.)

Jordan

···

On Jan 5, 2018, at 23:43, David Hart <david@hartbit.com> wrote:

On 6 Jan 2018, at 01:19, Jordan Rose via swift-build-dev <swift-build-dev@swift.org <mailto:swift-build-dev@swift.org>> wrote:

Hi, all. Swift 4.1 is off on its own branch and going well, but we never quite came up with an answer for a particular problem developers might have: "am I running a Swift 4.1 compiler?".

#if swift(>=3.2)
// Swift 3.2 (4.0 in compatibility mode)
// Swift 3.3 (4.1 in compatibility mode)
// Swift 4.0
// Swift 4.1
#endif

#if swift(>=3.3)
// Swift 3.3 (4.1 compatibily mode)
// Swift 4.0
// Swift 4.1
// this one is probably not very useful
#endif

#if swift(>=4.0)
// Swift 4.0
// Swift 4.1
#endif

#if ???
// Swift 3.3
// Swift 4.1
#endif

I don't think this is going to come up a lot, but given that we do have changes to the standard library and to the language, I can see people wanting it. Right now the only way to do it is the rather unwieldy:

#if swift(>=4.1) || (swift(>=3.3) && !swift(>=4.0))
print("new")
#else
print("old")
#endif

Do we need something better here, or do you think people will be okay with this? I'm realizing I don't really know how many people try to keep their libraries working across Swift versions and run into compatibility issues.

(Strictly speaking this problem is already present with Swift 4.0.2 with 3.2.2 compatibility mode, but that's much less likely to come up.)

Jordan
_______________________________________________
swift-build-dev mailing list
swift-build-dev@swift.org <mailto:swift-build-dev@swift.org>
https://lists.swift.org/mailman/listinfo/swift-build-dev

Yep, though there's a difference between compile-time availability (which is what `#if swift` and `@available(swift, …)` check) and run-time availability (which is what `@available(macOS, …)` and `#available` check). Thus far, availability of features in the standard library has been a compile-time check, but as soon as the standard library starts shipping with Apple's OS, it becomes a run-time check. The conclusion I draw is that there's a good chance we'll end up with different solutions for checking compiler versions vs. checking run-time versions.

(And then there's another thing people have asked for, which is statically checking SDK versions. Thus far they've been able to emulate that with `#if swift` because we've shipped a new Swift with every new Apple SDK, but it's still potentially an interesting feature.)

Jordan

···

On Jan 8, 2018, at 17:18, Chris Lattner <clattner@nondot.org> wrote:

On Jan 5, 2018, at 4:19 PM, Jordan Rose via swift-dev <swift-dev@swift.org <mailto:swift-dev@swift.org>> wrote:

Hi, all. Swift 4.1 is off on its own branch and going well, but we never quite came up with an answer for a particular problem developers might have: "am I running a Swift 4.1 compiler?”.

I agree, this is getting bad. Ted mentioned that something like __has_feature in clang is probably the best way to go, so people could check the specific thing they care about, instead of a set of global version numbers.

Another thing that could help is something along the lines of:

  #if swift_stdlib(>=5.0)

which would presumably be active in any language mode when the 5.0 standard library is available. It would be even better to use a generalized availability system for this: the standard library version could be treated just like Foundation versions are handled, for example.

Hi, all. Swift 4.1 is off on its own branch and going well, but we never quite came up with an answer for a particular problem developers might have: "am I running a Swift 4.1 compiler?”.

I agree, this is getting bad. Ted mentioned that something like __has_feature in clang is probably the best way to go, so people could check the specific thing they care about, instead of a set of global version numbers.

Another thing that could help is something along the lines of:

  #if swift_stdlib(>=5.0)

which would presumably be active in any language mode when the 5.0 standard library is available. It would be even better to use a generalized availability system for this: the standard library version could be treated just like Foundation versions are handled, for example.

Yep, though there's a difference between compile-time availability (which is what `#if swift` and `@available(swift, …)` check) and run-time availability (which is what `@available(macOS, …)` and `#available` check). Thus far, availability of features in the standard library has been a compile-time check, but as soon as the standard library starts shipping with Apple's OS, it becomes a run-time check. The conclusion I draw is that there's a good chance we'll end up with different solutions for checking compiler versions vs. checking run-time versions.

(And then there's another thing people have asked for, which is statically checking SDK versions.

I think your second paragraph answers the issue you observe in the first paragraph. You’re right that we have #available and @available, but neither of them are the thing we need here. We need a third thing that allows people to conditionally build (#ifdef-style) based on build time information.

I don’t see the OS or the standard library as being special here. The same thing is true for source packages - a dependent package should be able to write conditional code to allow it to work with different versions of some other package it depends on. I outlined this sort of stuff in this ancient doc:
https://github.com/apple/swift/blob/master/docs/proposals/archive/ProgramStructureAndCompilationModel.rst#sdks <https://github.com/apple/swift/blob/master/docs/proposals/archive/ProgramStructureAndCompilationModel.rst>

Thus far they've been able to emulate that with `#if swift` because we've shipped a new Swift with every new Apple SDK, but it's still potentially an interesting feature.)

Sure, this is just a way of saying that people are already doing this - so we might as well make it nice and consistent. Also, given that Swift is cross platform, it would be nice to have a solution that works in other environments too.

-Chris

···

On Jan 8, 2018, at 5:26 PM, Jordan Rose via swift-dev <swift-dev@swift.org> wrote:

On Jan 8, 2018, at 17:18, Chris Lattner <clattner@nondot.org <mailto:clattner@nondot.org>> wrote:

On Jan 5, 2018, at 4:19 PM, Jordan Rose via swift-dev <swift-dev@swift.org <mailto:swift-dev@swift.org>> wrote: