SE-0521: Improved Syntax for Optionals of Opaque and Existential Types

Hello, Swift community!

The review of SE-0521: Improved Syntax for Optionals of Opaque and Existential Types begins now and runs through March 30, 2026.

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 me as the review manager by email or DM. When contacting the review manager directly, please put "SE-0521" in the subject line.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  • 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?

More information about the Swift evolution process is available at:

swift-evolution/process.md at main · swiftlang/swift-evolution · GitHub

Thank you,

Freddy Kellison-Linn
Review Manager

21 Likes

Very glad to see this one, it's been a constant annoyance for me personally, writing a lot of some/any optional parameters... :sweat_smile:

Migration to this might be held up a bit in libraries needing to compile on older Swifts for a while, like the package ecosystem, however it's a beneficial change overall IMHO.

I was briefly wondering if there's any confusion that might arise with typealiases like typealias Kappa = any P but AFAICS this doesn't really change much here. I.e. Kappa? would be (any P)?, right?

3 Likes

Very reasonable feature.

I know we want to narrowly scope this, but I do wonder if making any ~Copyable? 'just work' in the same release (i.e., also rolling in support for ~) would make for a nicer resting place.

5 Likes

Love it! Such a welcome improvement. Looking forward to autocorrecting out a ton of parens :slight_smile:

1 Like

Strong +1!

Having just migrated a fairly large code base to more modern Swift including ExistentialAny, I can confidently say that omitting these parentheses would be very welcome! Every time I wrote them, I asked myself what clarity they are supposed bring (although IIRC, the original proposal about this topic already mentioned that omitting them would be future direction).

5 Likes

Would this also become the canonical representation in a .swiftinterface file?

1 Like

Correct; since there's no optional on the right hand side of that typealias, its meaning is unchanged by this proposal.

On the surface, this seems like a reasonable change. I have mixed thoughts on it though from subtle language grammar and implementation points of view, though.

In Swift expressions, postfix operators always bind more closely than prefix operators—that's a fundamental parsing rule in the language. Now, types don't always have to follow the same rules as expressions with regard to "operators" when they're parsed in type contexts, but there's one rub: types that must be parsed in expression context and be converted later during semantic pre-checking. Something like:

let x = [() -> ~Copyable?]()

that must produce a parse tree like (~(Copyable?)) because we have no way of knowing at parse time whether that's a type or some other expression.

On the other hand, if we see any in that context, the parser knows that what follows must be a type, so it can enter a type parsing context. In other words,

let x = [() -> any ~Copyable?]()

could use different type parsing rules that fold the ~ into the constraint.

Now here's where things get more interesting: unlike existentials of regular protocol constraints, existentials of suppressed constraints unconditionally emit a warning:

let x = [() -> P?]()  // OK
let x = [() -> (~Copyable)?]()
             // `- warning: constraint that suppresses conformance requires 'any';
             //    this will be an error in a future Swift language mode [#ExistentialAny]

If we were already at the point where any was required, I'd say we could go for it and handle ~ here. But to do that today where the any-less form is still allowed would require some concession:

  • We subsume ~ in type contexts but not in expression contexts, leaving the current surface language in an inconsistent state
  • We subsume ~ in all contexts, which requires special casing the expression form during semantic pre-checking
    • However, my original implementation did this (avoided changing the parse trees anywhere and did the rearrangement during pre-checking) and it was felt to be the wrong approach to take. Unlike the existing support for just ? which could be handled entirely on the parse tree side, supporting ~ would force us into a position where we have to use the non-preferred implementation to handle a subset of valid syntax.

I would need to double-check this. One thing I'd like clarity on is, is it permitted for a .swiftinterface from a newer compiler to be consumed by an older compiler if the interface doesn't use any language features newer than the compiler that consumes it? If so, using the new syntax wouldn't be safe. If that use case isn't supported, then I think we should use the more compact syntax whenever printing an AST.

That's certainly an interesting point. Although we deliberately paused requiring any in Swift 6, I'm surprised we extended any-less usage to suppressed constraints. I don't recall this aspect of the feature getting specific review, and we had notably declined to take that approach when we added support for existentials of protocols with Self or associated type requirements.

Your point is well taken that any resolution here leaves a lump in the rug somewhere, so to speak. In the end, though, I still think pushing the lump around so that any ~Copyable? and some ~Copyable? both "just work," even if (~Copyable)? still doesn't, is an improvement over the status quo. To me at least, it would feel like less of a surface language inconsistency than requiring (some ~Copyable)? but permitting some Copyable? on the basis of interactions with an extension of a rule in flux revolving around any. (It would also have the salutary effect of being a forcing function to get around to supporting ~Copyable? whenever we do come to an ultimate resolution on the existential any issue.)

3 Likes

This would prevent module interfaces generated by a newer compiler from being accepted by an older compiler, even if no other new features are used by the module's public interface. I think printing out all types with disambiguating parentheses and other sugar in place is best for a module interface anyway, since its not intended to be read by a human developer directly.

4 Likes

To be absolutely clear, .swiftinterfaces are not formally intended to be backward compatible between major releases of the compiler (even when no new features are used). They are an artifact for which maintaining forward compatibility is required, but have no backward compatibility guarantees. That said, practically speaking there are many workflows that need to be supported where various recent versions of the compiler do need to consume newer .swiftinterface files for a temporary window of time. We therefore stage in changes to .swiftinterface printing to maintain backward compatibility for a while, but not indefinitely.

I think Slava is right, though, that we should just avoid this question altogether and continue to print types in .swiftinterface files with disambiguating parentheses since they are artifacts designed for compiler consumption, not human consumption.

7 Likes

Thank you to everyone who participated in this review! The proposal has been accepted with modifications.