SE-0290: Unavailability Condition

I'm still confused, though I'm happy to be shown the confusion is in my head rather than the proposal. In the Swift Language Guide, it says about #available:

The * argument is required and specifies that on any other platform, the body of the code block guarded by the availability condition executes on the minimum deployment target specified by your target.

So #available(iOS 13, *) is true when compiling for iOS deployment in any range that includes version 13, and true in any compilation for tvOS deployment. Right?

OTOH, though #unavailable(iOS 13, *) is obviously the negation of the #available value when compiling for iOS, what is the value for tvOS compilations?

  1. If it's always false for tvOS, then the * in the syntax is a bit hard to explain. It means something different when it's used with #unavailable than when used with #available. In that case, the syntax would be much clearer as !#available(iOS 13, *), rather than #unavailable(iOS 13, *). #unavailable would be actively misleading to the unwary.

  2. If it's always true for tvOS, then the syntax !#available(iOS 13, *) must not be allowed, because it suggests a negation that isn't actually happening.

Edit: Actually, after writing that down and reading it back, I see I probably asked the wrong question. Regarding tvOS, if #available(iOS 13, *) means "available on all", does #unavailable(iOS 13, *) mean "unavailable on all" or "unavailable on some"? There's a negation and a logical quantification happening per platform, I think, but the order isn't obvious.

4 Likes

This is a great point that I have also asked myself. In the current implementation #unavailable(*) means "always unavailable", so it would always return true. The impression I have is that * means "anything" here, so it would return true for both #available and #unavailable. This was my personal take when developing this though so I'm also unsure if it was correct to assume that.

Let me try my question again:

If there was actually an expression grammar here, would it be true that:

  !#available(iOS 13, *) == #unavailable(iOS 13, *)

on both iOS and tvOS platform compilations?

— If yes, then I'd argue that #unavailable should be spelled !#available.

— However, it sounds like you're saying it's not true in general. In that case, I'd argue that #unavailable should not be spelled !#available. Also, in that case, I think the proposal needs to describe the full and exact semantics of #unavailable before its review can proceed.

1 Like

My opinion is that I think they shouldn't have the same result because they are not the same thing.

Assuming that we agree that * means "anything", On tvOS, while #unavailable(iOS 13, *) would mean "is anything unavailable", !#available(iOS 13, *) would mean "the inverse of 'is anything available'". While the former is an availability check, the latter is an expression on top of an availability check, and the case to me seems to be that even if this proposal does end up including the !#available notation as a fix-it, we still don't want to give developers the idea that you can use #available as an expression in Swift.

But on the other hand, if * means "any supported version of this platform", then #unavailable(*) would return false and match what !#available(*) would do. In the end I don't think this boils down to which notation is more correct, but to what * is supposed to represent. Does it mean anything known or absolutely anything, even iOS 9999?

Given that #available(iOS 9999, *) is a valid statement, I assume that the intention was for it to mean the latter, which would result in #unavailable(*) returning true. But as I mentioned before, even if they had the same result they still wouldn't be representing the same thing, so they probably shouldn't be compared in this sense.

Doesn't the * mean "All versions of all other platforms and any future versions of this platform"?

So #available(iOS 13, *) would mean "iOS 13 (or later versions) + any tvOS + any macOS + any watchOS"

1 Like

Do removals have any compiler assistance with #available? I can’t think of an example of this to be able to test it out.

In terms of ergonomics, the main downside of the ProcessInfo API is that it’s relatively verbose and needs a separate check for platform. However, I think this could be overcome with a more Swifty API, eg

ProcessInfo.platform.unavailable(.ios, .version(13, 0))

1 Like

If you're going to make available into a method then it can be negated and there's no need for an unavailable method.

Ok, I think that most of the confusion when reading these unavailability conditions stems from the asterisk.

Let's consider this example:

   if #available(iOS 14, *) {
       print("I'm running iOS 14 or later, or any version of any other platform.")
   } else /*    #unavailable(iOS 14, *) or !#available(iOS 14, *)    */ {
       print("I'm running a version of iOS that is earlier than 14")
   }

I think the problem with #unavailable(iOS 14, *) is that the asterisk isn't actually saying anything about other platforms, so when you read it and try to apply the same mental logic as you do to an availability condition it's just weird. I think we should consider leaving the asterisk off for the unavailability conditions completely. Also for this reason, I think having #unavailable instead of !#available makes more sense, because they would have different asterisk requirements.

So instead the example might look like this:

 if #unavailable(iOS 14) {
    print("I'm running a version of iOS that is earlier than 14")
 } else {
   print("I'm running iOS 14 or later, or any version of any other platform.")
 }
6 Likes

I think that's the perfect solution.

2 Likes

In this case, if * means All versions of all other platforms and any future versions of this platform, then we don't need to remove it because it already dictates that the condition should return false. If you can compile for a platform, there will always be a version available.

I agree that it makes things weird though. I would be fine with removing it if that's the consensus but I personally think it's not an issue because it's just a matter of describing what * means.

I'm fine either way, I guess all I'm saying is that only one side of available/unavailable condition needs the asterisk. So for whatever reason that makes it weird if they both have the asterisk (in my mind at least).

I’d like to revisit this. Without additional magic semantics, if #unavailable(iOS 14), #unavailable(macOS 11) will always be false. In order to compose #unavailable, you would need if #unavailable(iOS 14) || #unavailable(macOS 11). Note that && is not, er, available with #available because it is a “clause” rather than an expression.

This isn’t necessarily a show-stopper; as far as I can see the fact that , can be used as a general conjunction is just a weird wart in the language anyway. But in the case of #unavailable it isn’t “useless in a way”, it’s just useless. :slight_smile:

It's a good revisit. The original intention in that post was for * to always return true, but with the idea that it should instead return false it will make sense to disallow multiple unavailability checks.

Before we close the review, I think the conclusion is that we need to amend the proposal to contain that:

  • #unavailable(*) should return false, because a "future version/platform" will always be unavailable. It also better matches how the hypothetical !#available(*) would behave. Corrected: #unavailable(*) should return false, because if you're able to compile for a platform, then "all current and future versions" will always contain a version that is available. It also matches how the hypothetical !#available(*) would behave.
  • Given the above, multiple unavailabilities in the same statement should throw a warning, as two unavailabilities for different platforms will always cancel each other.
  • !#available can be diagnosed with a fix-it that converts it to #unavailable.

It's possible also to drop the asterisk as suggested, but I personally think the semantics of * already match the expected behavior.

1 Like

looks perfect

You mean true?

Argh, I confused myself which is a good indicator of how subjective * can be in this situation. The correct argument I meant to share is that it should return false because if you can compile for a platform, then it will always have a version available. Thus it would be impossible for "all current and future versions" to be unavailable.

Returned for revision

Based on this review thread's excellent discussion, the core team has decided to return this review for revision. The core team believes this is a merited addition to the language but that specific details of the proposal (as pointed out in the review thread) need to be addressed.

The core team feels that the spelling of #unavailable, as opposed to !#available, was the preferred path. As pointed out in the review discussion, #available is not a general expression. Further, the core team observed that people are likely to say "unavailable" instead of "not available” in conversation — further preferring the #unavailable spelling. The review thread also brought up a great insight to have a compiler fix-it for users reaching for !#available as the syntax — that would be a great refinement (now or in the future) to the implementation.

The proposal needs revision to address the semantics of the #unavailable annotation for the platforms that are not specified. The wildcard * for #available has precise semantics, where * indicates the minimum deployment target for the unspecified platforms. The proposal needs to define the semantics for #unavailable for the platforms not specified and clearly explain how those semantics compose with the use of #available and other #unavailable annotations that may decorate a declaration.

Once the proposal is revised with those changes, the core team will run a second review of this proposal.

Thank you to everyone who participated in this review! The signal from the review was invaluable in gleaming these essential details to consider for this language enhancement.

13 Likes

The second review of this proposal is now running:

Terms of Service

Privacy Policy

Cookie Policy