[Pitch] Language Mode Compiler Condition

This is a follow-on proposal to SE-0441. It was originally included as part of the original pitch for but split into its own proposal.

Your feedback and comments are greatly appreciated.

UPDATE: To allow for a pending revision of the proposal I have moved the text of the proposal to a gist.

(As of Aug 30, 2024 I have not yet had an opportunity to update the proposal and at a certain point, forum posts become uneditable.)

15 Likes

I need to think more, but one immediate reaction is that the warning text should include your "This code will always / never compile." statement so it's clear to the user what's happening with it.

11 Likes

And, in my opinion, the warning for languageMode(>=oldVersion) should offer a Fix-It that removes the #if and #endif lines, at least in simple cases.

2 Likes

It would help the pitch to list the currently accepted language modes: 4, 4.2, 5, and 6. Obviously some of those will also be dropped in the future, while 7 and so on will be added.

Maybe swift() should be modified, deprecated, warned, or further documented in some way, since it is so problematic.

You could link to the SE-0212 proposal.


An unknown -language-mode argument must be an error, but an unknown languageMode() argument could be a warning. For example:

#if arch(x86)
//       ^~~
// warning: unknown architecture for build configuration 'arch'
// note: did you mean 'i386'?
#endif

On the other hand, the following compiles without warning (in Xcode 16 beta 5).

#if compiler(>=7) && languageMode(>=7)
#endif

Typos.

2 Likes

+1
I wanted to migrate some parts of my code to the new language mode but they don't compile with Xcode 15.4 (only with beta). This can make the transition a bit easier.

2 Likes

I don’t think so: if the same code is meant to compile with an older compiler, this fix-it would actively encourage breaking the code. There needs to be a way to silence the warning in source without removing the conditional for older compiler versions.

4 Likes

Thanks, that would definitely help flesh out the proposal.

The behavior of swift() needs to stay the same to avoid breaking existing code that depends on it.

Even though it is not straightforward to use as a language mode condition, the behavior of swift() is not incorrect.

The behavior of swift() is not incorrect, but it is not always straightforward to use as a language mode condition. I don’t think it rises to the level of causing active harm and needs to be deprecated or removed.

Yes, I will add a link to that proposal, although even that proposal doesn't seem to cover the full range of behavior of swift().

Yes, it also occurred to me that sometimes packages need to be compiled with multiple versions of the compiler, so specifying languageMode(<7) in a hypothetical Swift 7.0 compiler would generate an error in a Swift 6.x compiler.

I think it is desirable for the compiler to be strict within the range of language modes that it knows about. In the Swift 6.0 compiler that would be 4, 4.2, 5, 6.

So, any semantic version value between 4 and 6 that aren't a valid language mode should be an error.

All past language modes that are no longer supported are also known, so even there semantic versions less than 4 can be checked to ensure they are valid language modes.

For semantic versions beyond the highest language mode, the compiler could possible emit a warning, or possible accept it as is.

I would prefer to emit a warning, but that then runs afoul of people who prefer 'treat warnings as errors'.

Of course, since this functionality would likely be available in Swift 6.1 or later, this would not be an issue until a language mode beyond Swift 6 is introduced (which hopefully is a long while) and possibly the facility to suppress individual warnings will be added to the language by then.

1 Like

As written, there are two problems which I believe need to be blocking:

  • If languageMode only tolerates versions it knows about, then this is useless to allow new code to build with old compilers. I couldn't write if languageMode(>=7) to conditionally compile out code intended to build with a Swift 6 compiler because Swift 6 will reject it.
  • If languageMode starts emitting diagnostics for versions that are not supported anymore, essentially all uses of languageMode are (minor) time bombs and will "stop working" (emit a diagnostic) at some point in the future. This prevents you from using languageMode to allow a code base to continue to build on old compilers.
5 Likes

Thank you for your feedback.

Yes, this is definitely something I missed in the original pitch I posted. I have noted this in a post earlier in the thread. I have not had a chance yet to flesh out the details and incorporate this into the pitch itself, but I intend to do so.

I don't believe that emitting a warning is equivalent to making something "stop working".

The behavior for previously supported language modes follows the same pattern as symbol deprecations. If you build using a prior compiler release there is no warning, if you build the same code on the release where a deprecation occurs, or in this case where a referenced language mode is no longer supported, there is a warning.

Also note that languageMode() can be used in conjunction with the compiler() compilation condition to accommodate different code paths depending on the compiler version and language mode.

The @available attribute can also use language modes. For example:

@available(swift, introduced: 4.2, deprecated: 5, obsoleted: 6)

will be unavailable in language modes 4 and 6.

However, version numbers other than 3, 4, 4.2, 5, and 6 are currently allowed.

I'm not sure if @available(languageMode …) should be a future direction.

1 Like

If it's OK to indefinitely carry a warning in a code base, what is the warning protecting developers from?

1 Like

In this case, it is warning a developer that the language mode referenced in their code is no longer supported in the current version of the compiler.

Overall though, different developers and organizations handle warnings in vastly different ways. I have done work for organizations that work to address each warning as it appears and others that allow dozens of warnings in their code base for long periods of time.

The proposed behavior for previously supported language modes follows the same pattern of warning as deprecated symbols. But the overall philosophy of warnings in general and how developers should handle them is beyond the scope of the proposal.

1 Like

It cannot be beyond the scope of this proposal to decide when warnings are appropriate if this proposal includes a warning. It would not be responsible language design to accept all diagnostics until there are core guidelines on when it is appropriate to have a diagnostic.

The danger advertised by a deprecation warning is that the API will go away entirely in a future release and/or bugs reported against it won't be fixed anymore. That danger makes it worth a diagnostic. We can do this exercise for all existing diagnostics. I think that we need to do it here.

When people run into false positive diagnostics, they file bugs against the compiler. For instance, it used to be that using var (foo, bar) = blah() and modifying only one of foo or bar would diagnose that it should be a let binding, which is incorrect.

Given a warning that can have false positives (code can be built with old and new compilers alike), I feel that its existence must have a stronger basis than there being another similar diagnostic that exists for its own enunciable reasons (which are inapplicable to the new one).

Yes, that is being done here. The warning exists to let the developer know that the language mode referenced in the compilation condition is no longer supported by the version of the compiler that emits the warning.

Your earlier post made the case that emitting a warning would make the code "stop working" when the developer moved to the new compiler, if the code needed to compile on the current and previous versions of the compiler.

I made the analogy to deprecation warnings because those do not prevent a developer from having their code compile on current and previous compiler versions. The same techniques developers use for these warnings can be used for the proposed warning.

I am not sure what you mean that this warning would generate false positives. If the compilation condition uses a language mode the compiler supports, no warning is emitted. If the compilation condition uses a previously supported language mode, a warning is emitted.

Why not assume that any language mode with a numeric value less than the lowest currently supported language mode is a valid language mode that the current language mode is greater than, and any language mode with a value higher than the current compiler version is greater than any supported language mode? That way there would never be a diagnostic produced for future or no-longer-supported language modes, but users would still receive assistance when using an invalid language mode like 5.7.

A parallel but separate feature could be to add a compiler option to produce diagnostics for old language modes and #available/@available declarations with values lower than the deployment target, which would enable developers to actively choose to identify and clean up these sorts of dead conditionals, rather than being forced to look at false warnings all the time.

Yes, I think it might be most accurate to say that

@available(swift, ...)

takes the current language mode into account when determining availability.

The swift availability attribute is at the granularity of specific Swift / compiler / toolchain versions since things can potentially be introduced, deprecated, etc. in any release.

I believe the current behavior of taking the current language mode into account when determining availability is the correct behavior and does not need to change.

I also think that determining availability is a different topic beyond what is proposed. The compilation condition is intended as a straightforward condition for "I want this code to run in Language Mode 6 and later and this code to run in every language mode before 6." (and of course, the various 'elseifs' possible.)

I don't think availability based on language mode should be a future direction. It is most natural to express availability based on the specific version that the availability of a symbol changes. Since determining availability already takes the language mode being used to compile into account, using the existing availability attribute as designed already gives the desired results.

1 Like

I think there are two different cases of unsupported language modes.

For now-unsupported language modes, I believe there should be a warning because, generally, when moving to a new version of the compiler and your code contains things that are no longer supported, deprecated, etc. a warning is generated to warn the developer of those issues. (i.e. the support or availability of something in your existing code has changed in this release of the compiler and you are being warned/alerted to that change.)

For future unsupported versions, as discussed in an earlier post, as I think more about it, I'm coming around to thinking those should not emit a warning. I haven't written out the full rationale yet but will update the pitch once I have a chance.

Yes, I think that would be a separate feature beyond the scope of the proposal.

I don’t think API deprecations are really a good comparison for language mode checks. API deprecation warnings only appear (at least for OS libraries) when you update your deployment target to match or exceed the version where the deprecation happened. I don’t think there is really a mechanism to do that kind of staged deprecation for SwiftPM dependencies (probably because you control the version of the dependency you use?). However, language mode checks are much like compiler version checks in that they allow you to work around differences between different versions of the language’s syntax and semantics. That’s why I think the compiler should either commit to never removing a supported language mode, or commit to never warning about a language mode check that a released version of the compiler accepts without a warning: the whole point of this feature is to allow code to be written that can be compiled with a wide variety of compiler versions without causing diagnostics to be emitted.

1 Like