When and why can't you qualify a generic type's placeholders with the type namespace?

Primary associated types have made it so that I hardly need this anymore :revolving_hearts:, but it's not always possible to use them.

enum E<T> {
  typealias πŸ«–1 = T      // compiles

  typealias πŸ«–2 = Self.T // 'T' is not a member type of generic enum 'E<T>'
  typealias πŸ«–3 = E.T    // same
  typealias πŸ«–4 = E<T>.T // same
}

Is the bug just with the bad error message, or should no error be generated?

Protocols don't exhibit the restriction.

protocol P<T> {
  associatedtype T
  typealias πŸ«–1 = T      // compiles
  typealias πŸ«–2 = Self.T // compiles

  typealias πŸ«–3 = P.T    // Cannot access associated type 'T' from 'P'; use a concrete type or generic parameter base instead
  typealias πŸ«–4 = P<T>.T // same
}

And why does it sometimes work? E.g. this compiles:

public extension Dictionary {
  /// Group key-value pairs by their keys.
  ///
  /// - Parameter pairs: Either `KeyValuePairs<Key, Self.Value.Element>`
  ///   or a `Sequence` with the same element type as that.
  /// - Returns: `[Key: [Value]]`
  init<Value>(grouping pairs: some Sequence<(key: Key, value: Value)>)
  where Self.Value == [Value] {
    self = [_: _](grouping: pairs, by: \.key).mapValues { $0.map(\.value) }
  }

…but

extension E {
  // 'T' is not a member type of generic struct 'E<T>'
  func Ζ’<T>(_: some Sequence<T>) where Self.T == [T] { }
}

You'd need to declare T as a typealias within the type's body. If you conform to a protocol with associatedtype requirements, then the compiler does it for you. For Dictionary, they probably come from its ExpressibleByDictionaryLiteral conformance with the associatedtype requirements for Key and Value.

(Edited to add: I vaguely remember an old version of Swift requiring the conforming type to further declare a nested type typealias Key = Key if the compiler couldn't infer it from other protocol requirements. I'm glad that generic arguments no longer require that.)

3 Likes

What @pyrtsa has said is right on. The error message is entirely correctβ€”the generic parameter T is not a member of the type: this was an early design decision (which cannot be changed now because it’s not possible to know how much source breakage there would be). To make T a member type, declare typealias T = T (or, as you’ve found, conform to a protocol where there is an associated type of the same name and make use of implicit inference rules).

3 Likes

Swift Regret: Generic Parameters Aren't Members

8 Likes