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