protocol P {
associatedtype A
func getA() -> A
}
func openSimple<T: P>(_ value: T) { }
func testOpenSimple(p: any P) {
openSimple(p) // okay, opens 'p' and binds 'T' to its underlying type
}
I was wondering can we move further to open existential for something like this?
(Currently the following code will give an error)
protocol P {
associatedtype A
func getA() -> A
}
struct S<T: P> {
var s: T
}
func testOpenSimple(p: any P) {
let _ = S(s: p) // **error**, Type 'any P' cannot conform to 'P'
}
There is a temp workaround for solving this - Add a function to bridge.
But still we can't get a concrete type S variable from any P(which means opening existential on any P)
protocol P {
associatedtype A
func getA() -> A
}
func openSimple<T: P>(_ value: T) {}
struct S<T: P> {
var s: T
}
func testOpenSimple(p: any P) {
openSimple(p) // okay, opens 'p' and binds 'T' to its underlying type
// _ = S(s: p) // **error**, Type 'any P' cannot conform to 'P'
func bridge1<T: P>(_ s: T) {
_ = S(s: s)
}
bridge1(p) // okay, we use a function to bridge the init usage
func bridge2<T: P>(_ s: T) -> S<T>{
S(s: s)
}
// _ = bridge2(p) // **error**, Type 'any P' cannot conform to 'P'
}
In this kind of initializer, you're implicitly using the generic type twice — once in the parameter and again in the return type. SE-352 allows the implicit opening of existentials only when the generic type occurs exactly once in the function signature.
It seems possible that the compiler could be made to special-case this scenario and substitute the opened type in both places, but maybe there's a technical reason why not.
For now, though, the compiler is correctly enforcing the SE-352 rules.
When you invoke an initializer, it's acting like a generic function call of type <T:P> (s: T) -> S<T>. If we open p, that calls the initializer by binding T to the dynamic type inside the value of p, so the call becomes (s: {dynamic type of p}) -> S<{dynamic type of p}>. SE-0352 backed off from providing a way to refer to {dynamic type of p} after p is opened, and instead chose to leave cases like this where the dynamic type shows up as part the return type unsupported. The diagnostic should be improved to make this more obvious.
If it makes sense to make the generic S type conform to a protocol, then instead of calling the S initializer directly, you could use a static factory method that returns the S as a protocol existential or opaque return type, which you can then use with an opened existential:
protocol Q {}
struct S<T: P>: Q {
var s: T
static func make(s: T) -> some Q { return S(s: s) }
}
func testOpenSimple(p: any P) {
let q = S.make(s: p) // q has type `any Q`
}
Filing an issue on our Github would be good, yeah. If you can post an example of the code you expect to compile, or even the example I just provided, that should be sufficient. Thank you!
In that case, I think some kind of error is appropriate, although the generic error you're getting is not very descriptive. Calling bridge(keypad: keypad) inside of bridge2 passes the dynamic type into bridge, but then re-boxes the return value of bridge into an any View. Since bridge2 is declared to return a concrete value of some View type, any View doesn't satisfy that return type requirement. It's still worth filing a bug to improve the diagnostic.
Is this bug the reason why we cannot use @ObservedObject in generic views constrained to protocols yet?
E.g.
import PlaygroundSupport
import Foundation
import SwiftUI
protocol P: ObservableObject {
var hello: String { get }
}
final class Implementation: P {
@Published var hello: String = "Hello"
}
struct Sample: View {
let something: any P = Implementation()
var body: some View {
VStack {
PView.make(something) // Type 'any P' cannot conform to 'P'
createPView(something) // Type 'any P' cannot conform to 'P'
}
}
func createPView<T: P>(_ s: T) -> PView<T> {
PView(s)
}
}
struct PView<T: P>: View {
@ObservedObject var p: T
public init(_ p: T) {
self.p = p
}
var body: some View {
Text(p.hello)
}
static func make(_ s: T) -> some View { PView(s) }
}
PlaygroundPage.current.setLiveView(Sample())
You can't have it both ways. While you can use a generic type inside the function by opening the existential in some way, you can't return it or use it in the main body of the function, since it requires the generic parameter.
func makeArray(withOneValue: Any) -> Array</* what would you write here? */> {
let array: Array</* what would you write here? *?> = [withOneValue]
{