Support Negative Availability Literals

I don't mean to be condescending, but we once again circled back to the beginning without addressing what I asked... There is a theoretical confusion, but will it actually happen in code? Is there someone who really relies on * for things to work, in code?

This whole discussion for me sounds like saying that computers are a bad invention because they will stop working if you explode them. Surely bad, but who the hell does that? That's what I think we should discover for * before arguing about whether or not we should change the syntax. In the case of *: who is using * in a way that would make the functionality confusing?

I'm not against coming with a different solution, I'm against doing so with no concrete proof besides what we personally think. I think what I intend to achieve here is that ultimately we should have no expectations for * in terms of functionality, because (I think) no one will ever rely on it. Thus, it would be present simply as a way to make it clear that other platforms are also being considered in the statement, just like for the original #available.

i.e, If I'm correct about my claims, then we are overthinking an aspect of this feature that no one will every rely on. We made up this problem, it's not legitimate.

My interpretation of the motivation for * in #available is that it's there so future platforms are supported automatically (at least that's what the error message is saying if you leave it out). This is already the case for #unavailable because it's a negative statement so I think the * could be left out entirely.

1 Like

That's the ultimate goal, but the feature itself tells you what will happen to all other platforms. As in, #available(iOS 13, *) in watchOS is equivalent to #available(watchOS minimumTarget). This behavior comes from *, so my worry about removing it is that we would lose this visual component.
If we did this we would probably also have to remove the ability of having multiple platforms in the same statement, which is definitely not a bad idea, but as I mentioned before I would like to make sure we're not dealing with a made-up problem before going to a different path.

I've read the upthreads about * concerns, but I think the culprit is NOT the * itself, but the relationship between specified part and the rest part... (all other platforms *) which in #available is OR but in #unavailable should be AND !

e.g. the logical expression (A or B), the negation of it !(A or B) should be (!A and !B).

For #available and #unavailable (!#available in semantic) , #available(macOS 11, *) means available in macOS 11+ OR all other platforms, but #unavailable(macOS 11, *) means not available in macOS 11+ AND all other platforms which equivalent to "ONLY available in ! (macOS 11) (macOS<11)." At the same time #unavailable(macOS 11) means unavailable in macOS 11 ONLY but still maintain availability of all other platforms.

In conclusion, #unavailable(something,*) means unavailable something AND others... but #unavailable(something) means unavailable something ONLY (all others still available).

Hope it clear enough to interpret * in #(un)available statements ~

1 Like

What if we support the syntax that omits the * as a syntax sugar? The thing here is that the wildcard has a technical definition that makes sense for unavailability (minimum target), but from a syntax point of view it looks incorrect because this technical definition is not a developer's first thought. This is essentially what Quincey proposed above but without the * makeover.

If this makes sense, we could consider #unavailable(iOS 12, *) as the valid spelling but also allow writing #unavailable(iOS 12) as the syntax sugar version of it. We would then eat the inconsistency between what's written / what actually happens deep down for the benefit of having a better syntax overall.

What I was trying to say (and maybe I'm wrong) is that the * in #availability isn't about the visual component or about making it clear that other platforms have been considered, but to ensure that if more platforms are added to Swift in the future that the condition defaults to available instead of unavailable.

Now with #unavailable statements, the condition would default to available for future platforms by definition, because all platforms except the listed ones are included. Which would imply that * isn't needed with #unavailable.

1 Like

This would mean that * has no meaning at all, which doesn’t seem right. It also doesn’t address the issue that #unavailable(iOS 12) is somehow false for macOS, nor the issue that some users expect #unavailable(iOS 12, *) to be somehow true for macOS.

I think fundamentally the feedback is that users expect unnamed platforms to be available, and it seems that with any spelling whatsoever, that unnamed platforms are unavailable is surprising to some proportion of users. I don’t get it either, but if that’s the case, the issue is inherent to “negative availability” in general rather than the wildcard, and I would agree with @QuinceyMorris then that it’s unsolvable.

If you trigger the error in a different platform, you'll get an additional diagnostic asking for the current platform to be mentioned:

error: condition required for target platform 'macOS'
if #available(iOS 12) {
   ^

But you are not wrong, this was primarily done to support future platforms and we're now going deeper to fit it into another context.

Taking a broader view, what’s actually desired here isn’t a negated check, it’s a different version range.

If rethinking #available was in scope, I’d want to consider something like:

  • Allow the existing clause #available(iOS 13, *) to be written as #available(iOS >= 13, *)
  • Allow the use of < to express the reverse.

* would still mean “every version of every unnamed platform”.

3 Likes

During the review, I proposed omitting the *, but as @xwu has pointed out, it actually suffers from a similar problem as with the *.

Let's consider the following example:

if #unavailable(iOS 13)

Let's say I'm running iOS 12, of course iOS 13 is unavailable, so this is true.

Now let's say I'm running macOS 11, well, iOS 13 is still unavailable because this is macOS, so this is still true right? Nope, it is false, hence the confusion.

Now let's consider this example:

if #unavailable(iOS 13, *)

As @xwu mentioned earlier, this will be read as "If iOS 13+ is unavailable, or if any other platform is unavailable".

Let's say I am running iOS 12, iOS 13+ is unavailable, any other platform is also unavailable, so this is true.

Now let' say I am running macOS 11, first part: iOS 13+ is unavailable so first part is true, second part: macOS falls into "any other platform" here so second part is false, so overall this is true right? Nope, it's false because as I mentioned earlier, and as @BigSur mentioned, in #unavailable the comma is AND not OR, but everyone will read it as OR.

So asterisk or no asterisk, there is still a very high likelihood of misinterpretation (because it has been demonstrated several times in these threads that even experienced Swift programmers misinterpret these statements).

But the spec list is not a boolean expression, that statement is read as simply “iOS 13 is unavailable”.

I think this is why we're having so much trouble with this, some of us are seeing it like an if expression when it reality it's more comparable to an Obj-C complex macro. To put in short, the specs that talk about other platforms are irrelevant.

I've tried updating the draft to fully describe how the spec list works. Can you tell me if #unavailable(*) it's still confusing after reading the semantics section?

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)