Same-type Requirement Error and Implicit Existential Opening

If two generic parameters are intentionally made equivalent by a same-type requirement, then you may as well delete the second generic parameter entirely, since it no longer serves a distinct purpose. But since this is not usually the intended outcome, the warning is there to tell you that potentially have made a mistake in a where clause.

Now, consider this code:

protocol P {}
struct S: P {}

class C<T: P> {
  init(_: T) {}
}

let p: any P = S()
C.init(p)

(You can also write C instead of C.init). The reason this is rejected is that the type of the initializer C.init is <T> (T) -> C<T>. While it is valid to open an any P and bind it to T for the parameter, the return type of the initializer allows the opened type to "leak out" inside C<T>. It is not correct to replace T with any P there because C<any P> is not a subtype (or supertype) of any other C<T>.

The trick with the UIViewController works because the static function returns a base class and not the generic class itself (you've erased the type parameter T).

In fact I would consider it a bug that this works:

class C<T: P>: Base {
  init(_: T) {}

  static func f<U: P>(_ t: U) -> Base where T == U { return C(t) }
}

But this doesn't:

class C<T: P>: Base {
  init(_: T) {}

  static func f(_ t: T) -> Base { return C(t) }
}

The call C.f(p) where p: any P should be type-safe. @Douglas_Gregor, what do you think?

In the mean-time, can you use a top-level function instead of a static method?

protocol P {}
struct S: P {}

class C<T: P>: Base {
  init(_: T) {}
}

static func f<T: P>(_ t: T) -> Base { return C(t) }

let p: any P = S()
f(p)
5 Likes