Inferred result type requires explicit coercion

I'm having trouble with this error (with Xcode 14 beta 5; on previous betas the compiler just crashed):

protocol Primary<ID>
{
    associatedtype ID
    associatedtype Sec: Secondary<ID>
    var sec: Sec? { get }
}

protocol Secondary<ID>
{
    associatedtype ID
    var name: String { get }
}

func f(p: any Primary) -> String?
{
    // Inferred result type '(any Secondary)?' requires explicit coercion due to loss of generic requirements
    p.sec?.name
}

I can satisfy the compiler by explicitly casting p.sec to (any Secondary)?, but I don't understand why it's necessary. What are the "generic requirements" that got lost?

And interestingly I get a different error if I get rid of the associated type Primary.Sec and reference Secondary<ID> explicitly:

protocol Primary<ID>
{
    associatedtype ID
    var sec: (any Secondary<ID>)? { get }
}

protocol Secondary<ID>
{
    associatedtype ID
    var name: String { get }
}

func f(p: any Primary) -> String?
{
    // Runtime support for parameterized protocol types is only available in macOS 13.0.0 or newer
    p.sec?.name
}

Why does this get a different result?

Since SE-0309, Swift permits you to use a protocol with self or associated type requirements as a type. In your first example, you have an instance of such an existential (p), and you are calling a property sec which, as the proposal describes, is subject to covariant type erasure to its upper bound, the type that most closely describes the capabilities of that associated type.

With the latest and greatest improvements in Swift, we now have an implementation of SE-0353, allowing the "most closely describ[ing]" type to in this scenario to be (any Secondary<ID>)?. However, such a constrained existential type requires runtime support, and since Swift is now shipped with macOS, that means that the upper bound can be (any Secondary<ID>)? only when macOS 13 or later is available.

In the absence of runtime support for constrained existential types, the "most closely describ[ing]" type is instead (any Secondary)?, which isn't as specific as the tightest possible upper bound type, (any Secondary<ID>)?. Specifically, you lose the information that the type of p and the type of p.sec always have the same associated type ID.

Fortunately, the loss of that constraint doesn't affect your ability to use name, but in the general case, having a less specific upper bound means that there are things you can't do that rely on knowledge of the discarded constraint. So that you have an opportunity to explicitly acknowledge this loss of specificity, and also so that the inferred upper bound type doesn't change silently when you recompile for a later version of Swift, you're required to write the explicit cast to (any Secondary)? in your source.

In your second example, you have changed the protocol requirement for the type of sec to an optional existential (any Secondary<ID>)?; this has nothing to do anymore with inferring an upper bound existential type: you've specified an explicit constrained existential type for which runtime support isn't available before macOS 13, so you can't use it.

3 Likes

Thanks! That was a great explanation.