The rules around existential any syntax are becoming increasingly convoluted and hard to follow. Whether or not you enable the ExistentialAny feature, a missing any can produce an error, a warning, or nothing at all, depending on an already painfully large amount of factors, including bugs. Here is a rough outline of the status quo:
Any and AnyObject are always exempt from any syntax.
A missing any is an error if a protocol constraint is suppressed, e.g. ~Copyable.
A missing any is an error if a protocol has "Self or associated type requirements". What exactly this term of art means is a mouthful on its own.
A missing any is an error under ExistentialAny.
A missing any that is an error in any of the above cases is a warning until a future major language mode if the type appears in a catch pattern.
Various bugs where we should — but do not — error (1, 2, 3).
Motivation
The current rules for when and how a missing any is called out are unreasonably confusing.
Rule 3 implies that introducing a Self or associated type requirement to a protocol (with appropriate default implementations) is a source-breaking change for clients that do not already spell the protocol with any.
Many projects are yet to opt in to ExistentialAny, and the migration experience leaves much to be desired, if only because the feature often entails scores of errors that demand immediate attention.
The any syntax checker is not doing a good enough job. Source-compatible fixes for the aforementioned bugs are technically challenging and will make the algorithm significantly less robust. They also will further complicate the rules. On the other hand, source-incompatible fixes for these same bugs are known to cause regressions.
Proposed Solution
Keep rule 1.
A missing any is never diagnosed pre ExistentialAny. This will make the rules straightforward and consistent. Rules 2 and 3 were introduced in relation to previously unsupported code for the sole purpose of reducing the source compatibility impact of ExistentialAny — a concern addressed by the next step.
A missing any is a warning under ExistentialAny and an error in the future major language mode that will include ExistentialAny. This will encourage developers to adopt any sooner and grant us ample time to gracefully address any remaining bugs before the source compatibility burden resurfaces.
Introduce a new group for any syntax diagnostics. This will allow developers to unconditionally escalate the warnings with -Werror <group>.
We've been thinking about this lately as a part of Airbnb's Swift Style Guide. We find that the current status quo of any being optional in many places, but sometimes required, creates confusion for engineers.
When there are two equivalent syntaxes for the same functionality, we prefer to have a style rule defining a preference, and automate that rule with code autocorrect (SwiftFormat). Code autocorrect is an important part of this because it decreases the mental overhead of following each individual style rule (since it becomes automatic).
We'd like to have a rule to prefer omitting any in Swift 5 and 6, however currently can't implement autocorrect for this rule. any is required in a small subset of cases, like if the protocol has a Self or associated type requirement. Code formatters that operate on individual files (so, all of the popular code formatters / linters for Swift code) don't have this information, so can't distinguish the required anys from the optional anys.
A missing any is a warning if a protocol has "Self or associated type requirements".
In the case we're considering, this is helpful (code without these anys can still compile) but not sufficient (code autocorrect that generates tons of warnings is not viable).
Could we have all of these warnings be gated by the ExistentialAny future feature flag, as opposed to having a small subset of anys be required?
Until any is required everywhere, allowing any to be optional everywhere (without producing warnings) would be a usability improvement over the current status quo.
The various bugs that inhibit understanding are a huge issue.
Separately, how the source-breaking feature, even if it were to be perfectly implemented, is being staged in over releases and language versions without users being able to recover a concrete benefit from handling the source break is an issue.
Separately, even if there were no issues with how we are staging it in, I understand that there is some disagreement about what both the appropriate next resting place and the final destination ought to be with respect to this feature.
I think we need some clarity on all of these points—which are separable discussions—before it's possible to understand how more zagging on the specific diagnostics and feature flags and language modes where previously we were zigging advances the "direction of Swift."
I don't think we should re-litigate the resting place of SE-0335 as part of this pitch.
SE-0335 predates upcoming features, and this pitch is about providing a better staging experience for SE-0335 now that we have upcoming features and precedence from other upcoming features that introduce diagnostics as warnings. Most Swift 6 upcoming features took the approach of enabling warnings for the change under the upcoming feature, which are promoted to errors under the language mode that enables the upcoming feature by default. I think this is a good approach, especially when paired with diagnostic groups and control over diagnostic behavior via SE-0443; by default, programmers can evaluate all of the places they need to make source changes when enabling an upcoming feature, continue building and testing as they incrementally address the warnings, etc. If they want to escalate those warnings to errors, they can do so using the diagnostic group. I'd advocate for changing the ExistentialAny implementation to follow this practice even if the feature were implemented perfectly and we didn't have a source compatibility problem with bug fixes in the implementation.
Unless I'm mistaken, this pitch is explicitly not about staging in SE-0335, which has already been retrofit with an upcoming feature flag. Rather, it discusses "steps [to] reset the current rules back to SE-0335 days" for subsequently expressible code which has never had source-compatibility implications.
That is, it proposes to make code which has never compiled in any version of Swift and which did not exist at the time of SE-0335—for example, existentials missing any for protocols with Self or associated type requirements—compile with warnings (or even without warnings, per @cal's suggestion for all usages and per the pitch specifically for suppressibles like ~Copyable), and that it should do so retroactively even in the Swift 5 language mode.
A good reason to do this might be that we are no longer confident in the direction of Swift articulated in SE-0335, or that we have new evidence that the subsequent progress made in that direction does not leave us with a satisfactory resting place. But what is pitched is both a new feature and a new (diametrically opposite) direction: it is not about staging in SE-0335 with an upcoming feature flag—a step we have already taken.
There is an existing ExistentialAny upcoming feature flag, but it opts you into errors about missing any, not warnings. This prevents you from incrementally addressing those issues while continuing to build and test your code along the way. For example, if you wanted to switch to some instead of any to address the error, you cannot actually build and test your code without addressing all other missing any errors. This pitch downgrades those errors to warnings to provide a better staging workflow for the existing ExistentialAny upcoming feature, while still preserving the ability to opt into errors using the diagnostic group.
The ability to write existential types using protocols with Self or associated type requirements did exist at the time of SE-0335 - that was introduced with SE-0309. The rule in SE-0335 to require any for such protocols was opportunistic because SE-0309 and SE-0335 were included in the same release of Swift, with the following justification (copied from the source compatibility section of SE-0335):
SE-0309 Unlock existentials for all protocols enables more code to be written using existential types. To minimize the amount of new code written that will become invalid in Swift 6, I propose requiring any immediately for protocols with Self and associated type requirements. This introduces an inconsistency for protocols under the Swift 5 language mode, but this inconsistency already exists today (because you cannot use certain protocols as existential types at all), and the syntax difference serves two purposes:
It saves programmers time in the long run by preventing them from writing new code that will become invalid later.
It communicates the existence of any and encourages programmers to start using it for other existential types before adopting Swift 6.
The purpose of this rule was to help with the staging of ExistentialAny to alleviate some of the code churn when you do enable the upcoming feature. We can decide that the inconsistency was not worth lessening the code churn later on, both for programmer understanding and for the difficulty it introduces for compiler engineers fixing bugs in the implementation of SE-0335. But this is also a separate decision point than the above issue. Even if we decide to keep requiring any for new kinds of existentials that have been introduced in SE-0309 / SE-0353 and SE-0427, we can still change the ExistentialAny upcoming feature to emit warnings instead of errors.
There are other decisions we can make too - we could decide to continue requiring any for constrained existential types and for existentials with suppressed constraints, but move any diagnostics for protocols with Self and associated type requirements under the upcoming feature flag. In hindsight, I think the exception to requiring any in SE-0335 is particularly confusing, because it's been a few years since SE-0309, and there are programmers who never knew about the limitations of protocols with these requirements. The rule not only means you need to understand language evolution history to understand why the rule exists, but it also means that adding a Self or associated type requirement to a protocol (with appropriate default implementations) is a source breaking change if a client uses that existential without any. These issues don't apply to the other kinds of existentials that currently require any.
I don't agree that this pitch is a new feature and a new direction. Yes, there is an existing upcoming feature flag, but there are usability issues with the existing upcoming feature as currently implemented, and the current implementation prevents us from fixing bugs where a missing any is not properly diagnosed. That is the motivation for this pitch. Of course it's fine if you don't agree with that justification or if you see different reasons to make these changes.
Personally, while I am still interested in continuing the discussions about eliding some, I still think the direction laid out in SE-0335 is better than the pre-SE-0335 state, and I think the changes in this pitch are valuable independent of eliding some. My experience from working on data-race safety in Swift 6 is also that warnings in some contexts but not others don't necessarily help programmers reach the end state of full enforcement of those rules due to the confusion it introduces in the meantime - many folks are very confused by minimal concurrency checking showing preconcurrency actor isolation diagnostics only in contexts that explicitly use a concurrency annotation. I believe that upcoming features are a much better mechanism for staging in these sorts of changes, even if it means there's a bit more change in code that is written between now and the time that upcoming feature is enabled.
Regardless of whether this is a new pitch or hidden under feature flags or whatever, I think people are asking that this anomaly be removed for the sake being able to implement linters which would be a win and could be delivered in short order without a source break.
My opinion is the situation with SE-0335 needs to be clarified as my impression is that it should be formally withdrawn if the feature flag is never going to be turned on. Talk of eliding to some (which is not feasible if you look at real code) makes me nervous this unfortunate chapter in Swift evolution has not reached its final resting place. Adding the any keyword, fine. Making it compulsory would have been (and would still be) massively source breaking and an order of magnitude more disruptive than any of the other Swift 6 migration problems discussed of late.
Yeah, as and educator I often just go with "because reasons" when trying to explain why any is required for just some protocols. But truth to be told, I really wish that either any or some was always required. It's just SO MUCH easy to explain protocol usage with the keywords.
Thank you for clarifying this bit--it's a blink-and-you'll-miss-it footnote in the pitch. When we retrofit the feature flag onto SE-0335, I don't recall specific discussion as to what the diagnostics behavior with the flag should be.
Although underspecified, my assumption then and now (until this pitch pointed out that it does not behave this way) was that in the absence of specific comment the retrofit flag would behave as all other feature flags do. To my mind, this ("change the ExistentialAny upcoming feature to emit warnings instead of errors") is a part of this pitch that can be severed and considered a bugfix.
SE-0309 was accepted but hadn't shipped at the time of SE-0335; in fact, the proposal histories state that it shipped one point release after. For which reason, as you say, there was no code churn involved to adopting mandatory any for protocols with Self and associated type requirements.
One needn't invoke proposal histories to explain the current design of Swift (modulo any ~Copyable and friends): You can write any to mark any existential type, but it is mandatory where the existential type may not be able to expose the full interface described by the protocol due to Self or associated type requirements.
This may be an argument to make any optional (for now) for suppressible constraints.
to me Concurrency–related warnings are completely different from existential any-related warnings.
the former requires a lot of difficult problem solving to resolve, and in many cases, cannot be resolved at all due to code outside the developer’s control.
the latter is such a mechanical process that we really ought to be able to create a tool to automate the migration.
I do not think we want it to be mechanical as in automatically prepend any everywhere. Existentials are often misused, and there are some type positions, such as value parameters, where we really should be offering a choice between any and some.
I think it should be mechanical by default with an option to use type parameters instead. In any case, automatic migration is not the focus of this pitch. (though everyone can look forward to a forthcoming pitch for a discussion on migration tooling for upcoming features!)
I amended the proposed solution to never diagnose when ExistentialAny is disabled. I don’t think the more conservative original solution is worth mentioning in the alternatives considered section because it barely addresses the complexity of the rules, never mind Cal’s point about having to live with selective warnings when the legacy syntax is enforced by tools.