SE-0290: Unavailability Condition

The review of SE-0290, "Unavailability Condition", begins now and runs through November 17, 2020.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to the review manager or direct message in the Swift forums).

What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift.

When reviewing a proposal, here are some questions to consider:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

As always, thank you for your contribution to making Swift a better language.

Ted Kremenek
Review Manager

13 Likes

The Alternatives Considered section says the following about why !#available wasn't proposed:

While allowing the original condition to be reversed seems to be the obvious choice, supporting it in practice would require hardcoding all of this behavior as #available cannot be used as an expression. The author would rather not add tech debt to the compiler.

That argument comes only from the perspective of a compiler developer, rather that considering the experience of someone using the language. Was there additional discussion about this on the pitch thread that I'm overlooking? The ! syntax seems like a better fit with the rest of the language, and is what a developer would reach for first — and then be surprised that it doesn't work.

7 Likes

Same question, is there any compiler hurdle to add !#available to the language?

!#available seems more intuitive and easy to understand.

!#available would make sense if #available were a function, which it is not. You can probably make your own free function that works like that but that's not we've got. #available is a compiler built-in and #unavailable will match it.

1 Like

Was there consideration of using the NSOperatingSystemVersion API for negative version checks? (Or adding something similar but more concise)

My understanding is that the main reason for using #available instead of a plain API is so you can check for API availability at compile time. However, for the examples of negative checks given in the proposal, it’s for accessing APIs that aren’t limited by version availability.

I only saw a brief mention of this in the pitch thread, without any discussion. At the least, I think it would be good to cover this under ‘alternatives considered’

1 Like

Using the NSOperatingSystemVersion API doesn't silence deprecation warnings.

2 Likes

The only reason that it would matter that this is spelled !#available rather than #unavailable would be if availability conditions could be part of larger expressions within a single comma-separated clause of the if — but they can't, right?

On the other hand, the proposal seems to allow if #unavailable(…), #unavailable(…). That seems a little unfortunate, because the comma would still be a logical "and", but it's unlikely this would ever be the meaning wanted.

Maybe, instead of just outlawing mixed #available/#unavailable in one if statement, we should outlaw multiple #unavailables too?

2 Likes

Maybe I’m misremembering things, but I thought deprecation warnings only started showing when the minimum OS target was above the deprecated-from version?

So, if you were building with the iOS 14 SDK, deploying to iOS 13, the following would compile without warnings anyway:

ASIdentifierManager.shared().isAdvertisingTrackingEnabled

I would like to note that supporting !#available is perfectly possible. The only reason it wasn't the main proposal is that it was necessary to shed a light on the current state of the condition today in the compiler, but I also vote for it being the main choice. It's not great that we have to parse the operators manually, but the workaround is completely contained to the availability logic.

Maybe, instead of just outlawing mixed #available / #unavailable in one if statement, we should outlaw multiple #unavailable s too?

Deep down, having multiple availabilities/unavailabilities is the same as having one that handles everything:

if #available(iOS 13, watchOS 3, *)
if #available(iOS 13, *), #available(watchOS 3, *)

In the second case the difference is that the compiler has to traverse the other conditions to determine which version it needs to provide symbols for, but it's a matter of searching for the maximum value. This is also the case for #unavailable, except that we're calculating the version for the else block instead.

So having multiple conditions is useless in a way, but it's not harmful and I guess some people find that to be more readable. The problem is that if we did that for #unavailable it would be probably make sense to forbid them in #available as well, which would break source compatibility.

That’s true for deprecations, but there are also removals. In any event do you have an ergonomic comparison between the two approaches?

+1 for the feature, but I agree with others in the thread that this should be spelled !#available. This seems more intuitive compared with adding an additional word into Swift's grammar. #available(...) appears to behave as a simple boolean expression from the surface, so this seems like the most logical extension IMO.

Edit: Accidentally referred to #available an expression.

I will echo the others, with a caveat:

  • I love this in principle and I want it
  • I agree that it would be great if we could somehow get this to be a "regular" boolean expression
  • I will vehemently oppose any proposal where !#available(...) is the only way to use it
2 Likes

This proposal LGTM, I agree with the author that #unavailable is a better way to go than !#available or other approaches. The #available clause in an if is not an expression.

-Chris

11 Likes

I think it's fair to say as well that even if !#available doesn't get fully implemented, it would be trivial for us to provide a fix-it that converts it to #unavailable. This way someone unaware of this proposal wouldn't be lost as most people would assume that the former was the correct spelling.

9 Likes

I think this is going to be quite a nice improvement over the status quo, and while there might be some preferences as to !#available or #unavailable, I don't any particular issues with either. +1 for me regardless of y'all decide to go.

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.