`Self` requirements in non-final classes

In the following example:

protocol P {
    typealias _Self = Self
    var p: AnyPublisher<Self, Never> { get }
}
extension P {
    var p: AnyPublisher<Self, Never> { Empty().eraseToAnyPublisher() }
}
class A: P { // Protocol 'P' requirement 'p' cannot be satisfied by a non-final class ('A') because it uses 'Self' in a non-parameter, non-result type position
}

We find that there exists a limitation in using Self on a non-final class in the position of a type parameter (even though this position is technically a result-type position).

Would love to source some feedback on why this limitation exists, what it aims to address, and whether it can be lifted either through a more narrow check or through synthesis of the required default implementations for the necessary concrete types.

Yeah, Swift doesn't allow you to specify the variance of your generic parameters, so as far as the language is concerned AnyPublisher<A, Never> and AnyPublisher<B, Never> are completely unrelated types. The variance relationships of a few types (e.g. Array, Optional) are known to the compiler.

I think the best you can do today is something like this:

protocol P {
    associatedtype Result
    var p: AnyPublisher<Result, Never> { get }
}
extension P {
    var p: AnyPublisher<Result, Never> { Empty().eraseToAnyPublisher() }
}
class A: P {
    typealias Result = A
}

but this loses the requirement that Result and Self be related. :slightly_frowning_face:

Ideally you could do something like:

associatedtype Result: Self

which would then let you declare

typealias Result = A

within the definition of A, so that if you had class B: A you'd still have B().p with type AnyPublisher<A, Never>.

1 Like

Thanks, @Jumhyn.

The original problem I was seeking to address was to expose a variant-specific signature on all concrete implementations of a protocol without adding boilerplate to each concrete subtype.

Ergo, ideally I'd like to limit the boilerplate to just P, and not bleed it into the concrete variant subtypes.

I found that I can achieve my goals using exclusively a protocol extension which isn't declared on the protocol itself:

protocol P {
}
extension P {
    var p: AnyPublisher<Self, Never> { Just(self).eraseToAnyPublisher() }
}
class A: P {
}

However, while this works for the original use case, it would be great to have a generic solution per your analysis.

Furthermore, this would likely only resolve Self on concrete types explicitly declared as conforming to P, and not any variant subtypes of them (per Covariant 'Self' - #2 by Jumhyn).