[Accepted] SE-0352: Implicitly Opened Existentials

Hi everyone. The second review of SE-0352: Implicitly Opened Existentials completed on May 18, 2022, and the core team has decided to accept the proposal. This second review focused on forward compatibility concerns when the return type of a call involving an implicitly-opened existential has to be type-erased to keep the dynamic type of the existential from leaking into the return type. As the expressivity of existentials increases, we may be able to use a more specific type as the return type of such a call, which could create source breaks. To avoid this, the proposal required an explicit as any P type annotation on calls where the return type that can be expressed today would discard constraints on the original associated type being type-erased, as in the example from the proposal:

protocol P {
  associatedtype A
}
protocol Q {
  associatedtype B: P where B.A == Int
}

func getP<T: P>(_ p: T)
func getBFromQ<T: Q>(_ q: T) -> T.B { ... }

func eraseQAssoc(q: any Q) {
  let x = getBFromQ(q)          // error, must specify "as any P" due to loss of constraint T.B.A == Int
  let y = getBFromQ(q) as any P // ok, explicitly throws away constraint
}

During review, an issue was raised with this design that it becomes ambiguous when a call like this is used as the argument to another call, since as any P is also the syntax for suppressing implicit opening of an existential. In response, the authors revised the proposal to document this ambiguity, and explicitly specify that using parentheses prevents the "suppress implicit opening" meaning from taking effect:

protocol P {
  associatedtype A
}
protocol Q {
  associatedtype B: P where B.A == Int
}

func getP<T: P>(_ p: T)
func getBFromQ<T: Q>(_ q: T) -> T.B { ... }
func eraseQAssoc(q: any Q) {
  getP(getBFromQ(q))          // error, must specify "as any P" due to loss of constraint T.B.A == Int
  getP(getBFromQ(q) as any P) // suppresses error above, but also suppresses opening, so it produces
                              // error: now "any P does not conform to P" and op
  getP((getBFromQ(q) as any P)) // okay! original error message should suggest this
}

The core team has accepted these amendments to the original proposal, and the community response was positive as well. Thank you to everyone who participated in both rounds of review!

22 Likes

Should this behavior be available in Xcode 14 beta 2, or is its version of Swift 5.7 still lagging? I can't seem to pass existentials to functions that take the generic equivalent yet.

Edit: Ah, it actually does seem to work, just not in playgrounds. I guess playgrounds somehow don't support this feature yet? Filed FB10447228.

1 Like