[Pitch] Platform conditional shorthand with on/or keywords

import Darwin on macOS,iOS,tvOS,watchOS,visionOS or Glibc on Linux or Musl on Musl or Android on Android or WinSDK on Windows

let osabbrev = "darwin" on macOS,iOS,tvOS,watchOS,visionOS or "android" on Android or "linux" on Linux or "other"

VStack {
    Text("\(osabbrev) says 2³ is \(pow(2, 3))")
}
.windowDecorations(true) on macOS
.navigatonHeader("Title") on iOS,macOS
.navigationHeaderHeight(10 on iOS or 12 on macOS or dynamicHeight())

would expand to:

#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(visionOS)
import Darwin
#elseif os(Linux)
import Glibc
#elseif os(Musl)
import Musl
#elseif os(Android)
import Android
#elseif os(Windows)
import WinSDK
#endif

#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(visionOS)
let osabbrev = "darwin"
#elseif os(Android)
let osabbrev = "android"
#elseif os(Linux)
let osabbrev = "linux"
#else
let osabbrev = "other"
#endif

VStack {
    Text("\(osabbrev) says 2³ is \(pow(2, 3))")
}
#if os(macOS)
.windowDecorations(true)
#endif
#if os(iOS) || os(macOS)
.navigatonHeader("Title")
#endif
#if os(iOS)
.navigationHeaderHeight(10)
#elseif os(macOS)
.navigationHeaderHeight(12)
#else
.navigationHeaderHeight(dynamicHeight())
#endif

1 Like

I have a few questions which would help to flesh out this proposal;


on seems like it would be limited to exclusively operating systems which is a subset of the various things that could be easily parsed from this, for example processor architecture like the following:

let arch64 = "x86" on x86_64 or "ARM" on arm64 or "unknown architecture"

Would it be easy to understand that on, as proposed, is exclusively concerned with Operating Systems


Does the Language Steering Group want to include a more extensive English vernacular within the Swift grammar; could existing concepts be adjusted to support these features?


Would this be better filled by providing a compiler macro[1] for conditional compilation evaluation and allowing for conditional compilation conditions to have variable quantity arguments

let osabbrev = 
    #_compiler(os(macOS,iOS,tvOS,watchOS,visionOS)) ? 
        "darwin" :
        #_compiler(os(Android)) ?
            "android" :
            #_compiler(os(Linux)) ?
                "linux" :
                "other"
// or
let osabbrev: String
if #_compiler(os(macOS,iOS,tvOS,watchOS,visionOS)) {
    osabbrev = "darwin"
} else if #_compiler(os(Android)) {
    osabbrev = "android"
} else if #_compiler(os(Linux)) {
    osabbrev = "linux"
} else {
    osabbrev = "other"
}

/*...*/

VStack {}
    .navigationHeaderHeight(#_compiler(os(iOS)) ? 10 : #_compiler(os(macOS)) ? 12 : dynamicHeight())

Note:
The compiler can simplify ternary operators and if statements if the compiler can prove a path is unreachable[2] to optimize execution.
This means that branching operators can act as conditional compilation blocks.


Could where clauses be extended to more locations and gain conditional compilation clause evaluation properties:

import Darwin  where os(macOS,iOS,tvOS,watchOS,visionOS)
import Glibc   where os(Linux)
import Musl    where os(Musl)
import Android where os(Android)
import WinSDK  where os(Windows)

// Maybe
extension String : MacOSProtocol where os(macOS) {
    /*...*/
}

Could a #_limited macro be used with optional conditionals where the @available macro’s information is implied when omitted, when the constraint fails the code block is not included

VStack {}
    #_limited(.windowDecorations(true))
    #_limited(where: os(iOS, macOS), .navigationHeader("Title"))
    .navigationHeaderHeight(dynamicHeight())

  1. this is #_compiler() which indicates a non-finalized macro ↩︎

  2. ie. true ? 15 : 6 simplifies to 15 ↩︎

2 Likes

I appreciate how this proposal simplifies conditional imports. However, I’m concerned that forcing everything onto a single line could negatively impact readability, especially as the number of platforms increases.

I’d like to support the idea suggested by @MinerMinerMain. Using a where clause for each import could be much cleaner and more explicit:

import Darwin  where os(macOS,iOS,tvOS,watchOS,visionOS)
import Glibc   where os(Linux)
import Musl    where os(Musl)
import Android where os(Android)
import WinSDK  where os(Windows)
4 Likes

Great responses!

One of my original ideas, but which I excluded from the proposal, was that you would also be able to alias the import. So something like this:

import Platform as Darwin on macOS,iOS,tvOS,watchOS,visionOS or Glibc on Linux or Musl on Musl or Android on Android or WinSDK on Windows

let value = Platform.abs(negativeNumber) // "Platform" aliases to the right import

That could also be expressed on multiple lines, but having it all on one line does reduce duplication.

Could a #_limited macro be used with optional conditionals where the @available macro’s information is implied when omitted, when the constraint fails the code block is not included

That could work. I think my proposed syntax feels a bit nicer, but if it could be more consistent (or easier to implement) using macro syntax, I think that'd be fine too.

I'm not really sold on this as special syntax on any arbitrary value like this.

This actually doesn't really read much better than the boilerplate it is replacing: import Darwin on macOS,iOS,tvOS,watchOS,visionOS or Glibc on Linux or Musl on Musl or Android on Android or WinSDK on Windows That's really hard to parse to be honest (for a human).

Also affecting the outer scope from inside the func call seems pretty suspicious in this example:

.navigationHeaderHeight(10 on iOS or 12 on macOS or dynamicHeight())

#if os(iOS)
.navigationHeaderHeight(10)
#elseif os(macOS)
.navigationHeaderHeight(12)
#else
.navigationHeaderHeight(dynamicHeight())
#endif

It also is somewhat suspicious to introduce more conditional keywords to the language only to specialize them to platforms... Rather, #if being turned into expressions might be helpful here instead perhaps?

5 Likes

That could be interesting, like:

.navigationHeaderHeight(#if os(iOS) 10 #elseif os(macOS) 12 #else dynamicHeight() #endif)

That would satisfy the common need for a different value for different conditionals, but it wouldn't help with the common need for including a line of code only for certain platforms (which is especially common when creating SwiftUI, where View modifiers are frequently unavailable based on the platform).

Maybe if #if could also have special meaning when used as a trailing unclosed fragment:

.windowDecorations(true) #if os(macOS)
.navigatonHeader("Title") #if os(iOS) || os(macOS)

For cases like these I'd prefer having something like these:

(not current swift / SwiftUI but possible)

.navigationHeaderHeight(os(iOS) ? 10 : os(macOS) ? 12 : dynamicHeight())

.windowDecorations(os(macOS) ? true : .ignore)
.navigatonHeader(os(iOS) || os(macOS) ? "Title" : .ignore)

The last two rely on a feature of passing something that effectively says: "oh, please treat that as if I didn't put that modifier at all if the condition is not met". Sometimes it could be nil, although often times nil is treated as some default value rather than "ignore" (e.g. .padding(nil) is not the same as not including that attribute at all), so some explicit ignore is a better option in this case.