Support Negative Availability Literals

I completely understand this is not a boolean expression.

However, in the #available(iOS 13, *) case, one can read this (and evidently many do, as evidenced in these threads) as "If iOS 13 is available, OR if any other platform is available", and they will always arrive at the correct answer.

In other words, #available, while not actually a boolean expression, happens to work exactly like A || B.

And #unavailable with asterisk, while not actually a boolean expression, happens to work exactly like A && B, but most programmers will fall into the trap of reading it as A || B because it has identical syntax to #available.

I agree with that. I think though that this could ultimately be concluded as a documentation issue, unless we can come up with a superior syntax (or if the behavior is confusing even after reading said documentation)

Sure, obviously using !#available solves this issue because now it will be read as !(A || B), which will always result in the correct evaluation by the programmer, but I believe this has been noted as not preferred for implementation reasons.

If the feature was introduced without the *, I think it would be quite straightforward to understand (and I would agree with you that #unavailable(iOS 12) would be true on macOS, or would need to be rejected by the compiler, as @bjhomer suggested).

What I haven't understood yet it is why do we need to support unnamed platforms with #unavailable?

A requirement of the availability spec list is that there should always be an entry for the platform being compiled (otherwise the statement wouldn't make sense). If we mix that with the other requirement where every spec list should contain the wildcard, the result is that you can port your code to different platforms without having to manually add the new platform to your statements. Note that this isn't related to the "new platforms should return true" thing, here we're just mentioning the compilation aspect of it.

If we make the wildcard unnecessary, then the unavailability statements would cause compilation errors if you attempted to the compile the code in a new platform. I think this is a valid option, but since the wildcard was created precisely to avoid this I'm not sure if the core team would be happy with it.

This can be solved by making it a syntax sugar, but we seem to be in agreement that this would just shift the problem elsewhere.

This could be resolved with @bjhomer's suggestion.

I guess my previous question was where this requirement is coming from.

This is where I think that the reasoning was the other way around: By the requirement to add unnamed platforms to #availability (via *) I think the idea was that code wouldn't silently be skipped if new platforms are added.

So if code with #unavailable would stop compiling on a new platform that would be a good thing–code parts would not silently be skipped and the developer would be able to make a conscious decision how the new platform should be supported.

That suggestion still has the same issue I've been describing.

For example, let's say I wrote #unavailable(iOS 13), I try compiling for macOS, I get an error telling me that I need to be explicit about what I want to happen on macOS (I want it to be false on macOS).

Ok, so now I write #unavailable(iOS 13, macOS)

Now let's interpret what I've written using the same logic that people use for #available

"If iOS 13 or later is unavailable, or if macOS is unavailable"

A: "If iOS 13 or later is unavailable" true
B: "If macOS is unavailable" false

A || B: true

Except this is actually supposed to mean false, so I have still failed...

You'd have to be explicit about what version of macOS you mean:

if #unavailable(iOS 13, macOS 11) {
  // macOS 11 is my minimum deployment target,
  // so this will never run on Mac, but now I've
  // been explicit about it
}

Explicit about version or not, it still does not work when you apply the #available logic that so many Swift programmers do.

Now the example becomes "If iOS 13 or later is unavailable, or if macOS 11 is unavailable"

A: "If iOS 13 or later is unavailable" true
B: "If macOS 11 is unavailable" false

A || B: true

Nothing changed here by adding the version number (except that we have changed the evaluation of B in the future, where we might be running macOS 12, not that it matters, because we ORed it, and A is always logically considered true (in programmers minds) when running macOS)

Right, I'm not saying it should be an OR operation. While it's a valid way to look at the #available declaration, I think it's simpler to say that you just need to look up the platform you're currently compiling for, and ignore any other declarations. It's not spelled with an || because it doesn't need to be one.

In other words, I think it's more like this:

// Look up the platform you're on. All other platforms are implicitly
// ignored, because they're irrelevant.
if #unavailable(/*iOS 13*/, macOS 11) 
1 Like

Understood, thinking of it as a boolean expression is definitely incorrect, but the reality of the situation is that (because it never led them astray in the #available case to think of it as A || B) many Swift programmers will read it as a boolean expression. It's not just me, the evidence for this is in this very same thread, most recently in @xwu's post here: Support Negative Availability Literals - #67 by xwu

He is wrong to think of that example as "// If macOS 11+ is unavailable, or any other platform is unavailable...", and yet that is how he wrote it.

I understand how the statement with a wildcard can create confusion, but how would one misinterpret this statement?

if #unavailable(iOS 13, macOS 11)

I think we're bound to go in circles when coming up with definitions because it seems to be clear that there is no solution that 100% fits our criteria. We do however have many viable solutions with downsides -- some have downsides in portability (removing the wildcard) while some have downsides in terms that the readability could be confusing at first (the original proposal).

If we ignore the confusion part, do we have an agreement that the description in the revision draft is 100% correct?

If we know that the semantics regarding spec lists, requirements on versions and requirements on wildcards are valid, then it might be easier to determine which of these downsides we're willing to eat. As a personal thought, I still think that the problem is simply that the average Swift developer doesn't understand that the availability is not a boolean expression, which can be solved by improving the official documentation once this feature lands.

1 Like

The Negation operation not only negate the operand but also negate the operator; !(A||B)==(!A && !B).

According to #unavailable==!#available, we can treat #available as OR, so #unavailable==un#available==!OR==AND.

Think #available as OR operator can simplify the problem and reduce the confusion between #(un)available scenarios.

#unavailable(x,*)==!OR(x,*)==AND(!x,*), this should be noted explicitly in proposal document for developer to understand easily and correctly.

Ok let's break it down:

Assume I read this with the same logic that people read #available with (A || B)

Now, let's say I am running macOS 11

A: "iOS 13 or later is unavailable" true
B: "macOS 11 or later is unavailable" false

A || B = true

But the condition actually is false, so I have now misinterpreted it because I applied the same logic I apply for #available (and just to pre-empt this, yes I know #available and #unavailable are not actually boolean expressions, but in reality people read it as such)

Yes I agree that the description in the revision draft is 100% correct.

Imo, something that needs to be noted explicitly in a proposal document for the sake of correct understanding cannot be considered "intuitive".

Yep, for correctly to understand. :laughing:

So I don't know if we would need to go deep into comparing in the proposal itself, but I agree that it's fair to mention (assuming that we agree with #95) that we have an issue with how developers perceive this syntax and that it needs to eventually be clarified in the official docs.

Ok here is my last ditch proposal, I will preface this with, "Yes I know these are not actually boolean expressions".

Ok here is the proposal:

#unavailable(iOS 12 && *)

One should read this logically as "If iOS 12 or later is unavailable && if any other platform is unavailable"

A: "If iOS 12 or later is unavailable"
B: "If any other platform is unavailable"

Let's go through a few examples:

Example 1: I am running iOS 10

A: iOS 12 or later is unavailable, true
B: I am on iOS, so any other platforms are unavailable, true

A && B = true. Hooray that is the correct result.

Example 2: I am running iOS 13

A: iOS 12 or later is unavailable, false
B: I am on iOS, so any other platforms are unavailable, true

A && B = false. Hooray that is the correct result.

Example 3: I am running macOS 11

A: iOS 12 or later is unavailable, true
B: I am on macOS, so any other platforms are unavailable, false (because macOS is one of those other platforms)

A && B = false. Hooray that is the correct result.

So, to recap, I am proposing that we replace ", *" with "&& *". This would mean that the fix-it would put in "&& *". This also means that (even though this is not actually a boolean expression), programmers can still read/interpret it as such, and as long as they evaluate the conditions correctly, they will arrive at the correct result.

We get to keep the preferred spelling of "unavailable", we get to keep pretending this is a boolean expression, everyone is happy right?

1 Like