Proposing to expand `available` to introduce `discouraged`

This pitch skips over precisely what diagnostics would be provided to the user if a discouraged declaration were used, when that's really the key information needed to evaluate it. My reading of it seems to imply that it would only surface information for IDE usage but not emit compiler diagnostics—is that the intention?

Using either interpretation though, I worry about introducing a new level of nuance here:

  1. If usage of a discouraged declaration only surfaces information in an IDE, but does not cause compiler warnings to be emitted, then we lose the ability to detect (and possibly fail on) usage of discouraged decls in automated builds without using a semantics-aware (i.e., SourceKit-based) external tool. It would be easier for the compiler to just emit a diagnostic. (If the compiler can warn me about deprecated decls, why not discouraged ones?)
  2. If usage of a discouraged declaration also emits a compiler warning, then it is not meaningfully distinct from deprecated except in the text of its message.

In most cases, I don't think the distinction between discouraged and deprecated would be very clear, and where it is I would expect the latter to be preferred because, assuming interpretation #1 above, deprecated is a stronger guarantee. I think we'd get more mileage by fixing deprecated so that it works better than it does today, and then looking at alternatives to discouraged for the other use cases that avoid the introduction of nuance.

Fixing deprecated

These examples look precisely like deprecation to me. When you say that "the original form is required for compatibility and cannot be deprecated", what is blocking the deprecation? If it's the warnings emitted by a module's own uses of the deprecated declaration, or by other valid uses that cannot be migrated easily, then we should solve that problem—it's one that has come up a few times before on this forum (one such thread). Swift today doesn't give any control over where deprecation warnings are emitted, and possible remedies have included omitting deprecation warnings in the same file/module, attaching a "deprecation scope" to a decl that says where its use is permitted, or just letting users bracket their code with directives that enable/disable deprecation warnings for a region.

This is an issue that we've faced on swift-protobuf, for example, because we would like to generate @available(*, deprecated) annotations for properties that represent deprecated messages/fields in the original .proto file. However, the generated serialization code in the same type must necessarily refer to the deprecated property in order to read/write it, so I wouldn't even be able to compile the type's own implementation without warnings. I don't think a new availability level is needed to solve that problem—I just want better control over the existing deprecation.

Discouraging access to decls that aren't "deprecated"

If the intention is to indicate that these initializers should not be directly called (something that I agree with), then we should take a stronger stance than documenting that via an attribute. Given that these protocols/initializers are already given special treatment by the compiler, it could theoretically ban direct calls, and we should weigh that against source compatibility concerns.

Is anyone writing code today that depends on being able to call these, and if so, do we believe that to be harmful enough to outweigh the cost of forcing them to migrate? If we don't believe that, do we think that it's still a serious enough issue to introduce a new level of nuance in @available?

More generally, this is a case of a declaration being forced to be public for external reasons, such as making it accessible to tests or to a closely-related companion module, but otherwise it's not meant to be generally used by clients. In those situations, I prefer the direction taken by the recently added experimental SPI support, because it's enforced by the compiler instead of just being a suggestion.

16 Likes