Using typealias from protocol in implementing type

I have a PAT, where I would like to declare a typealias to improve ergonomics of the conforming code.

Below is the simplified example, reproducing the problem:

protocol P {
    associatedtype A
    typealias B = Array<A>

    func make(x: A) -> B
}

struct Q {
    typealias A = Int
    func make(x: A) -> B { [x] } // error: Use of undeclared type 'B'
}

In actual codebase B is a long generic type that I really don't want to type in every single implementation of the protocol.

My impression was that SE-0092 should allow this, but apparently this does not work.

I've also tried Self.B, Q.B, P.B`, but no luck.

You forgot to conform Q to P.

Indeed :smile:. I didn't notice that the error in production code is different from the error in the demo.

The error I'm getting in production is Reference to invalid type alias 'B' of type 'Q'.

After some experimentation, I was able to come up with an example which reproduces the problem:

protocol P {
    associatedtype A
    typealias B = [A]
    associatedtype C

    func foo() -> A
    func bar(_ x: B) -> C
}

struct Q: P {
    func foo() -> Int { 42 }
    func bar(_ x: B) -> String { "\(x)" }
}

I'd like compiler to:

  1. infer A based on signature of foo()
  2. resolve B based on this
  3. substitute resolved B into bar()
  4. infer C based on the signature of bar()

In theory, this should be possible - there are no circular dependencies. But apparently, at the moment this is too much.

But if I add explicit type alias for C, inference of A starts to work in the example. While in the production code, C is already declared as a nested type. So, maybe example is still not 100% representative.

Here is the minimized example based on stripped down production code. This one should be representative.

protocol ChildrenCollection {}
struct Q: ChildrenCollection {}
class ProviderImpl<WC: ChildrenCollection> {}

protocol ContainerWidget {
    associatedtype Children: ChildrenCollection // A
    typealias Provider = ProviderImpl<Children> // B

    func children() -> Children
    func foo(provider: Provider)
    func bar(provider: Provider)
}

struct MyWidget: ContainerWidget {
    func children() -> Q { Q() }
    func foo(provider: Provider) {} // No error! Why?
    func bar(provider: Provider) {} // Error: Reference to invalid type alias 'Provider' of type 'MyWidget'
}

Commenting out one of the foo() or bar() from both ContainerWidget and MyWidget eliminates the error.

UPD: Minimized ever further. Type C seems to be irrelevant.

Interestingly, for me at least (Swift 5.1.3, Xcode 11.3.1) if I leave both foo and bar in the struct, but comment one out in the protocol, I get different results:

If the protocol has foo as requirement (but not bar), then the program compiles.

But if the protocol has bar as a requirement (but not foo), then it does not compile.

Put another way, the declaration order of foo and bar in the struct affects whether the code compiles.

1 Like

Did some quick testing, and both of these work:

struct MyWidget: ContainerWidget {
    func children() -> Q { Q() }
    func foo(provider: ProviderImpl<Q>) {}
    func bar(provider: ProviderImpl<Q>) {}
}

struct MyWidget: ContainerWidget {
    typealias Children = Q
    func children() -> Q { Q() }
    func foo(provider: Provider) {}
    func bar(provider: Provider) {}
}

From what I can tell, the compiler is able to infer that Children has type Q as normal. But once it typechecks foo and bar, it does not know yet whether or not MyWidget comforms to ContainerWidget, and decides the Provider typealias cannot be applied.

My guess is that when one of the function requirements are removed, the compiler is able to make some assumptions since it only needs to satisfy a constraint over a single requirement. But when Provider is referenced twice, the compiler doesn't know how to handle it.

Could be a bug, tbh.